FIKR.SPACE LIVE · Q3 2026 LAUNCH
FOUNDING / 200 JOIN FOUNDING
Home/Blog/AI Software Development/Worker KV vs D1 Database: When to Use Ea…
AI Software Development

Worker KV vs D1 Database: When to Use Each

How we reduced query latency by 89% and cut database costs by 75% by choosing the right Cloudflare data store for each use case in our edge computing stack.

How we reduced query latency by 89% and cut database costs by 75% by using Worker KV and D1 Database strategically across our edge computing infrastructure

The Problem: Every Data Access Looked the Same

When we started building FIKR's edge-rendered platform on Cloudflare Workers, we faced a fundamental question that would determine our application's performance and cost structure: where should we store our data? Coming from a traditional backend architecture with PostgreSQL handling everything, our first instinct was to reach for Cloudflare D1, their SQLite-based distributed database. It felt familiar, comfortable, and capable of handling complex queries.

But after a few weeks of development, we noticed a troubling pattern. Simple operations like fetching a user's theme preferences or checking feature flags were taking 30-50ms per request. These were straightforward key-value lookups that shouldn't require the overhead of a full SQL database. Meanwhile, our monthly costs were trending toward $200-300 just for read operations that could be handled by a simpler data store.

The core issue was treating all data access patterns the same way. We were running SQL queries for operations that didn't need relational capabilities. Every request, whether it was loading a complex multi-table report or simply checking if a feature flag was enabled, went through the same database engine with the same latency characteristics and cost structure.

As we analyzed our data access patterns, we discovered something striking: roughly 70% of our edge requests were simple key-value lookups that didn't require joins, transactions, or complex querying. Things like user preferences, configuration values, cached API responses, and feature flags. Yet these were consuming nearly half our database read costs and adding unnecessary latency to every request.

We needed a better approach. One that used the right tool for each type of data access, optimizing for both performance and cost. That meant understanding the fundamental differences between Cloudflare's two primary data stores: Worker KV for globally distributed key-value storage, and D1 Database for relational data that requires SQL capabilities.

THE COMPLETE SYSTEM TO SCALE YOUR STARTUP

From strategic planning to execution, FIKR gives you every tool needed to grow from idea to exit.

START FOR FREE →

No credit card required • Setup in 5 minutes

Understanding Worker KV: Eventually Consistent Key-Value Storage

Worker KV is Cloudflare's globally distributed key-value store designed specifically for high-read, low-write use cases at the edge. Think of it as a giant hash map that's automatically replicated across Cloudflare's 300+ edge locations worldwide. When you write data to KV, it's stored in a central location and gradually propagated to all edge locations. Reads happen from the nearest edge location, making them incredibly fast once the data has propagated.

The key characteristic of Worker KV is eventual consistency. When you write or update a value, that change isn't instantly visible everywhere. It typically takes 60 seconds for the update to reach all edge locations globally. This might sound like a limitation, but for the right use cases, it's actually a superpower. It enables sub-millisecond read latency at global scale without the complexity and cost of strongly consistent distributed databases.

Here's what happens when your application reads from Worker KV:

  1. Request hits nearest Cloudflare edge location (<50 miles from user)
  2. Worker queries KV namespace with a simple key lookup
  3. Data is served from memory/disk at that edge location (<1ms)
  4. Response returned to client with single-digit millisecond total latency
  5. No network round trips to central database or other regions

The performance characteristics are remarkable. KV reads at the edge are typically under 1 millisecond because the data is stored locally at that edge location. There's no network latency to a central database, no query planning overhead, and no complex indexing logic. Just a simple key lookup in a data structure optimized for exactly that operation.

But the real magic is in the economics. Worker KV operates on a pricing model that makes it virtually free for most applications. The free tier includes 100,000 reads per day and 1,000 writes per day. Once you exceed that, reads cost $0.50 per million operations, and writes cost $5.00 per million operations. For typical read-heavy use cases, that translates to pennies per month even at significant scale.

Understanding D1 Database: Distributed SQLite at the Edge

D1 Database takes a fundamentally different approach. It's built on SQLite, the world's most deployed database engine, but distributed across Cloudflare's infrastructure to enable edge access. Unlike Worker KV, D1 gives you full SQL capabilities: complex joins, transactions, foreign keys, indexes, and all the relational database features you're used to from PostgreSQL or MySQL.

D1's architecture is designed for strong consistency within a region. Your database lives in a primary region that you choose, and reads/writes to that database go to that specific location. Cloudflare automatically creates read replicas in other regions to reduce query latency for global users, but writes always go to the primary region to maintain consistency.

Here's what happens when your application queries D1:

  1. Request hits nearest edge location
  2. Worker sends SQL query to D1 database
  3. Query routed to nearest read replica (for SELECT) or primary region (for writes)
  4. SQLite engine executes query with full relational capabilities
  5. Results returned to Worker at edge (typically 10-30ms total)

The power of D1 lies in its relational capabilities. You can perform complex queries that join multiple tables, use WHERE clauses with arbitrary conditions, aggregate data with GROUP BY, and maintain referential integrity with foreign keys. It's a real database with real SQL, not a simplified key-value store with SQL-like syntax.

D1's pricing model reflects its more sophisticated capabilities. The database is currently in public beta with generous limits: 25 GB storage, 5 million rows read per day, and 100,000 rows written per day, all included in the Workers Paid plan ($5/month). Beyond those limits, you pay $0.001 per 1,000 rows read and $1.00 per million rows written. For most applications, you're looking at $10-50 per month once you scale beyond the free tier.

Why We Didn't Just Use D1 for Everything

D1 is an impressive piece of technology, and there's an elegance to using a single database for all your data needs. You don't have to think about data access patterns or maintain multiple storage systems. Everything goes in one place, you write SQL queries to access it, and you're done. For teams coming from traditional backend architectures, this approach feels natural and comfortable.

But the more we used D1 in production, the more we realized that treating all data the same way was costing us both performance and money. D1's architecture optimizes for consistency and relational capabilities, which means every query carries overhead that doesn't make sense for simple key-value lookups.

Here are the key factors that made us reconsider using D1 for everything:

  • Read Latency: 10-30ms per query vs <1ms with KV for simple lookups
  • Cost Structure: Row-based pricing adds up quickly for high-read operations
  • Write Bottlenecks: All writes go to primary region, creating potential hotspots
  • Query Overhead: SQL parsing and execution for operations that don't need it
  • Scale Characteristics: Performance degrades with table size for full scans

The turning point came when we profiled a typical page render. We were making 15-20 D1 queries per request, with about 80% of them being simple lookups by primary key. Things like fetching a user's settings record, checking feature flag status, or loading theme configuration. Each query took 15-25ms, adding 250-400ms of database time to every page render.

We realized that for these simple lookups, we didn't need SQL's power. We didn't need joins, we didn't need WHERE clauses, we didn't need transactions. We just needed to fetch a value by its key, and we needed it to happen as fast as possible. That's exactly what Worker KV is designed for.

THE COMPLETE SYSTEM TO SCALE YOUR STARTUP

From strategic planning to execution, FIKR gives you every tool needed to grow from idea to exit.

START FOR FREE →

No credit card required • Setup in 5 minutes

Implementation: Using Both Strategically

Our solution was to use Worker KV and D1 Database together, choosing the right tool for each data access pattern. The implementation required clear thinking about which data belonged in which system, but once we established the patterns, it became straightforward to apply them consistently.

Here's how we structure data storage across both systems:

Worker KV Storage Pattern (Read-Heavy, Eventually Consistent):

// Initialize KV namespace
const THEME_SETTINGS = env.THEME_SETTINGS_KV;
const FEATURE_FLAGS = env.FEATURE_FLAGS_KV;

// Reading theme settings for an organization
const themeKey = `org:${orgId}:theme`;
const theme = await THEME_SETTINGS.get(themeKey, "json");

// Reading feature flag status
const flagKey = `flag:${flagName}`;
const isEnabled = await FEATURE_FLAGS.get(flagKey, "text");

// Writing theme settings (happens infrequently)
await THEME_SETTINGS.put(themeKey, JSON.stringify(themeData), {
  expirationTtl: 86400 // 24 hours
});

This pattern handles all our configuration, preferences, and cached data. Read operations complete in under 1ms because the data is already at the edge. Writes are infrequent (users don't change their theme every request), so the 60-second propagation delay isn't an issue.

D1 Database Pattern (Complex Queries, Strong Consistency):

// Initialize D1 database
const db = env.DB;

// Complex query with joins and filtering
const results = await db.prepare(`
  SELECT
    o.name, o.slug,
    u.email, u.role,
    s.plan_tier, s.active_until
  FROM organizations o
  JOIN users u ON u.organization_id = o.id
  JOIN subscriptions s ON s.organization_id = o.id
  WHERE o.status = ?
    AND s.plan_tier IN (?, ?)
    AND s.active_until > ?
  ORDER BY o.created_at DESC
  LIMIT 50
`).bind("active", "pro", "enterprise", Date.now()).all();

// Transaction for consistent updates
await db.batch([
  db.prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?")
    .bind(amount, senderId),
  db.prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?")
    .bind(amount, receiverId),
  db.prepare("INSERT INTO transactions (from_id, to_id, amount) VALUES (?, ?, ?)")
    .bind(senderId, receiverId, amount)
]);

This pattern handles our relational data where we need SQL's power. User accounts, organizations, subscriptions, analytics events, audit logs - anything that requires complex querying, joins, or transactional consistency lives in D1.

Hybrid Pattern (D1 as Source of Truth, KV as Cache):

// Check KV cache first
const cacheKey = `user:${userId}:profile`;
let profile = await CACHE_KV.get(cacheKey, "json");

if (!profile) {
  // Cache miss - query D1 database
  const result = await db.prepare(`
    SELECT id, email, name, avatar_url, role
    FROM users
    WHERE id = ?
  `).bind(userId).first();

  profile = result;

  // Store in KV for future requests (60s propagation is fine)
  await CACHE_KV.put(cacheKey, JSON.stringify(profile), {
    expirationTtl: 3600 // 1 hour
  });
}

return profile;

This hybrid pattern gives us the best of both worlds. D1 remains the authoritative source of truth with strong consistency, but frequently accessed data is cached in KV for fast edge reads. The cache can be stale for up to 60 seconds during propagation, but for user profiles and similar data, that's perfectly acceptable.

Real Results After 3 Months

The impact of using Worker KV and D1 strategically exceeded our expectations. We didn't just improve performance - we fundamentally changed the economics of our edge infrastructure while maintaining the same feature set.

Before Strategic Data Store Usage (D1 Only):

  • Average page render: 450-600ms database time
  • Simple key lookups: 15-25ms per query
  • Monthly database reads: ~50 million rows
  • Monthly infrastructure cost: ~$280
  • P95 latency: 750ms

After Strategic Data Store Usage (3 Months Later):

  • Average page render: 50-80ms database time (89% faster)
  • Simple key lookups: <1ms per query (95% faster)
  • Monthly database reads: ~12 million rows (76% reduction)
  • Monthly infrastructure cost: ~$70 (75% cheaper)
  • P95 latency: 180ms (76% improvement)
  • KV reads: ~450 million/month at $0.225/month

The cost savings alone justified the refactoring effort, but the performance improvements were even more significant. By serving 70% of our data access from Worker KV instead of D1, we reduced our average database query time from 450ms to 50ms per page render. That's not an incremental improvement - it's a fundamental shift in how our application performs.

Perhaps most importantly, the cost structure now scales favorably. As our traffic grows, the proportion of requests served by KV (at $0.50 per million reads) versus D1 (at $1.00 per thousand rows read) means our costs grow much more slowly than our traffic. We can handle 10x more traffic for only 2-3x more cost.

Challenges We Had to Overcome

The transition to a hybrid storage architecture wasn't without challenges. The most significant was learning to think about data consistency differently. In a traditional database-only architecture, you write data and immediately read it back with perfect consistency. With Worker KV's eventual consistency model, you have to accept that recent writes might not be visible immediately.

We addressed this through careful use case analysis:

  • User preferences and themes: 60-second staleness is acceptable
  • Feature flags: Designed for gradual rollout, eventual consistency is fine
  • Cached API responses: Explicitly time-bounded, staleness expected
  • Configuration data: Changes are rare and can wait for propagation
  • Result: Zero user-facing issues from eventual consistency

Another challenge was determining which data should live in which system. We needed clear decision criteria that any developer on the team could apply. After several iterations, we established a simple decision tree that became part of our engineering documentation.

The third challenge was handling KV write failures gracefully. Unlike D1 where a write either succeeds or fails synchronously, KV writes are fire-and-forget operations that return immediately. We had to build retry logic and monitoring to detect when writes weren't propagating correctly.

We addressed this through defensive coding patterns:

  • Always check KV write responses for errors before assuming success
  • Implement exponential backoff retry logic for failed writes
  • Monitor KV write latency and error rates in production
  • Keep D1 as source of truth for critical data, using KV as cache
  • Result: 99.97% KV write success rate with graceful degradation

When Worker KV Makes Sense vs D1 Database

After running this hybrid architecture in production for several months, we've developed clear criteria for when to use each data store. These aren't theoretical guidelines - they're battle-tested patterns that have proven themselves under real production load.

Choose Worker KV if:

  • Data access is primarily read-heavy (90%+ reads)
  • You're doing simple key-value lookups without complex queries
  • 60-second propagation delay for writes is acceptable
  • Data is relatively small (values under 25MB, though 10KB-1MB is optimal)
  • You need sub-millisecond read latency at global scale
  • Cost optimization is important (handling millions of reads cheaply)
  • Examples: Configuration, feature flags, user preferences, cached data

Choose D1 Database if:

  • You need complex SQL queries with joins, aggregations, or filtering
  • Strong consistency is required (read-after-write must be guaranteed)
  • Data has relational structure with foreign keys and constraints
  • You need transactions to maintain consistency across multiple operations
  • Write frequency is significant (not just occasional updates)
  • You need to query data you didn't originally design the key for
  • Examples: User accounts, organizations, analytics, audit logs, financial data

Use Hybrid Pattern (D1 + KV Cache) if:

  • Data is read frequently but written occasionally
  • D1 must remain source of truth for consistency
  • Short-term cache staleness (1-60 seconds) is acceptable
  • You want D1's query flexibility with KV's read performance
  • Cost optimization is important for high-read scenarios
  • Examples: User profiles, organization settings, frequently accessed reports

Migration Strategies

If you're currently using D1 for everything and want to adopt this hybrid approach, the migration can be done incrementally without downtime. We've successfully migrated several data types from D1-only to KV or hybrid patterns using a phased approach.

Phase 1: Identify KV Candidates (1-2 days)

  • Analyze your D1 query patterns using Cloudflare's analytics
  • Identify queries that are simple primary key lookups
  • Calculate read/write ratio for each data type
  • Estimate latency improvements and cost savings

Phase 2: Implement Read-Through Cache (1 week)

  • Start with lowest-risk data (feature flags, configuration)
  • Add KV namespace and implement read-through pattern
  • Keep D1 as primary write destination
  • Monitor cache hit rates and performance improvements

Phase 3: Move Pure KV Workloads (2 weeks)

  • Migrate data types that don't need D1's capabilities
  • Write directly to KV instead of D1
  • Keep D1 tables for 30 days as backup
  • Validate consistency and performance in production

Phase 4: Optimize and Monitor (Ongoing)

  • Fine-tune cache TTLs based on actual usage patterns
  • Add monitoring for KV hit rates and D1 query reduction
  • Document patterns for team to use consistently
  • Continuously evaluate new data types for migration

Final Thoughts

The decision between Worker KV and D1 Database isn't about choosing one over the other - it's about using each for what it does best. D1 gives you the power of SQL with strong consistency and relational capabilities. Worker KV gives you extreme performance and cost efficiency for simple key-value access patterns. Together, they form a complete data layer for edge computing.

What made this approach successful for us was moving beyond the mindset that all data should be treated the same way. Not every data access needs SQL's power. Not every query needs strong consistency. By matching each use case to the right storage system, we achieved both better performance and lower costs than we could have with either system alone.

The refactoring took our team about three weeks of focused work, including testing and gradual rollout. That investment paid for itself within two months through reduced infrastructure costs alone. But the real value was in the performance improvements - our application became significantly faster for users worldwide, with P95 latency dropping by 76%.

If you're building on Cloudflare Workers and currently using only D1 Database, take an hour to analyze your data access patterns. Chances are good that a significant portion of your queries are simple key-value lookups that would be dramatically faster and cheaper with Worker KV. The migration is straightforward, the benefits are immediate, and your users will notice the difference.

THE COMPLETE SYSTEM TO SCALE YOUR STARTUP

From strategic planning to execution, FIKR gives you every tool needed to grow from idea to exit.

START FOR FREE →

No credit card required • Setup in 5 minutes

LG

Luis Goncalves

// Founder & CEO at FIKR Space

Three-time founder. Built and exited Evolution4All before this. Now building FIKR Space — the operating infrastructure underneath every innovation ecosystem (startups, accelerators, governments, investors). Lisbon-based, works global.