Uçta Önbelleğe Alma: Geleneksel CDN'nin Ötesinde

Geleneksel bir CDN, standart HTTP başlıklarını takip ederek statik varlıkları önbelleğe alır. Cloudflare Çalışanları konsepti çok daha ileri götürüyor: Önbellek API'si, kodunuz TypeScript, neyin ne kadar süreyle önbelleğe alınacağını cerrahi hassasiyetle kontrol eder. nasıl geçersiz kılındığı ve önbellek eskidiğinde hangi geri dönüş mantığının uygulanacağı.

Sonuç bir Programlanabilir CDN: kuralları yapılandırmak yerine Bir kontrol panelinde statik, bir şeyin olup olmadığına anında karar veren iş mantığını yazın. yanıt, hangi TTL ve hangi önbellek anahtarıyla önbelleğe alınabilir. Bu esneklik REST API'leri, özel sayfalar ve yarı dinamik içerik için özellikle değerlidir.

Ne Öğreneceksiniz

  • Cloudflare Workers Cache API'si nasıl çalışır ve Tarayıcı Önbelleği API'sinden farkı
  • Önbelleğe alma stratejileri: Önce Önbellek, Önce Ağ, Eskimişken Yeniden Doğrulama
  • Özel önbellek anahtarları: önbelleği kullanıcıya, dile ve sürüme göre ayırın
  • Cloudflare Zone Purge API ile URL'ler, etiketler ve önekler için geçersiz kılma
  • Başlıkları değiştirin: Accept-Encoding, Accept-Language için farklılaştırılmış önbellek
  • Gelişmiş modeller: önbellek ısınması, ödemesiz süre ve KV ile devre kesici
  • Yaygın hatalar ve üretimde bunlardan nasıl kaçınılacağı

Önbellek API'si: Temel Bilgiler ve Tarayıcıdan Farklılıklar

Workers'ta kullanıma sunulan Cache API, API ile aynı arayüzü izler. Hizmet Çalışanı Önbellek API'si tarayıcı, ancak önemli farklar var. İşçilerde önbellek dağıtılmış tüm Cloudflare PoP'larında: Frankfurt'taki bir İşçi bir yanıtı önbelleğe aldığında, bu yanıt Londra veya Amsterdam'da otomatik olarak mevcut değildir. Her PoP'ta kendi yerel önbelleği.

// Accesso alla cache nel Worker
// In Workers esiste un unico "default" cache namespace
const cache = caches.default;

// Oppure cache named (isolata per nome, utile per namespace logici)
const apiCache = await caches.open('api-v2');

// Le operazioni fondamentali:
// cache.match(request) -> Response | undefined
// cache.put(request, response) -> void
// cache.delete(request) -> boolean

Önbellek API'si: Önemli Kısıtlamalar

  • Yalnızca HTTP istekleri (tarayıcıdaki gibi rastgele URL'ler değil)
  • Yanıtları önbelleğe almak mümkün değildir Vary: *
  • Önbellek PoP başınadır: otomatik olarak veri merkezleri arası geçersiz kılma yoktur. cache.delete()
  • Durumu 206 (Kısmi İçerik) olan yanıtlar önbelleğe alınamaz
  • Uyulan maksimum TTL 31 gündür

Strateji 1: Geri Dönüş Ağı ile Önbellek Öncelikli

Nadiren değişen verilere sahip API'ler için en yaygın strateji: önbellekten sunma mümkün olduğunda kaynağa gidin ve önbelleği doldurun.

// worker.ts - Cache-First Strategy

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // Solo richieste GET sono cacheable
    if (request.method !== 'GET') {
      return fetch(request);
    }

    const cache = caches.default;

    // 1. Controlla la cache
    let response = await cache.match(request);

    if (response) {
      // Cache HIT: aggiungi header diagnostico e restituisci
      const headers = new Headers(response.headers);
      headers.set('X-Cache-Status', 'HIT');
      return new Response(response.body, {
        status: response.status,
        headers,
      });
    }

    // 2. Cache MISS: fetch dall'origin
    response = await fetch(request);

    // 3. Clona la risposta (il body e un ReadableStream, consumabile una sola volta)
    const responseToCache = response.clone();

    // 4. Metti in cache con ctx.waitUntil() per non bloccare la risposta al client
    ctx.waitUntil(
      cache.put(request, responseToCache)
    );

    // 5. Restituisci la risposta originale con header diagnostico
    const headers = new Headers(response.headers);
    headers.set('X-Cache-Status', 'MISS');
    return new Response(response.body, {
      status: response.status,
      headers,
    });
  },
};

interface Env {}

Strateji 2: Eskimişken Yeniden Doğrulama

Strateji yeniden doğrulama sırasında eskimiş en iyisini veren odur veri güncelliği ile algılanan hız arasındaki uzlaşma: müşteri alır Her zaman arka planda çalışıyor olsa bile önbellekten anında yanıt alınması Çalışan bir sonraki istek için önbelleği günceller.

// Stale-While-Revalidate implementato manualmente
// (Cloudflare supporta anche il header standard, ma questa versione offre più controllo)

const CACHE_TTL = 60;        // Secondi prima che la cache sia "fresh"
const STALE_TTL = 300;       // Secondi aggiuntivi in cui la cache e "stale but usable"

interface CacheMetadata {
  cachedAt: number;
  ttl: number;
}

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    if (request.method !== 'GET') return fetch(request);

    const cache = caches.default;

    // Crea una Request con una custom cache key che include i metadata
    const cacheKey = new Request(request.url, {
      headers: request.headers,
    });

    const cached = await cache.match(cacheKey);

    if (cached) {
      const cachedAt = parseInt(cached.headers.get('X-Cached-At') ?? '0');
      const age = (Date.now() - cachedAt) / 1000;

      if (age < CACHE_TTL) {
        // FRESH: servi dalla cache senza revalidazione
        return addCacheHeaders(cached, 'FRESH', age);
      }

      if (age < CACHE_TTL + STALE_TTL) {
        // STALE: servi dalla cache ma revalida in background
        ctx.waitUntil(revalidate(cacheKey, cache));
        return addCacheHeaders(cached, 'STALE', age);
      }
    }

    // MISS o troppo vecchio: fetch sincrono
    return fetchAndCache(cacheKey, cache, ctx);
  },
};

async function revalidate(cacheKey: Request, cache: Cache): Promise<void> {
  const fresh = await fetch(cacheKey.url);
  if (fresh.ok) {
    const toCache = addTimestamp(fresh);
    await cache.put(cacheKey, toCache);
  }
}

async function fetchAndCache(
  cacheKey: Request,
  cache: Cache,
  ctx: ExecutionContext
): Promise<Response> {
  const response = await fetch(cacheKey.url);

  if (response.ok) {
    const toCache = addTimestamp(response.clone());
    ctx.waitUntil(cache.put(cacheKey, toCache));
  }

  const headers = new Headers(response.headers);
  headers.set('X-Cache-Status', 'MISS');
  return new Response(response.body, { status: response.status, headers });
}

function addTimestamp(response: Response): Response {
  const headers = new Headers(response.headers);
  headers.set('X-Cached-At', String(Date.now()));
  // Cache-Control: max-age elevato per far "sopravvivere" la risposta in cache
  headers.set('Cache-Control', 'public, max-age=86400');
  return new Response(response.body, { status: response.status, headers });
}

function addCacheHeaders(response: Response, status: string, age: number): Response {
  const headers = new Headers(response.headers);
  headers.set('X-Cache-Status', status);
  headers.set('Age', String(Math.floor(age)));
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

Özel Önbellek Anahtarları: Önbelleği Bağlama Göre Ayırın

Varsayılan olarak önbellek anahtarı isteğin tam URL'sidir. Ama çoğu zaman ihtiyacın var dile, API sürümüne, kullanıcı veya cihaz katmanına göre ayrılmış önbelleklerin sayısı. Çözüm şu: bir tane inşa et özel önbellek anahtarı bir nesne olarak Request kısa bir URL ile.

// Cache differenziata per lingua e versione API

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const cache = caches.default;
    const url = new URL(request.url);

    // Estrai parametri rilevanti per la cache key
    const lang = request.headers.get('Accept-Language')?.split(',')[0]?.split('-')[0] ?? 'en';
    const apiVersion = url.searchParams.get('v') ?? 'v1';
    const tier = request.headers.get('X-User-Tier') ?? 'free';

    // Costruisci una URL sintetica come cache key
    // Non deve essere una URL reale, solo identificativa
    const cacheKeyUrl = new URL(request.url);
    cacheKeyUrl.searchParams.set('_ck_lang', lang);
    cacheKeyUrl.searchParams.set('_ck_v', apiVersion);
    // Non includiamo 'tier' nella key se vuoi condividere la cache tra tier

    const cacheKey = new Request(cacheKeyUrl.toString(), {
      method: 'GET',
      // Importante: non copiare headers di autenticazione nella cache key
      // altrimenti ogni utente avrebbe la propria cache entry
    });

    // Cerca nella cache con la custom key
    let response = await cache.match(cacheKey);

    if (response) {
      return response;
    }

    // Fetch dall'origin passando la richiesta originale (con auth headers)
    const originResponse = await fetch(request);

    if (originResponse.ok && isCacheable(originResponse)) {
      const toCache = setCacheHeaders(originResponse.clone(), 300);
      ctx.waitUntil(cache.put(cacheKey, toCache));
    }

    return originResponse;
  },
};

function isCacheable(response: Response): boolean {
  // Non mettere in cache risposte con dati personali o Set-Cookie
  if (response.headers.has('Set-Cookie')) return false;
  if (response.headers.get('Cache-Control')?.includes('private')) return false;
  if (response.headers.get('Cache-Control')?.includes('no-store')) return false;
  return true;
}

function setCacheHeaders(response: Response, maxAge: number): Response {
  const headers = new Headers(response.headers);
  headers.set('Cache-Control', `public, max-age=${maxAge}, s-maxage=${maxAge}`);
  // Rimuovi header che potrebbero impedire il caching
  headers.delete('Set-Cookie');
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

Önbellek Başlıkları: s-maxage, yeniden doğrulama sırasında eskime, hata durumunda eskime

Cloudflare saygı duyuyor Standart Önbellek Kontrolü başlıkları ve onu uzatıyor anlamı. Bu yönergeleri anlamak önemlidir:

Direktif Anlam Örnek
max-age=N Tarayıcı ve CDN için TTL (N saniye) max-age=300
s-maxage=N Yalnızca CDN/proxy için TTL (Cloudflare için maksimum yaşı geçersiz kılar) s-maxage=3600, max-age=60
stale-while-revalidate=N Yeniden doğrulanırken bayat yayınlanacak ek saniyeler s-maxage=60, stale-while-revalidate=300
stale-if-error=N Kaynak hata döndürürse, bayat yayınlanacak saniyeler stale-if-error=86400
no-store Hiçbir koşulda önbelleğe almayın Hassas veriler için
private Yalnızca önbellek tarayıcısı, CDN değil Kimliği doğrulanmış yanıtlar için
// Esempio: API con s-maxage e stale-while-revalidate via header

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // Routing con TTL differenziati per tipo di risorsa
    if (url.pathname.startsWith('/api/products')) {
      return fetchWithCacheHeaders(request, {
        sMaxAge: 300,           // 5 minuti fresh in CDN
        staleWhileRevalidate: 3600, // 1 ora stale accettabile
        staleIfError: 86400,    // 1 giorno stale in caso di errore origin
      });
    }

    if (url.pathname.startsWith('/api/user')) {
      // Dati utente: non cacheare in CDN
      return fetchWithCacheHeaders(request, {
        sMaxAge: 0,
        private: true,
      });
    }

    if (url.pathname.startsWith('/static')) {
      // Asset statici: cache aggressiva
      return fetchWithCacheHeaders(request, {
        sMaxAge: 31536000, // 1 anno
        immutable: true,
      });
    }

    return fetch(request);
  },
};

interface CacheOptions {
  sMaxAge?: number;
  staleWhileRevalidate?: number;
  staleIfError?: number;
  private?: boolean;
  immutable?: boolean;
}

async function fetchWithCacheHeaders(
  request: Request,
  options: CacheOptions
): Promise<Response> {
  const response = await fetch(request);
  const headers = new Headers(response.headers);

  let cacheControl = '';

  if (options.private) {
    cacheControl = 'private, no-store';
  } else {
    const parts: string[] = ['public'];
    if (options.sMaxAge !== undefined) parts.push(`s-maxage=${options.sMaxAge}`);
    if (options.staleWhileRevalidate) parts.push(`stale-while-revalidate=${options.staleWhileRevalidate}`);
    if (options.staleIfError) parts.push(`stale-if-error=${options.staleIfError}`);
    if (options.immutable) parts.push('immutable');
    cacheControl = parts.join(', ');
  }

  headers.set('Cache-Control', cacheControl);
  return new Response(response.body, { status: response.status, headers });
}

interface Env {}

Geçersiz Kılma: URL, Etiket ve Önek için Temizleme

cache.delete(request) önbelleği yalnızca Worker'ın çalıştığı yerel PoP'ta silin. Önbelleği geçersiz kılmak için küresel olarak tüm PoP'lardaAPI'yi kullanmanız gerekir Cloudflare Bölgesini Temizleme REST. Bu içerik yönetimi için doğru mekanizmadır ve konuşlandırın.

// Invalidation globale tramite Cloudflare API
// Da usare tipicamente da un webhook CMS o da un Worker admin

interface PurgeOptions {
  files?: string[];      // URL specifici
  tags?: string[];       // Cache-Tag headers
  prefixes?: string[];   // URL prefix
  hosts?: string[];      // Tutti gli URL di un host
}

async function purgeCloudflareCache(
  zoneId: string,
  apiToken: string,
  options: PurgeOptions
): Promise<void> {
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/zones/${zoneId}/purge_cache`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(options),
    }
  );

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Purge failed: ${JSON.stringify(error)}`);
  }
}

// Worker che funge da webhook per invalidazione CMS
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== 'POST') {
      return new Response('Method Not Allowed', { status: 405 });
    }

    // Verifica il secret del webhook
    const secret = request.headers.get('X-Webhook-Secret');
    if (secret !== env.WEBHOOK_SECRET) {
      return new Response('Unauthorized', { status: 401 });
    }

    const body = await request.json() as WebhookPayload;

    // Invalida le URL specifiche aggiornate dal CMS
    if (body.type === 'post.updated') {
      await purgeCloudflareCache(env.ZONE_ID, env.CF_API_TOKEN, {
        files: [
          `https://example.com/blog/${body.slug}`,
          `https://example.com/api/posts/${body.id}`,
          `https://example.com/sitemap.xml`,
        ],
      });
    }

    // Invalida per tag (richiede Cache-Tag header sulle risposte origin)
    if (body.type === 'category.updated') {
      await purgeCloudflareCache(env.ZONE_ID, env.CF_API_TOKEN, {
        tags: [`category-${body.categorySlug}`],
      });
    }

    return new Response(JSON.stringify({ purged: true }), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};

interface WebhookPayload {
  type: string;
  id?: string;
  slug?: string;
  categorySlug?: string;
}

interface Env {
  ZONE_ID: string;
  CF_API_TOKEN: string;
  WEBHOOK_SECRET: string;
}

Önbellek Etiketleri: Semantik Geçersiz Kılma

I Önbellek Etiketleri geçersiz kılmanın en güçlü mekanizmasıdırlar seçici. Bir başlık ekleyerek çalışırlar Cache-Tag cevaplara: her yanıtın birden fazla etiketi olabilir ve tüm URL'leri geçersiz kılabilirsiniz tek bir API çağrısına sahip bir etiketle ilişkilendirilir.

// Origin server o Worker che aggiunge Cache-Tag alle risposte

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);
    const response = await fetch(request);
    const headers = new Headers(response.headers);

    // Aggiungi tag semantici basati sul contenuto
    const tags: string[] = [];

    // Tag per tipo di risorsa
    if (url.pathname.startsWith('/api/products')) {
      const productId = url.pathname.split('/')[3];
      tags.push('products');                    // Invalida tutti i prodotti
      if (productId) tags.push(`product-${productId}`); // Invalida questo prodotto specifico
    }

    if (url.pathname.startsWith('/api/categories')) {
      const catId = url.pathname.split('/')[3];
      tags.push('categories');
      if (catId) tags.push(`category-${catId}`);
    }

    // Tag per versione dell'API
    const apiVersion = url.pathname.split('/')[2];
    if (apiVersion?.startsWith('v')) {
      tags.push(`api-${apiVersion}`);
    }

    if (tags.length > 0) {
      // Cache-Tag: lista separata da virgole, max 16KB
      headers.set('Cache-Tag', tags.join(','));
    }

    return new Response(response.body, { status: response.status, headers });
  },
};

// Esempio di invalidazione per tag dopo un aggiornamento:
// POST /api/admin/purge
// { "tags": ["product-123", "categories"] }
// Invalida tutte le URL che hanno Cache-Tag: product-123 o categories

interface Env {}

Gelişmiş Model: L2 olarak KV ile Önbellek

Önbellek API'sinin önemli bir sınırlaması vardır: program aracılığıyla erişilemez isteğe bağlı okuma/yazma için, yalnızca HTTP istekleri. Daha fazla desen için karmaşık (koordineli geçersiz kılma, devre kesici veya nesne önbelleği gibi) HTTP olmayan), kullanım L2 önbelleği olarak İşçi KV'si.

// Cache a due livelli: Cache API (L1, HTTP) + KV (L2, programmabile)

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const url = new URL(request.url);
    const cacheKey = buildCacheKey(url);

    // L1: Cache API (piu veloce, locale al PoP)
    const l1Cache = caches.default;
    const l1Hit = await l1Cache.match(request);

    if (l1Hit) {
      return addHeader(l1Hit, 'X-Cache', 'L1-HIT');
    }

    // L2: KV Store (globale, programmabile, con TTL gestito da KV)
    const kvCached = await env.API_CACHE.getWithMetadata<CacheMetadata>(cacheKey);

    if (kvCached.value !== null) {
      const { value, metadata } = kvCached;

      // Ricostruisci una Response dalla stringa KV
      const cachedResponse = new Response(value, {
        headers: {
          'Content-Type': metadata?.contentType ?? 'application/json',
          'Cache-Control': 'public, max-age=60',
          'X-Cache': 'L2-HIT',
          'X-Cached-At': String(metadata?.cachedAt ?? 0),
        },
      });

      // Popola anche L1 per richieste successive nello stesso PoP
      ctx.waitUntil(l1Cache.put(request, cachedResponse.clone()));

      return cachedResponse;
    }

    // MISS su entrambi i livelli: fetch dall'origin
    const originResponse = await fetch(request);

    if (originResponse.ok) {
      const body = await originResponse.text();
      const contentType = originResponse.headers.get('Content-Type') ?? 'application/json';

      const metadata: CacheMetadata = {
        cachedAt: Date.now(),
        contentType,
        url: url.toString(),
      };

      // Salva in KV con TTL di 5 minuti
      ctx.waitUntil(
        env.API_CACHE.put(cacheKey, body, {
          expirationTtl: 300,
          metadata,
        })
      );

      // Salva anche in L1
      const toL1 = new Response(body, {
        headers: {
          'Content-Type': contentType,
          'Cache-Control': 'public, max-age=60',
          'X-Cache': 'MISS',
        },
      });
      ctx.waitUntil(l1Cache.put(request, toL1));

      return new Response(body, {
        headers: {
          'Content-Type': contentType,
          'X-Cache': 'MISS',
        },
      });
    }

    return addHeader(originResponse, 'X-Cache', 'BYPASS-ERROR');
  },
};

function buildCacheKey(url: URL): string {
  // Normalizza l'URL per la cache key (rimuovi query params non semantici)
  const params = new URLSearchParams(url.searchParams);
  params.delete('utm_source');
  params.delete('utm_medium');
  params.delete('_t'); // timestamp di cache-busting
  params.sort(); // ordine deterministico
  return `${url.pathname}?${params.toString()}`;
}

function addHeader(response: Response, key: string, value: string): Response {
  const headers = new Headers(response.headers);
  headers.set(key, value);
  return new Response(response.body, { status: response.status, headers });
}

interface CacheMetadata {
  cachedAt: number;
  contentType: string;
  url: string;
}

interface Env {
  API_CACHE: KVNamespace;
}

Önbellek Isıtma: Önbelleği Önceden Doldurun

Il önbellek ısınması önce önbelleği önceden doldurma uygulamasıdır gerçek isteklerin gelmesini sağlayarak dağıtımdan sonra soğuk önbellek sorununu ortadan kaldırır büyük geçersizlikler. Cron Trigger aracılığıyla programlanan bir İşçi ile uygulanır.

// wrangler.toml - Cron Trigger per cache warming
// [triggers]
// crons = ["*/15 * * * *"]  # Ogni 15 minuti

// worker.ts - Cache Warming Worker

const URLS_TO_WARM = [
  'https://api.example.com/products?featured=true',
  'https://api.example.com/categories',
  'https://api.example.com/homepage',
  'https://api.example.com/navigation',
];

export default {
  // Scheduled handler per Cron Trigger
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext): Promise<void> {
    console.log(`Cache warming started at ${new Date(event.scheduledTime).toISOString()}`);

    const results = await Promise.allSettled(
      URLS_TO_WARM.map(url => warmUrl(url))
    );

    const succeeded = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected').length;

    console.log(`Cache warming complete: ${succeeded} success, ${failed} failed`);
  },

  async fetch(request: Request): Promise<Response> {
    return new Response('Cache Warmer Worker', { status: 200 });
  },
};

async function warmUrl(url: string): Promise<void> {
  // Forza bypass della cache aggiungendo header speciale
  // (da gestire lato Worker principale con whitelist IP o secret)
  const response = await fetch(url, {
    headers: {
      'Cache-Control': 'no-cache', // Forza revalidazione
      'X-Cache-Warm': 'true',
    },
  });

  if (!response.ok) {
    throw new Error(`Failed to warm ${url}: ${response.status}`);
  }
}

interface Env {}
interface ScheduledEvent {
  scheduledTime: number;
  cron: string;
}

Önbellek Hata Ayıklama ve İzleme

Cloudflare, önbellek durumunu anlamak için yanıtlarda teşhis başlıklarını gösterir. En önemlisi CF-Cache-Status:

CF-Önbellek Durumu Anlam
HIT Cloudflare önbelleğinden sunulur
MISS Önbelleğe alınmadı, kaynakta istendi
EXPIRED Önbelleğe alındı ​​ancak TTL'nin süresi doldu, kaynak isteği
STALE Bayat olarak servis edildi (yeniden doğrulama sırasında bayat)
BYPASS Önbellek atlandı (Çerez, Kimlik Doğrulama başlığı vb.)
DYNAMIC Önbelleğe alınamaz (dinamik yanıt)
REVALIDATED Önbellek, kaynakla doğrulandı (304 Değiştirilmedi)
// Worker che logga le metriche di cache su KV Analytics
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const response = await fetch(request);

    const cacheStatus = response.headers.get('CF-Cache-Status') ?? 'UNKNOWN';

    // Logga la metrica in background
    ctx.waitUntil(
      logCacheMetric(env, {
        url: request.url,
        status: cacheStatus,
        timestamp: Date.now(),
        country: request.cf?.country ?? 'unknown',
        datacenter: request.cf?.colo ?? 'unknown',
      })
    );

    return response;
  },
};

async function logCacheMetric(env: Env, metric: CacheMetric): Promise<void> {
  // Aggrega per finestre di 1 minuto
  const minuteKey = `metrics:${Math.floor(metric.timestamp / 60000)}:${metric.status}`;
  const current = parseInt(await env.METRICS_KV.get(minuteKey) ?? '0');
  await env.METRICS_KV.put(minuteKey, String(current + 1), { expirationTtl: 3600 });
}

interface CacheMetric {
  url: string;
  status: string;
  timestamp: number;
  country: string;
  datacenter: string;
}

interface Env {
  METRICS_KV: KVNamespace;
}

Sonuçlar ve Sonraki Adımlar

Cloudflare Workers Cache API, CDN'yi pasif bir araçtan bir bileşene dönüştürür Mimarlıkta aktif. Bu makalede açıklanan stratejilerle şunları yapabilirsiniz: yükü azaltan ayrıntılı, anlamsal, geçersiz bir önbellekleme katmanı Tipik genel API iş yükleri için kaynakta %70-90.

Hatırlanması gereken önemli noktalar: kullanın ctx.waitUntil() yanıtı engellememek için istemciye, farklı bağlamları izole etmek için özel önbellek anahtarları oluşturun, Önbellek Etiketlerini kullanın anlamsal geçersizleştirme için ve daha karmaşık modeller için Cache API'yi KV ile birleştirin.

Serideki Sonraki Yazılar

  • Madde 9: Yerelde İşçilerin Test Edilmesi — Miniflare, Vitest e Wrangler Dev: Çalışanlar için dağıtım olmadan birim testleri ve entegrasyon testleri nasıl yazılır?
  • Madde 10: Uçta Tam Yığın Mimariler — Örnek Olay İncelemesi Sıfırdan Üretime: Workers + D1 + R2 ve GitHub Eylemlerinde CI/CD içeren eksiksiz bir REST API.