Skip to content

Workers KV -- Caching Strategies and Best Practices

DodaTech 6 min read

In this tutorial, you'll learn about Workers KV. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Workers KV Caching strategies combine KV reads with the Cloudflare Cache API to reduce latency, minimize KV read costs, and serve data faster than direct KV access for frequently accessed keys.

Why KV Caching Strategies Matter

Every KV read operation counts toward your plan limits and incurs network latency, even at edge speeds. Caching frequently accessed keys in the Cloudflare edge cache or in-memory within a Worker reduces KV reads by orders of magnitude. A cache hit from edge memory takes under 1ms versus 5-15ms for a KV read. For high-traffic applications serving thousands of requests per second, Caching transforms KV from a storage layer into a high-performance content delivery system. Combined with Cloudflare Caching infrastructure and JavaScript runtime optimizations, smart Caching strategies can reduce origin load by 95 percent or more.

Real-world use: A news API Worker caches article data in KV with 60-second TTL and serves it from edge memory via the Cache API, handling 10,000 requests per second with only 200 actual KV reads per minute.

Caching Strategy Architecture

flowchart LR
    R[Request] --> W[Worker]
    W --> C{In edge cache?}
    C -->|Yes| CR[Return cached response]
    C -->|No| KV[Read from KV]
    KV --> S[Store in edge cache]
    S --> CR2[Return response]
    W --> M{In memory cache?}
    M -->|Yes| MR[Return cached]
    M -->|No| KV2[Read from KV]
    KV2 --> MC[Cache in memory]
    MC --> MR2[Return]
    style CR fill:#f90,color:#fff
    style S fill:#f90,color:#fff
    style MC fill:#f90,color:#fff

Cache-Aside Pattern with the Cache API

The most common pattern -- check cache first, fall back to KV, then populate the cache.

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const cacheKey = new Request(`${url.origin}/kv-cache${url.pathname}`, request);
    const cache = caches.default;
    let response = await cache.match(cacheKey);
    if (response) {
      return response;
    }
    const value = await env.MY_KV.get(`page:${url.pathname}`);
    if (value === null) {
      return new Response('Not found', { status: 404 });
    }
    response = new Response(value, {
      headers: {
        'Content-Type': 'text/plain',
        'Cache-Control': 'public, max-age=300'
      }
    });
    await cache.put(cacheKey, response.clone());
    return response;
  }
}

Expected output: First request reads from KV and caches the response for 5 minutes. Subsequent requests within 5 minutes serve from the edge cache without any KV read, reducing latency from ~10ms to under 1ms.

In-Memory Caching with Singleton Pattern

Cache values in Worker global scope for the lifetime of the isolate.

const memoryCache = new Map();
const CACHE_TTL = 60_000;

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const cacheKey = `config:${url.pathname}`;
    const cached = memoryCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
      return new Response(cached.data, {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    const value = await env.MY_KV.get(cacheKey);
    if (value === null) {
      return new Response('Not found', { status: 404 });
    }
    memoryCache.set(cacheKey, { data: value, timestamp: Date.now() });
    return new Response(value, {
      headers: { 'Content-Type': 'application/json' }
    });
  }
}

Expected output: The first request reads from KV and stores the result in a Map. All subsequent requests within 60 seconds served from the in-memory cache with zero KV reads. The cache resets when Cloudflare recycles the isolate (typically after 30 seconds of inactivity).

Write-Through Cache Pattern

Update both KV and the edge cache on writes to keep data consistent.

export default {
  async fetch(request, env) {
    if (request.method === 'PUT') {
      const url = new URL(request.url);
      const body = await request.text();
      await env.MY_KV.put(`data:${url.pathname}`, body);
      const cacheKey = new Request(`${url.origin}/kv-cache${url.pathname}`, request);
      const response = new Response(body, {
        headers: {
          'Content-Type': 'text/plain',
          'Cache-Control': 'public, max-age=300'
        }
      });
      await caches.default.put(cacheKey, response.clone());
      return new Response('Updated and cached', { status: 200 });
    }
    const url = new URL(request.url);
    const cache = caches.default;
    const cacheKey = new Request(`${url.origin}/kv-cache${url.pathname}`, request);
    let cached = await cache.match(cacheKey);
    if (cached) {
      return cached;
    }
    const value = await env.MY_KV.get(`data:${url.pathname}`);
    if (value === null) {
      return new Response('Not found', { status: 404 });
    }
    const response = new Response(value, {
      headers: {
        'Content-Type': 'text/plain',
        'Cache-Control': 'public, max-age=300'
      }
    });
    await cache.put(cacheKey, response.clone());
    return response;
  }
}

Expected output: A PUT request writes to KV and immediately updates the edge cache. A subsequent GET reads from cache with zero KV reads. The write-through pattern ensures consistency between KV and cache layers.

TTL Strategy for Different Data Types

Different data types benefit from different Caching TTLs based on how often they change.

const CACHE_STRATEGIES = {
  'config:': { ttl: 3600, kvCache: true },
  'session:': { ttl: 300, kvCache: false },
  'content:': { ttl: 86400, kvCache: true },
  'analytics:': { ttl: 60, kvCache: false }
};

export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const prefix = Object.keys(CACHE_STRATEGIES).find(p => url.pathname.startsWith(`/${p}`));
    if (!prefix) {
      return new Response('No strategy found', { status: 400 });
    }
    const strategy = CACHE_STRATEGIES[prefix];
    if (strategy.kvCache) {
      const cacheKey = new Request(`${url.origin}/kv-cache${url.pathname}`, request);
      const cached = await caches.default.match(cacheKey);
      if (cached) return cached;
    }
    const value = await env.MY_KV.get(url.pathname.slice(1));
    if (value === null) {
      return new Response('Not found', { status: 404 });
    }
    const response = new Response(value, {
      headers: {
        'Content-Type': 'text/plain',
        'Cache-Control': `public, max-age=${strategy.ttl}`
      }
    });
    if (strategy.kvCache) {
      const cacheKey = new Request(`${url.origin}/kv-cache${url.pathname}`, request);
      await caches.default.put(cacheKey, response.clone());
    }
    return response;
  }
}

Expected output: Configuration data is cached for 1 hour, sessions for 5 minutes, content for 24 hours, and analytics for 1 minute. Each prefix gets a custom Strategy tuned to its update frequency. This pattern minimizes KV reads for stable data while keeping fresh data for rapidly changing values.

Common Errors

Error Cause Fix
Stale cache serving old data Cache TTL too long for frequently updated data Reduce TTL or implement write-through invalidation
Memory cache across requests not working Worker isolate recycled after inactivity Use the Cache API instead of in-memory Maps for cross-request Caching
Cache-but-no-KV inconsistency Cache populated but KV write failed Use write-through or background validation with stale-while-revalidate
Exceeded KV read quota No Caching layer implemented Add at least one Caching layer to reduce KV reads by 80-95 percent
Cache key collision Different data types sharing same cache key prefix Use unique cache key prefixes per data type

Practice Questions

  1. What is the difference between cache-aside and write-through Caching patterns in KV?
  2. Why does in-memory Caching with a Map reset after periods of inactivity?
  3. How would you implement stale-while-revalidate for KV cached data?

FAQ

Can I use the Cloudflare Cache API with Workers KV on the free plan?

Yes, the Cache API is available on all plans including the free tier. Cache storage limits apply per plan level, but the API itself has no additional cost. Combined with KV, it reduces read operations and improves latency.

What happens if the edge cache is full?

Cloudflare automatically evicts least-recently-used entries when the cache reaches capacity. Set appropriate Cache-Control headers to prioritize important content. The Cache API respects these headers for eviction decisions.

Summary

Caching strategies dramatically reduce KV read costs and latency. Cache-aside reads KV only on cache misses, write-through keeps cache and KV synchronized, in-memory Caching eliminates KV reads entirely within isolate lifetimes, and TTL strategies match cache duration to data freshness requirements. These patterns power Doda Browser's edge configuration service, reducing KV reads by 95 percent while maintaining sub-millisecond response times globally. Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro -- security-first tools for the modern web.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro