Aankondiging van Cloudflare K2: serverloze event streams
Bij traditionele Remote Procedure Call (RPC)-architecturen bestaat er een kernprobleem: producenten en consumenten moeten in schaal en tijd op elkaar zijn afgestemd. Als producenten te veel data versturen voor de consumenten om te verwerken, of als de consumenten of downstream-services onbeschikbaar worden, gaan events verloren. Dit probleem wordt verergerd wanneer meerdere consumenten de data onafhankelijk van elkaar moeten verwerken. Een voorbeeld hiervan is een e-commerce backend die events verzendt wanneer transacties zijn voltooid; deze moeten vervolgens worden gelezen door zowel een analysesysteem als een fraudedetectieservice.
We kunnen dit oplossen door onze producenten en consumenten te ontkoppelen door een service in het midden te plaatsen die writes absorbeert, terwijl onafhankelijke readers de data in hun eigen tempo kunnen consumeren.
Vandaag lanceren we Cloudflare K2 in public beta om dit probleem op te lossen. K2 is een duurzame event-streaming-primitieve op het Developer Platform. Je verzendt events naar een K2-stream, die deze opslaat als een geordend logboek. Consumenten kunnen deze op verschillende manieren lezen, bijvoorbeeld door de leesacties te verdelen over een set consumenten, of door alle berichten naar alle consumenten te leveren. Het is volledig serverloos, schaalt naar enorme hoeveelheden data en ondersteunt langdurige retentie, waardoor data niet verloren gaat, zelfs niet bij langdurige downtime van de consument.
Onder de motorkap implementeert K2 een gepartitioneerd, duurzaam logboek bovenop R2 object storage, wat het mogelijk maakt om te schalen naar enorme hoeveelheden opslag.
Streams aan de edge
We hebben K2 oorspronkelijk gebouwd omdat we een duurzame buffer aan de edge nodig hadden, in eerste instantie als ingestielaag voor Basin Pipelines. Pipelines wordt aangedreven door een stream-processing-engine die werkt op een pull-gebaseerd model. Dit betekent dat een ander systeem events moet opslaan voordat ze worden gelezen, getransformeerd en naar R2 worden geschreven. Omdat we ons committeren om nooit events te verliezen zodra ze zijn geaccepteerd in de Pipelines Stream, moet die opslag duurzaam zijn — wat betekent dat er over potentieel lange perioden geen data verloren mag gaan.
Dit is het punt waar de meeste bedrijven Apache Kafka zouden implementeren. Echter, Pipelines draait op de Cloudflare edge, die zich verspreidt over een enorm aantal servers in meer dan 335 steden. Onze unieke architectuur betekent dat we vaak geen traditionele distributed systems-software zoals Kafka kunnen draaien, en dat we opnieuw moeten nadenken over hoe deze systemen worden gebouwd en beheerd.
Voor stateful services in het bijzonder brengt de wereldwijde infrastructuur van Cloudflare enkele uitdagingen met zich mee: we krijgen relatief kleine delen van machines, die machines zijn relatief efemeer, en netwerkinfrastructuur verloopt vaak via het publieke internet. Maar onze infrastructuur heeft ook een paar superkrachten: het bevindt zich dicht bij gebruikers overal ter wereld en heeft een ongelooflijke capaciteit om horizontaal te schalen.
Bij het ontwerpen van het duurzame buffersysteem dat K2 werd, besloten we te vertrouwen op de krachtige state-primitieve die we al hebben: R2. Object storage-systemen zoals R2 combineren extreem duurzame opslag (11 negens!) met sterk consistente API's. Door replicatie en consensus uit te besteden aan de opslaglaag, kunnen we de applicatielaag (in dit geval K2) radicaal eenvoudiger, goedkoper en performanter maken. Een secundair voordeel is dat het compute en storage scheidt, wat betekent dat beide onafhankelijk kunnen worden geschaald. Dit stelt ons in staat om enorme hoeveelheden historische data tegen lage kosten op te slaan.
Hoe bouwen we een logboek bovenop object storage? Een direct probleem is dat R2 — net als andere object stores — geen 'appends' ondersteunt, de standaardoperatie voor een logboek. In plaats daarvan moeten we volledige bestanden, of segmenten, schrijven die groot genoeg zijn om de kosten van het schrijven en lezen van elk bestand te overwinnen. We doen dit door writes eerst in het geheugen op een edge-service te verzamelen. Na een korte wachttijd voor binnenkomende data, schrijven we alle events als een segmentbestand. We bereiken ordening en strikt oplopende offsets met behulp van de atomaire operaties van R2, zonder dat daar een aparte coördinatieservice voor nodig is.
Hoewel bouwen op R2 veel voordelen heeft, is er één nadeel: hogere produce-latencies. Schrijven naar object storage is langzamer dan naar een lokale schijf, en we moeten wachten tot de lokale batch is verzameld voordat we het schrijven starten. In onze initiële release van K2 resulteert dit in ongeveer 1 seconde produce-latency bij het 99ste percentiel van de responstijden.
We zullen in een toekomstige technische deep dive meer details delen over het ontwerp van K2.
Streams, Queues of Pipelines?
Cloudflare heeft verschillende bestaande asynchrone leveringsprimitieven, waaronder Queues en Basin Pipelines. Wanneer moet je kiezen voor K2 in plaats van deze bestaande producten?
Er zijn oppervlakkige overeenkomsten tussen Queues en K2 Streams: beide ontvangen events, slaan deze duurzaam op en leveren ze aan consumenten. Queues zijn ontworpen om individuele items van kostbaar of tijdrovend werk te volgen die asynchroon voltooid moeten worden. Bijvoorbeeld, een applicatie voor beeldbewerking kan een gebruikersverzoek in een wachtrij plaatsen om door de eigenlijke beeldbewerkingsservice te worden afgehandeld. Ze ondersteunen complexe logica op het niveau van een specifiek werkelement, zoals retries, vertragingen en dead-letter queues voor mislukte pogingen.
K2 is daarentegen ontworpen voor datatransport op grote schaal, langdurige retentie en fan-out consumptie. Berichten worden geproduceerd en geconsumeerd in batches, wat efficiënte verwerking mogelijk maakt ten koste van retries op berichtniveau. Deze batching zorgt ook voor een hogere producer-latency dan bij queues.
Basin Pipelines is een serverloze ingestieservice. Je kunt Pipeline JSON-events verzenden, die getransformeerd kunnen worden en naar R2 of een Basin Catalog kunnen worden geschreven. We raden Pipelines aan wanneer het eindresultaat het schrijven van events naar object storage of Iceberg-tabellen is, en K2 wanneer er sprake is van aangepaste verwerking of het schrijven naar andere bestemmingen.
Aan de slag
Het gebruik van K2 begint met het maken van een stream. Je kunt veel streams in je account hebben voor verschillende use cases of typen events. Streams kunnen worden gemaakt via cf, Wrangler, het dashboard of de API.
Laten we het voorbeeld nemen van het verzamelen en verwerken van productanalytics. Eerst maken we een stream aan met cf:
$ cf k2 streams create --name app_events --http-enabled
{
"id": "d78b09ee1f50430e9ec92a8af92b0231",
"name": "app_events",
"retention_seconds": 604800,
"endpoint": "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com",
"http": {
"enabled": true,
"authentication": false
},
"worker_binding": {
"enabled": true
},
"created_at": "2026-09-28T15:14:39.053Z",
"modified_at": "2026-09-28T15:14:39.053Z"
}
Zodra we een stream hebben, kunnen we data gaan produceren via een HTTP API of Worker binding. Bijvoorbeeld, vanuit een Worker:
const result = await env.EVENTS.send([
{
content: new TextEncoder().encode(
JSON.stringify({
event: "page_view",
path: new URL(request.url).pathname,
timestamp: Date.now(),
}),
),
headers: { "content-type": "application/json" },
},
]);
if (!result.success) {
console.error(`Produce failed: ${result.error.message}`);
return new Response("Failed to record event", {
status: result.error.retryable ? 503 : 500,
});
}
K2 representeert data als bytes, dus je kunt elk formaat of elke codering gebruiken die zinvol is voor jouw applicatie.
Nu we events in een stream hebben, kunnen we een abonnement (subscription) aanmaken. Abonnementen verdelen het werk tussen consumenten, wat lees-parallellisme mogelijk maakt — het opschalen naar meerdere readers om meer belasting te kunnen verwerken dan één enkele server aankan.
We kunnen een abonnement aanmaken via de HTTP API:
$ curl -X POST "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com/subscriptions" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"name": "analytics_processor",
"start_at": { "type": "earliest" }
}'
{
"result": { "id": "ee13f761783d3823a447a47b572ebf76" },
"success": true,
"errors": [],
"messages": []
}
Met het abonnement aangemaakt, kunnen we dit vervolgens pollen vanuit elke consument:
$ curl -X POST \"https://4d8f5394e3e733debdeca9c65c5b7439.k2.cloudflarestorage.com/subscriptions/ee13f761783d3823a447a47b572ebf76/consume" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{
"worker_id": "analytics-1",
"max_records": 100
}'
{
"result": {
"batch_id": "b7e4c9210a3f468d95c2e1068fdb734a",
"leased_until_ms": 1790633929437,
"records": [
{
"timestamp_ms": 1790633629168,
"content": "eyJldmVudCI6InBhZ2VfdmlldyJ9",
"headers": {
"content-type": "application/json"
}
},
...
]
},
"success": true,
"errors": [],
"messages": []
}
Wanneer een client consume aanroept, ontvangen ze een lease voor die specifieke batch events voor 5 minuten. De client kan een van de volgende drie acties ondernemen:
- ack de batch: markeert deze als verwerkt en zorgt ervoor dat deze niet opnieuw wordt geleverd.
- nack (negative ack): geeft aan dat de verwerking is mislukt en dat de batch opnieuw geleverd moet worden.
- extend de lease: in het geval dat er meer tijd nodig is om de verwerking te voltooien.
$ curl -X POST "https://d78b09ee1f50430e9ec92a8af92b0231.k2.cloudflarestorage.com/subscriptions/ee13f761783d3823a447a47b572ebf76/batches/b7e4c9210a3f468d95c2e1068fdb734a/ack" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{ "worker_id": "analytics-1" }'
Dit is één manier om van K2 te consumeren: het werk verdelen over meerdere consumenten zodat elke consument een deel van de data krijgt. Een andere manier van lezen is met een apart abonnement voor elke consument — het pub/sub-patroon — waarbij elke consument alle berichten ziet. Je kunt deze twee benaderingen ook combineren door meerdere onafhankelijke consumer-pools te hebben.
Raadpleeg de K2-documentatie voor volledige details over de API's.
Prijsstelling en Beschikbaarheid
K2 is vandaag beschikbaar in public beta voor accounts met een Workers Paid-abonnement, binnen de volgende limieten:
- Maximaal 10 GB aan gebruikte opslag
- 30 MB/s produce per stream
Als je hogere limieten nodig hebt, neem dan contact op met het team via Discord of vul het formulier voor limietverhoging in.
Het gebruik van K2 zal tijdens de beta-periode niet in rekening worden gebracht. Zodra we starten met factureren, voorzien we de volgende prijsstelling:
| Item | Prijs |
|---|---|
| Geproduceerde Data | $0.04 / GB |
| Geconsumeerde Data | $0.04 / GB |
| Bewaarde Data | $0.02 / GB / maand |
Wat volgt er nog?
We hebben een ambitieuze roadmap voor K2 voor de komende maanden, waaronder:
- Hogere write-parallellisme, tot streams van meerdere GB/s.
- Message keys en garanties voor key-based ordening.
- Push-gebaseerde worker-consumenten.
- Een 'Express tier' met lagere produce- en end-to-end latencies.
- Drop-in ondersteuning voor Apache Kafka-clients.
Groetjes,