Programmable RevenueBuilder Edition Subscribe
Issue 02 Builder Spotlight Reported

Richard Artoul, WarpStream: Kafka rebuilt on object storage

A replicated Kafka cluster bills a byte three times: the replica disks, the traffic between zones, and the brokers that own the disks. Richard Artoul's WarpStream hands the first two to the bucket and makes the brokers stateless, and his posts print the bill by line, latency floor included. Profiled from his own published account.

Thomas Cornelius September 12, 2026 7 min read
Technical schematic: three producers feeding three stateless agents with crossed-out disks, converging into a batch box and one thick arrow into an object storage cylinder, with three consumers reading from it.
Three producers, three agents with no disks, one batch, one upload into the bucket, and three consumers reading the same objects.
Engraved line-art portrait of Richard Artoul Photo: graph8
Builder Spotlight Richard Artoul Co-founder , WarpStream

In July 2023 Richard Artoul priced one streaming workload two ways. At 140 MiB/s of produce traffic, a three-zone Kafka cluster pays $641 a day in inter-zone transfer, and the system he built instead pays under $15 a day (“Kafka is dead, long live Kafka”, WarpStream blog, 2023-07-25). The difference comes from where each byte is paid for. On Kafka a byte lands on the leader’s disk, crosses two zone boundaries to reach two follower disks, and the three brokers holding those disks have to stay up with their state intact. Object storage already keeps several copies of a byte across zones for one flat price per GiB-month and does not meter the traffic between them, so a log built on it has only one cost left to remove, which is the state on the machine.

Artoul co-founded WarpStream, and both posts this profile draws on are his. Confluent acquired WarpStream, and IBM completed its acquisition of Confluent on 2026-03-17, so the company is now part of IBM. The WarpStream numbers below are his, and the numbers about our node come from the earlier articles this week.

Stateless agents over a bucket

The 2023 post opens with the question that organises the design: “What would Kafka look like redesigned today to run in modern cloud environments?” His answer replaces the broker with a stateless agent, written in Go, that accepts produce requests, batches them, and writes each batch to S3 or an equivalent store. Any agent can serve any partition, because no agent owns any data. The data lives in the bucket, and the map of where everything is lives in a separate control plane. Local disks, broker rebalancing and ZooKeeper all go away, inter-zone replication traffic drops to zero, and the post claims the result is 5 to 10x cheaper than Kafka in the cloud.

Kafka is a system for moving a stream of events, a click or a form fill, from where it happened to the systems that react to it. It runs on a set of servers called brokers, and each broker keeps its share of the events on its own disk. WarpStream keeps the events in cloud storage instead, so the servers in front hold nothing of their own and can be swapped out at any time. What that costs is a moment’s wait before each event is confirmed.

The inter-zone arithmetic

Artoul puts two list prices side by side: inter-zone transfer at $0.053 per GiB, and S3 storage at $0.0214 per GiB-month. Moving a GiB across a zone boundary once costs more than storing it in S3 for two months.

His test workload produced 140 MiB/s and read it back with three consumers, 560 MiB/s in total. On Kafka, the inter-zone line for that produce rate works out to 0.14 GiB multiplied by $0.053 per GiB multiplied by the 86,400 seconds in a day, which is $641 a day. On WarpStream, the measured inter-zone fees for the same workload came to under $15 a day, the S3 API calls to under $40 a day, and the agents needed 27 vCPUs in total. To rerun the arithmetic for your own system, take the bytes per second that cross a boundary, multiply by your provider’s price, and multiply by 86,400.

ONE BYTE ON A REPLICATED LOG, PAID THREE TIMES producer leader broker, disk 1 follower, zone B, disk 2 follower, zone C, disk 3 zone crossing, metered per GiB zone crossing, metered per GiB three disks two crossings three stateful machines THE SAME BYTE ON STATELESS AGENTS OVER A BUCKET, PAID ONCE producer stateless agent batch object storage no disk, any agent, any partition wait for the batch replicated inside, flat per GiB-month one PUT, metered per request the byte is the same; the meter counts disks, zone crossings and requests
The same byte on a replicated log and on stateless agents over a bucket. The upper row pays for three disks, two zone crossings and three stateful machines. The lower row pays for one upload.

The latency tradeoff

The 2023 post also prints the latency cost. WarpStream doesn’t acknowledge a produce request until the batch is durably stored in S3 and committed to the control plane, and in 2023 that meant a P99 of about 400 ms for produce requests and about one second from producer to consumer. In exchange, nothing an agent has acknowledged is lost when that agent dies. The batching that saves the money is also what adds the wait, because a batch takes time to fill.

The 2025 benchmark on S3 Express One Zone

In 2025 the same design ran on S3 Express One Zone, the storage class that keeps data in a single availability zone in exchange for lower latency (“WarpStream S3 Express One Zone Benchmark and Total Cost of Ownership”, WarpStream blog, 2025-04-18). The benchmark ran five m7g.xl instances with an agent of 3 vCPUs and 12 GiB on each, against one topic with 288 partitions and 1,024-byte messages, and 64 producers and consumers pushed 270,000 messages a second for four days. That came to 268 MiB/s of traffic at roughly 50 percent of the agents’ CPU.

Produce latency fell to a P99 of 169 ms and a median of 105 ms, roughly 3x lower than on S3 Standard, with the same durable write before every acknowledgement.

He also prints the monthly bill line by line. Bandwidth for uploads came to $1,111 and for retrievals to $416. PUTs came to $1,034, the five VMs to $338, storage after compaction to about $130, and GETs to $62. The post states a total of $2,961 a month, which is the sum of those lines excluding storage. It prices an equivalent self-hosted three-zone Kafka cluster at over $20,252 a month, of which $14,765 is inter-zone networking, and puts the WarpStream bill, control plane fees included, at 63 percent cheaper.

What transfers to ClickHouse on R2

We run one ClickHouse node with every intent table in a Cloudflare R2 bucket behind a local NVMe cache, a steady daily ingest, and the merge settings described in the mechanism article. Cloudflare bills Class A operations, the class that includes PutObject and UploadPart, at $4.50 per million (R2 pricing page, updated 2026-08-07), so we watch the same line he does. His agents hold produce requests until a batch is large enough to be worth a PUT. Our node uploads in 128 MiB parts, keeps fresh parts compact, and caps merges at 50 GB, which on paper means a byte is rewritten roughly one to one and a half more times over its life. PUTs are the largest line in his breakdown after bandwidth, on a design that already batches hard, and our merge tuning exists to hold that line down. We haven’t measured our own write amplification yet, so it stays on the open list.

Losing a machine transfers too. His agents can be killed and our node can fail, and in both cases the bucket still holds the data. Neither claim says anything about availability. His control plane holds the map of where everything is, ours sits in local metadata, and that map has to be running somewhere before anyone can read.

What does not transfer

A streaming log and an analytical table are read in different ways, and that difference decides where the cache has to go. A log is read forward, once per consumer, soon after the write, so its working set is the tail that just arrived. An analytical table is scanned by column, many times, weeks after the write, so its working set is whatever recent queries happened to touch. Our node therefore keeps 2.5 TB of NVMe in front of the bucket.

The latency floor is different too. A produce request carries one message or a small batch, and the client waits for durability on every request. A ClickHouse INSERT carries thousands of rows into one part, waits for that part to upload, and returns. The round trip to the bucket is paid once per part rather than once per row, so an analytical store gets durability before acknowledgement almost for free, because it already batches for a different reason.

Our extension: an event stream in front of a signal pipeline

This section is ours, not his, and it describes a design rather than something we run. The signals a revenue system reacts to are events: a page visit, a form fill, a reply. Today they reach our ClickHouse node as batched inserts. Built Artoul’s way, a row of stateless agents would accept those events, batch them, and write each batch once to R2, with the resolver and the scoring job reading the same objects as consumers. An agent that dies loses nothing it acknowledged, and a new one joins with no disk to fill. A few hundred milliseconds to an acknowledgement is far below the minutes a sequence step runs on, so a signal pipeline would not notice the latency he pays for durability.

Still open

We don’t know whether a cache tier in front of object storage is a permanent part of this kind of design or a bridge. The 2025 post swaps in a single-zone storage class and cuts produce latency roughly 3x. Our node keeps a 2.5 TB NVMe cache, and ClickHouse Cloud built a shared cache tier (Tom Schreiber, “Building a Distributed Cache for S3”, ClickHouse blog, 2025-05-28). That question goes into the courtesy note to Artoul.

Builder Spotlight profiles an engineer outside graph8 from their own published work only, and a courtesy note goes to the subject before publication.

Further reading

When the durable copy of the data lives in object storage, the servers in front of it can be replaced at will.

Build with graph8