What one machine measured.

Latency and throughput for a produce, memory per broker, and the costs that changed in the last round of performance work. Each table compares the newest run with an earlier one. The full output is in the Benchmarks README.

Setup

2026-11-10, commit 56f0c3a. Apple M4 Pro, macOS, APFS on the internal SSD. Release build. MinIO in Docker on the same machine. The machine was otherwise idle, and each benchmark ran once.

The baseline is 2026-11-05, commit 97cd9f3, on the same machine. All brokers share one SSD, and on macOS fsync is a full device flush. Numbers with three brokers are pessimistic for that reason.

What an acknowledgement is

A producer sends a batch and waits for the broker to answer. That answer is the acknowledgement, or ack. It is the broker's promise that the batch is safe, and it is what every latency below measures: the time from sending a batch to its ack. A producer asks for a promise with the acks setting:

SettingKafka acknowledges whenCamus acknowledges when
acks=0Never. The producer does not wait.Never. The batch may be lost. Not benchmarked.
acks=1The leader has written the batch.The same moment as acks=all.
acks=allEvery in-sync replica has the batch.The topic's durability mode is satisfied, as below.

So for Camus the ack depends on the topic's durability.mode, not on acks:

ModeThe ack meansWhat can still be lost
walA majority of the WAL shard has fsynced the batch to local disk. The upload to the bucket follows.Acknowledged batches not uploaded yet, normally a few seconds of log at most. They are lost only if a majority of brokers lose their disks before the upload.
r2The object is in the bucket, and the metadata group has committed its offsets.Nothing acknowledged, even if every broker's disk is gone.

That is why the two modes differ by more than 20 times in the latency table. A wal ack waits for disk flushes on a majority of brokers. An r2 ack waits for an object store put and a metadata commit. The fsyncs per ack column in the throughput table shows how many acks share one disk flush. Under load, one flush answers many producers.

Time to an ack, ms
wal, a disk majority
p50 11.11 p99 27.07
r2, the object stored
p50 265.82 p99 280.20

Three brokers, one producer, 1 KiB records, acks=all.

Consumers acknowledge too, in a different sense. A consumer group commits the offset it has processed up to, and that commit is a metadata write. The metadata writes row near the end measures it. The ack to a waiting consumer row measures something else: how soon after a producer's ack a consumer sees the batch.

Latency, 3 brokers

One producer sends 1 KiB records with acks=all and waits for each acknowledgement before sending the next. wal answers after a disk majority has the batch. r2 answers after the object is stored. See durability.

ModeSamplesp50 msp99 msmax msBaseline p50 / p99 / max
wal200011.1127.0742.9611.06 / 31.06 / 38.03
r2100265.82280.20284.94278.40 / 284.75 / 285.28

Throughput

Each producer sends batches of 16 records of 1 KiB with acks=all for 10 seconds. The 3-broker runs use one WAL shard. fsyncs per ack shows how many acknowledgements share one disk flush. Lower means more sharing.

BrokersProducersMiB/sp50 msp99 msfsyncs/ackBaseline MiB/sBaseline p50 / p99Baseline fsyncs/ack
311.311.1328.961.031.311.06 / 32.211.03
384.129.1749.030.394.824.06 / 51.470.26
33215.630.9251.960.1015.330.05 / 60.230.06
113.84.0010.081.012.65.65 / 12.531.02
189.214.0624.060.4312.89.07 / 20.840.25
13227.717.9828.230.1242.211.09 / 22.600.06
Throughput, MiB/s
3 brokers, 1 producer
Now 1.3 Baseline 1.3
3 brokers, 8 producers
Now 4.1 Baseline 4.8
3 brokers, 32 producers
Now 15.6 Baseline 15.3
1 broker, 1 producer
Now 3.8 Baseline 2.6
1 broker, 8 producers
Now 9.2 Baseline 12.8
1 broker, 32 producers
Now 27.7 Baseline 42.2

Three runs are slower than the baseline: 1 broker with 8 and with 32 producers, and 3 brokers with 8 producers. That drop is older than the work measured below. Main just before that work, commit 9687151, measured the same on 1 broker: 8.9 and 9.4 MiB/s with 8 producers, 27.2 and 28.6 MiB/s with 32, with the same fsyncs per ack. The change came in between the baseline and #93, and it is being bisected in #129.

Memory

Resident memory of the broker serving produce, from ps. Peak is the highest reading, sampled every 20 ms. After is the mean over 2 seconds, starting 3 seconds after the load stopped.

RunPeak MiBAfter MiBBaseline peak MiB
Latency wal, 3 brokers29.329.330.8
Latency r2, 3 brokers22.422.6
3 brokers, 1 producer37.229.9
3 brokers, 8 producers41.647.0
3 brokers, 32 producers177.7152.8232.1
1 broker, 1 producer45.234.5
1 broker, 8 producers148.2151.5202.4
1 broker, 32 producers273.6263.2652.0
Peak memory, MiB
Latency wal, 3 brokers
Now 29.3 Baseline 30.8
Latency r2, 3 brokers
Now 22.4 Baseline 22.6
3 brokers, 1 producer
Now 37.2 Baseline 29.9
3 brokers, 8 producers
Now 41.6 Baseline 47.0
3 brokers, 32 producers
Now 177.7 Baseline 232.1
1 broker, 1 producer
Now 45.2 Baseline 34.5
1 broker, 8 producers
Now 148.2 Baseline 202.4
1 broker, 32 producers
Now 273.6 Baseline 652.0

Idle was 20.7 MiB in the wal latency run. Followers peaked at 36.7 to 38.4 MiB in the heaviest 3-broker run.

Costs changed in #117 to #128

These benchmarks were added with the changes they measure. Each "before" comes from that pull request's own run, on a different day, so read the comparison as indicative.

BenchmarkNowBefore
Produce to 8 partitions in one request, 1 broker, p50 / p994.04 / 10.99 ms34.26 / 49.95 ms
Ack to a waiting consumer, p50 / p90 / p99 / max0.18 / 0.31 / 1.56 / 4.00 ms11.35 / 19.68 / 22.34 / 22.60 ms
One connection, 5 produces in flight572 acks/sabout 225 acks/s
Metadata writes, 64 concurrent writers5349 writes/sabout 266 writes/s
3000 sealed extents: partition metadata row130 bytes413 KB
3000 sealed extents: fetch the newest, p50 / p990.15 / 0.35 ms57.09 / 72.76 ms
3000 sealed extents: produce, p50 / p994.98 / 12.38 ms
3000 sealed extents: read all 3000 batches103 ms cold, 2 ms again259 ms cold, 261 ms again
How many times better
Fetch the newest of 3000 extents, p50
381×
Partition metadata row size
3,177×
Read 3000 batches again, from cache
130×
Ack to a waiting consumer, p50
63×
Metadata writes, 64 writers
20×
Produce to 8 partitions, p50
8.5×
Read 3000 batches cold
2.5×
5 produces in flight
2.5×

Log scale, so the 3,177× row doesn't flatten the rest: equal lengths are equal multiples. For times and sizes the factor is before ÷ now; for rates it is now ÷ before.

What each one measures

Linux

Some of these were also run on Linux, in an OrbStack VM on the same Mac. Those results are in the descriptions of #118 and #120. They are useful only as before and after comparisons, so they are not in these tables.

Run them yourself

Every benchmark is an ignored test, run in release mode. The ones with CAMUS_MINIO=1 need the local bucket from the bucket page.

CAMUS_MINIO=1 cargo test --release -p camus-broker --test latency -- --ignored --nocapture
CAMUS_MINIO=1 cargo test --release -p camus-broker --test throughput -- --ignored --nocapture
CAMUS_BENCH_BROKERS=1 CAMUS_MINIO=1 cargo test --release -p camus-broker --test throughput -- --ignored --nocapture
CAMUS_MINIO=1 CAMUS_BENCH_BROKERS=1 CAMUS_BENCH_PARTITIONS=8 cargo test --release -p camus-broker --test latency -- --ignored --nocapture
CAMUS_MINIO=1 cargo test --release -p camus-broker --test extent_cost -- --ignored --nocapture
cargo test --release -p camus-broker --test tail_latency -- --ignored --nocapture
cargo test --release -p camus-broker --test pipelined_throughput -- --ignored --nocapture
cargo test --release -p camus-metadata --test write_throughput -- --ignored --nocapture