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.
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ület | Mikor 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 / wrangler | bucket-menedzsment, ad-hoc műveletek |
A meglévő S3-kódod átállítása jellemzően ennyi: endpoint + credential csere (a region: "auto"):
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
WNAM, EEUR…) és jurisdiction (eu) adható.{ "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.
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.
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-ba | NEM való KV-ba |
|---|---|
| config, feature flagek, A/B variánsok | szá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 routing | gyakran (több mint ~1/s) írt kulcsok |
| session-token → user lookup (read-heavy) | nagy bináris tartalom (→ R2) |
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 jellege | Ide 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, uploadok | R2 | S3 |
| config, flag, cache, routing-tábla | KV | ElastiCache / SSM |
| erősen konzisztens állapot, számlálók, realtime koordináció | Durable Objects (7. modul) | nincs |
| kérés-szintű HTTP-cache | Cache API / Cache-Control (3. modul) | CloudFront |
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.
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.
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.
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.
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.