18. modulCache és teljesítmény
Cloudflare for Devs · 18. modul

Cache és teljesítmény

Az edge azért éri meg, mert a válasz a felhasználó mellett születik — de ez csak akkor igaz, ha jól cache-elsz. Ez a modul a három cache-réteget, a multitenant cache-kulcs kérdését, a purge-stratégiát és a latencia-optimalizálás eszközeit veszi végig.

18.1A három cache-réteg — ne keverd össze őket

Ez a leggyakoribb zavarforrás, ezért kezdjük ezzel. A Workers körül három különböző cache-mechanizmus van, más-más viselkedéssel:

Workers CachingCache API (caches.default)fetch() + cf objektum
Mi eza Worker elé tett read-through cachealacsonyszintű, programozott cache-tára kimenő subrequestek cache-elése
Lefut-e a Worker cache-találatnál?nem — ez a lényegigen, mindig
Kérés-összevonás (thundering herd ellen)vannincsvan
Tiered cachevannincs (adatközpont-lokális)van
Purgectx.cache.purge()cache.delete() — csak lokálisan; globálishoz zóna-purge/tagzóna-purge, tag
Mikor használdúj projektben ez az alapértelmezettha finomhangolt, programozott kontroll kellha origin/külső API válaszát cache-eled
A gyakorlati tanács: új Workernél kezdd a Workers Cachinggel — ez az egyetlen, ami cache-találatnál meg sem hívja a Workeredet (tehát CPU-t sem fogyaszt, 9. modul), összevonja az egyszerre érkező azonos kéréseket, és részt vesz a tiered cache-ben. A Cache API attól még hasznos marad, ha nem-origin válaszokat akarsz programozottan tárolni, de ne azzal kezdd.

Workers Caching bekapcsolása

{
  "cache": { "enabled": true }
}

Innentől a Worker válaszainak cache-elhetőségét a szokásos Cache-Control header vezérli — vagyis ugyanaz a mechanizmus, amit HTTP-ből ismersz, csak most a saját válaszaidra alkalmazod:

return new Response(body, {
  headers: {
    // 5 percig friss; utána 1 óráig kiadható a régi, míg a háttérben frissül
    "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
  },
});

18.2A multitenant cache-kulcs — itt lehet nagyot hibázni

Multitenant SaaS-ban ugyanaz az URL (/api/dashboard) tenantonként és felhasználónként más tartalmat ad. Ha a cache-kulcs csak az URL, akkor A tenant válasza kimehet B tenantnak — ez a legdurvább hibaosztály, ami cache-eléssel elkövethető.

❌ Rossz: a kulcs csak az URL acme user globex user /api/dashboard egy cache-bejegyzés → globex az acme adatát kapja ✅ Jó: a kulcs tartalmazza a bérlőt acme user /api/dashboard + acme /api/dashboard + globex → külön bejegyzés, nincs keveredés
18/1. ábra — Cache-elés multitenant appban: a bérlő-azonosító a kulcs része kell legyen, különben adatszivárgás.

Négy megközelítés, növekvő biztonsággal:

MódszerHogyanÉrtékelés
Ne cache-eldCache-Control: private, no-store minden bejelentkezett válaszraa legbiztonságosabb alap — ezzel kezdj
Tenant az URL-benacme.app.com/api/dashboard vagy /t/acme/api/dashboardegyszerű és robusztus: a kulcs természetesen elválik
Egyedi cache-kulcsa kulcsba beleveszed a bérlőtműködik, de fegyelmet igényel — egy kifelejtett hely elég a bajhoz
ctx.props alapú izolációservice-binding hívásoknál a hívó azonossága automatikusan a kulcs részea legerősebb: nem lehet „elfelejteni", nem kerülhető meg
Vasszabály: alapértelmezésben minden bejelentkezett válasz ne legyen cache-elhető (private, no-store), és csak tudatosan, esetenként engedd meg — soha ne fordítva. Ha bizonytalan vagy egy végpontnál, az azt jelenti, hogy nem cache-elhető. A cache-elésből nyert néhány ezredmásodperc soha nem éri meg egy tenant-adatszivárgás kockázatát.

18.3Mit cache-elj egy SaaS-ban? — a rétegek

TartalomRétegStratégia
statikus assetek (/_nuxt/*, képek)Static Assets (3. modul)automatikus, fingerprintelt fájlnév → max-age=31536000, immutable
publikus marketing-oldalak, blogWorkers Caching / routeRulesswr vagy isr — percek-órák
publikus API (árlista, feature-lista)Workers Cachingpublic, max-age=60, stale-while-revalidate
bejelentkezett dashboard HTMLne cache-eld; helyette gyors SSR + streaming (18.5)
drága, ritkán változó számítás (riport, aggregátum)KV vagy D1, nem HTTP-cachealkalmazás-szintű cache tenant-kulccsal (6. modul)
külső API válasza (árfolyam, geo-adat)fetch() + cf.cacheTtl vagy KVa partner rate limitjét is védi
session/jogosultságcookie vagy KV (17. modul)soha ne HTTP-cache-be

Az alkalmazás-szintű cache (KV-ben) az, amit a leggyakrabban fogsz használni, mert a legtöbb SaaS-tartalom nem HTTP-cache-elhető, viszont a mögötte lévő számítás igen:

server/utils/cached.ts
export async function cached<T>(
  event, key: string, ttl: number, produce: () => Promise<T>
): Promise<T> {
  const { env } = event.context.cloudflare;
  const tenantId = event.context.tenantId;          // 17. modul middleware-ből
  const ns = `v1:${tenantId}:${key}`;                // verzió + bérlő a kulcsban!

  const hit = await env.CACHE.get(ns, { type: "json" });
  if (hit) return hit as T;

  const fresh = await produce();
  // nem várunk rá: a válasz mehet, az írás a háttérben fut (2. modul)
  event.context.cloudflare.context.waitUntil(
    env.CACHE.put(ns, JSON.stringify(fresh), { expirationTtl: ttl })
  );
  return fresh;
}

Két részlet, ami sokat számít: a kulcsban ott a verzió-prefix (v1:) — ha a válasz szerkezete változik, a prefix emelésével az egész cache-t érvényteleníted deploy nélkül; és a bérlő — lásd 18.2.

18.4Invalidálás: a nehezebbik fele

„A számítástechnika két nehéz problémája: a cache-invalidálás és az elnevezés." A gyakorlati eszköztárad:

EszközHatókörMikor
TTL lejáratautomatikusaz esetek 90%-a — ha elviselhető a késés, ez a legegyszerűbb
stale-while-revalidateautomatikusha a frissítés lassú, de a régi adat elfogadható közben
Verzió a kulcsbanminden érintett bejegyzésséma- vagy formátumváltásnál (deploy nélküli globális invalidálás)
Cache-Tag + purge by tagcímkézett bejegyzések„az acme tenant minden cache-elt oldala" — a válaszra Cache-Tag-et teszel, majd tag szerint purge-elsz
ctx.cache.purge()Workers Caching bejegyzéscélzott invalidálás írás után
Purge everything / prefix / hostzóna-szintvészhelyzet vagy nagy release
Két korlát, amit tudni kell: ① a Cache API-val írt bejegyzést a cache.delete() csak abban az adatközpontban törli, ahol a Worker éppen fut — globális törléshez zóna-szintű purge (tag, prefix, host, everything) kell. ② Egyedi cache-kulccsal cache-elt URL-t nem tudsz URL szerint purge-elni — ilyenkor tag vagy prefix szerinti purge az út. Ezért érdemes a purge-stratégiát a cache-kulcs megtervezésekor eldönteni, nem utólag.

Az írás-utáni invalidálás mintája

// riport frissítése után: a tenant összes riport-cache-ét eldobjuk
export default defineEventHandler(async (event) => {
  await updateReport(event, data);

  const { env } = event.context.cloudflare;
  const tenantId = event.context.tenantId;

  // KV: a verziószámot emeljük tenantonként — az összes régi kulcs elárvul
  await env.CACHE.put(`ver:${tenantId}`, String(Date.now()));
  return { ok: true };
});

A „verzió-token" minta gyakran jobb, mint a törölgetés: nem kell tudnod, milyen kulcsok léteznek — egy szám emelésével az összes régi bejegyzés érvénytelenné válik, és a TTL magától kitakarítja őket.

18.5Teljesítmény a cache-en túl

Smart Placement — ha sok a DB-kör

Az 5b. modulban már felmerült: ha egy kérés sok lekérdezést lő ugyanarra az adatbázisra, a Worker futtatása a felhasználó mellett rosszabb, mint az adatbázis mellett — mert minden query átmegy az óceánon. A Smart Placement ilyenkor magát a Workert helyezi át:

{ "placement": { "mode": "smart" } }

Mikor segít: több (3-4+) egymás utáni DB-hívás kérésenként. Mikor nem: ha kérésenként egy lekérdezés van — akkor a teljes körút ugyanannyi, akárhol fut a Worker (a doksi ezt külön kiemeli). Mérd meg, ne tippelj.

A latencia-lánc: mit optimalizálj sorrendben?

  1. Szüntesd meg a sorozatos (soros) hívásokat. Ez a leggyakoribb valódi ok. Ha három független lekérdezésed van, ne egymás után várd meg őket:
    // ❌ 3 × 80 ms = 240 ms
    const a = await q1(); const b = await q2(); const c = await q3();
    // ✅ ~80 ms
    const [a, b, c] = await Promise.all([q1(), q2(), q3()]);
    (Vigyázz a 6 egyidejű kimenő kapcsolat korlátjára — 2. modul.)
  2. Kevesebb kör a DB-hez: D1-nél batch() (5. modul), Postgresnél kevesebb, jobban megírt query — a Hyperdrive cache is itt segít (4. modul).
  3. Streaming SSR:
  4. Bundle-méret és startup: a Worker indulási ideje (1 s limit, 2. modul) a top-level importoktól függ. Nehéz könyvtárat importálj lustán, a startup-profilozóval mérj (11. modul).
  5. CPU-idő: a 9. modul versionönkénti percentilisei mutatják meg, hol drága a kód; a DevTools CPU-profil pedig, hogy min belül.
Mérj, mielőtt optimalizálsz — és a helyes helyen mérj. A Workers-metrikák CPU-időt és wall time-ot is adnak versionönként (9. modul); a valódi felhasználói élményt viszont a böngészőben kell mérni (Web Analytics vagy saját RUM). Gyakori félreértés, hogy „a szerver 12 ms-os p99-e" jó élményt jelent — miközben a felhasználó 2 másodpercet vár a kliensoldali hidratálásra. Az edge a hálózati részt oldja meg; a frontend-teljesítmény továbbra is a te dolgod.

18.6Ellenőrizd magad

  1. Mi a döntő különbség a Workers Caching és a Cache API között?
    Válasz

    Workers Caching read-through: cache-találatnál a Workered meg sem hívódik (nincs CPU-költség), összevonja az egyidejű azonos kéréseket, és részt vesz a tiered cache-ben. A Cache API alacsonyszintű tár: a Worker mindig lefut, nincs kérés-összevonás, és adatközpont-lokális.

  2. Ugyanaz az URL tenantonként más tartalmat ad. Mi az alapértelmezett szabály, és milyen megoldások vannak?
    Válasz

    Alapértelmezésben ne legyen cache-elhető (private, no-store). Ha mégis kell: a bérlő legyen a kulcs része — legrobusztusabban úgy, hogy az URL-ben van (aldomain vagy path), vagy service-binding esetén a ctx.props automatikus izolációjával.

  3. Miért nem elég a cache.delete() globális invalidáláshoz?
    Válasz

    Mert csak abban az adatközpontban töröl, ahol a Worker éppen fut. Globálishoz zóna-szintű purge kell: tag, prefix, host vagy purge everything — és egyedi cache-kulcsú bejegyzést URL szerint egyáltalán nem lehet purge-elni.

  4. Mikor segít a Smart Placement, és mikor nem?
    Válasz

    Akkor segít, ha egy kérés több egymás utáni hívást intéz ugyanahhoz a távoli adatbázishoz — ilyenkor a Workert érdemes a DB mellé tenni. Nem segít, ha kérésenként egyetlen lekérdezés van: a teljes körút ugyanannyi, bárhol is fut a Worker.

  5. Mi a „verzió-token" invalidálási minta lényege?
    Válasz

    A cache-kulcs tartalmaz egy verziószámot (globálisan vagy tenantonként). Invalidáláskor nem kulcsokat törölsz — amiket nem is feltétlen ismersz —, hanem emeled a verziót: az összes régi bejegyzés elárvul, és a TTL kitakarítja őket.

Előző17. modul — Auth, session és tenant-identitás Következő 19. modul — Webhookok és külső integrációk