6. modulTárolás: R2 objektumtár és KV
Cloudflare for Devs · 6. modul

Tárolás: R2 objektumtár és KV

Az S3-tudásod ~95%-a egy az egyben átvihető — a maradék 5% pedig jó hír: nincs egress díj. A modul második fele a KV: a globálisan replikált kulcs-érték tár, aminek nincs igazi AWS-megfelelője.

6.1R2 = S3, mínusz az egress-számla

Az R2 egy S3-kompatibilis objektumtár, és a legfontosabb tudnivaló üzleti, nem technikai: a kimenő adatforgalom ingyenes. Az S3-nál a tárolásnál gyakran nagyobb tétel a GB-onkénti egress (plusz a CloudFront-forgalom) — az R2-nél tárolásért és műveletekért (Class A/B operations) fizetsz, adatkiadásért soha. Egy médiafájlokat, exportokat, user-uploadokat kiszolgáló SaaS-nál ez tipikusan a legnagyobb egyedi költségcsökkenés az egész migrációban.

Három felületen éred el ugyanazt a bucketet:

FelületMikor használod
Workers binding (env.MY_BUCKET)az app-kódodban — natív API, credential nélkül (1. modul mintája)
S3-kompatibilis API (<account>.r2.cloudflarestorage.com)meglévő S3-kód, SDK-k, CLI-toolok, presigned URL-ek — a migráció hídja
REST API / dashboard / wranglerbucket-menedzsment, ad-hoc műveletek

A meglévő S3-kódod átállítása jellemzően ennyi: endpoint + credential csere (a region: "auto"):

meglévő Node-kódod, R2-re irányítva
const S3 = new S3Client({
  region: "auto",
  endpoint: `https://${ACCOUNT_ID}.r2.cloudflarestorage.com`,
  credentials: { accessKeyId, secretAccessKey },   // R2 API token
});
// innentől a PutObjectCommand, GetObjectCommand, multipart… változatlan
Fokozatos migráció: nem kell egy hétvégén átmásolni terabájtokat. A Sippy (inkrementális migráció) az R2-t az S3 bucketed elé teszi: amit kérnek és még nincs R2-ben, azt az S3-ból húzza be és eltárolja — a forgalom fokozatosan „átszivárog", az S3-egresst csak egyszer fizeted meg objektumonként. A bucket-helyszínre pedig ugyanaz igaz, mint a D1-nél (5b.2): létrehozáskor location hint (WNAM, EEUR…) és jurisdiction (eu) adható.

6.2R2 a Workerből: binding API és fájlkiszolgálás

wrangler.jsonc
{ "r2_buckets": [{ "binding": "UPLOADS", "bucket_name": "app-uploads" }] }
server/api/files/[key].get.ts — fájl kiszolgálása streamelve
export default defineEventHandler(async (event) => {
  const { env } = event.context.cloudflare;
  const obj = await env.UPLOADS.get(event.context.params.key);
  if (!obj) throw createError({ statusCode: 404 });

  return new Response(obj.body, {        // stream: sosincs egyben a memóriában (2. modul)
    headers: {
      "Content-Type": obj.httpMetadata?.contentType ?? "application/octet-stream",
      "Cache-Control": "public, max-age=31536000",  // a CDN innen cache-eli
      "ETag": obj.httpEtag,
    },
  });
});

Az API a megszokott műveleteket adja (get, put, delete, list, head, multipart), objektumonként httpMetadata (Content-Type, Cache-Control…) és szabad customMetadata mezőkkel. Publikus tartalomhoz nem is kell Worker: a bucket custom domainre köthető, és onnantól a Cloudflare CDN közvetlenül, cache-elve szolgálja ki — ez a „S3 + CloudFront static hosting" egy mozdulatos megfelelője.

6.3Presigned URL-ek: a nagy fájl ne folyjon át a Workeren

A 100 MB-os kérés-limit (2. modul) és a józan ész is azt diktálja, amit S3-nál is csináltál: nagy uploadnál a kliens közvetlenül a tárba töltsön. A minta ugyanaz — a Worker (a könnyű aws4fetch csomaggal vagy S3 SDK-val) aláír egy URL-t, a kliens pedig azt PUT-olja:

import { AwsClient } from "aws4fetch";

const r2 = new AwsClient({ accessKeyId: env.R2_KEY_ID, secretAccessKey: env.R2_SECRET });

const url = new URL(`https://${ACCOUNT_ID}.r2.cloudflarestorage.com/app-uploads/${key}`);
url.searchParams.set("X-Amz-Expires", "3600");

const signed = await r2.sign(new Request(url, { method: "PUT" }),
                            { aws: { signQuery: true } });
return Response.json({ uploadUrl: signed.url });   // a kliens ide PUT-ol

Két hasznos rokon funkció: az object lifecycles (félbehagyott multipart uploadok auto-törlése, lejárati szabályok — az S3 lifecycle rules megfelelője), és az event notifications: a bucket-események (új objektum, törlés) Queues-ba mennek — ez az „S3 event → Lambda" minta megfelelője, és átvezet a következő modulba.

6.4KV: a globális kulcs-érték tár, aminek nincs AWS-párja

A KV-t nehéz egy AWS-termékre képezni: leginkább „ElastiCache + DynamoDB read-heavy fele + SSM Parameter Store" egyben, de globálisan replikálva és a Workerbe drótozva. A modellje: írsz egy kulcsot bárhonnan, és az néhány másodpercen belül a világ minden PoP-jában olvasható — a forró kulcsok a helyi cache-ből ~ezredmásodperc alatt jönnek. Az ára: eventually consistent — egy írás után a világ másik felén rövid ideig (akár ~60 s) még a régi értéket olvashatják, és egy kulcsot érdemes legfeljebb ~1/s gyakorisággal írni.

tipikus használat
// feature flag / config — pontosan erre való
const flags = await env.CONFIG.get("feature-flags", { type: "json" });

// cache lejárattal — pl. drága külső API válasza
await env.CACHE.put(`fx:${date}`, JSON.stringify(rates), { expirationTtl: 3600 });

// tenant-routing a multitenant appodhoz: registry-lookup cache-elve (5b!)
const dbId = await env.TENANT_MAP.get(`tenant:${slug}`);
Jó KV-baNEM való KV-ba
config, feature flagek, A/B variánsokszámlálók, egyenlegek (írás-verseny → DO)
cache (API-válaszok, renderelt részletek)erős konzisztenciát igénylő adat (→ D1/DO)
redirect-térképek, tenant→DB routinggyakran (több mint ~1/s) írt kulcsok
session-token → user lookup (read-heavy)nagy bináris tartalom (→ R2)

6.5A nagy térkép: hova tegyem az adatot?

Ezzel a modullal együtt már az összes tárolási primitívet ismered — így áll össze a döntési térkép:

Adat jellegeIde valóAWS-megfelelő
relációs üzleti adat (meglévő, nagy)Postgres + Hyperdrive (4. modul)Aurora + RDS Proxy
relációs adat (új, közepes, per-tenant)D1 (5. modul)
fájlok, blobok, exportok, uploadokR2S3
config, flag, cache, routing-táblaKVElastiCache / SSM
erősen konzisztens állapot, számlálók, realtime koordinációDurable Objects (7. modul)nincs
kérés-szintű HTTP-cacheCache API / Cache-Control (3. modul)CloudFront

6.6Ellenőrizd magad

  1. Miért az R2 a migrációd „leggyorsabban megtérülő" lépése, ha sok fájlt szolgálsz ki?
    Válasz

    Az S3-nál az egress (+CloudFront-forgalom) tipikusan a tárolásnál is nagyobb tétel; az R2-nél a kimenő forgalom ingyenes, és a meglévő S3-kód endpoint-cserével működik — kevés munka, azonnali költségcsökkenés.

  2. Hogyan migrálnál át 5 TB-nyi S3-adatot leállás és nagy egyszeri egress-számla nélkül?
    Válasz

    Sippyvel: az R2 az S3 elé kerül, a kért-de-hiányzó objektumokat első hozzáféréskor húzza át és tárolja el — a forgalom fokozatosan áll át, objektumonként egyszeri S3-egress mellett.

  3. Egy 2 GB-os videót kell fogadnod a userektől. Miért nem mehet a Workeren át, és mi a helyes minta?
    Válasz

    A kérés-body limit (plan-függően ~100–500 MB) és a Worker-memória (128 MB) miatt. Helyes minta: a Worker presigned PUT URL-t ír alá, a kliens közvetlenül R2-be tölt — multipart uploaddal, a félbehagyottakat lifecycle-szabály takarítja.

  4. Feature flaget tárolsz KV-ban. Egy átkapcsolás után meddig láthatnak egyes userek régi értéket, és baj-e ez?
    Válasz

    Az eventual consistency miatt jellemzően másodpercekig, legfeljebb ~60 s-ig. Feature flagnél ez elfogadható; ha egy adatnál nem az, akkor az nem KV-ba való, hanem D1-be vagy Durable Objectbe.

  5. A multitenant appod minden kérésnél kikeresi a tenant→DB hozzárendelést a registryből. Hogyan gyorsítod ezt KV-val, és mi a kockázata?
    Válasz

    A registry-lookup eredményét KV-ba cache-eled (tenant:slug → db_id) — a forró tenantok lookupja helyi cache-ből jön. Kockázat: átállításkor (pl. tenant-migráció másik DB-be) a régi érték max ~60 s-ig élhet — ilyenkor rövid TTL-t használsz vagy verziózott kulcsot.

Előző5b. modul — D1 a gyakorlatban — mélymerülés Következő 7. modul — Aszinkron munka: Queues, Cron, Workflows, Durable Objects