Skip to main content

Spate Benchmark

Every system here consumes the same topic of Confluent-framed Avro, flattens each message's events array into one row per event, and inserts those rows into ClickHouse. Same bytes, same envelope, same hardware, same delivery guarantee.

One sortable row per arm, one table per comparability context, and nothing ranked that the fairness contract does not allow to be ranked. Here are the results; make your own determination.

Kafka → Avro → ClickHouse. 32 CPU and 96 GiB of data plane per system, at-least-once. Every system consumes the same topic, decodes and flattens each message, applies the same two filters and two derived columns, and lands the surviving rows in ClickHouse.

6 arms · 5 systems · 1 measurement context across 1 environment. Contexts are never compared with one another — what invalidates a comparison.

Spate is run by the author of this benchmark. Every row of that system carries a dagger saying so, and no published number is reported by the system that produced it.

How to read this

The mark is the uncertainty. The capsule spans the smallest to the largest repetition and the notch is the median — at three repetitions those are the three measurements, and nothing is modelled. Measuring one arm twice under two labels in these sweeps moved it by 7.2%, so a difference narrower than that is the rig rather than the system.

Grey is shown, not ranked. A tuned or stripped arm is drawn on the same axis, because quantifying its difference is the only reason it exists — but it gets no rank and it never sets the scale.

An empty lane is a disowned number. An infra-bound figure keeps its digits and its reason but not its position — a position on a shared axis is itself a claim, and that number describes ClickHouse rather than the system.

A dagger is a conflict of interest. It marks a system run by the author of this benchmark, on every row that system has, and it renders from the descriptor rather than from anything the site knows about that name. No published number is reported by the system that produced it.

Every axis starts at zero and prints its real end value, so no difference is magnified by a cropped scale. Scales belong to one column of one group and are never shared across groups. No system has a colour, and nothing is coloured by whether its number is good.

Find a system
Runtime
Kind
Licence
Columns

Each column’s axis rescales to what is visible, so hiding a much faster arm does not leave the rest crushed against zero — which matters more the wider the field gets. The end value printed on every axis is what it currently means.

c8gd-metal-24xl-ec2-dockerdrain6 arms

decode, flatten, filter, derive — one row per surviving event · drain — how fast the system can go through a fixed corpus · harness v2 · corpus d2-60d7e5bb2a82

Arms measured on c8gd-metal-24xl-ec2-docker under harness v2. Ordered by throughput per core.
#System · armThroughput per corehigher is better →02.00Mrows/s per coreThroughputhigher is better →025.00Mrows/sCores used← lower is better040.00coresPeak memory← lower is better080.00 GBbytesDuplicate rows← lower is better01rowsdetail
1Spate — run by the vendor of this benchmarkNativenative · 0.2.0 · 2026-08-261.59Mrange 3.4%20.37Mrange 2.5%12.79range 0.9%21.84 GBtiesrange 0.7%0tiesno spread (3 reps)Detail for Spate Native
Spate · Nativeclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
native · threads 32 · shards 32 · inflight 4 · linger_ms 2000 · max_rows 1048576
Provenance
0.2.0 · 2026-08-26 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:285c47a5618498972148b8a8c7b7d99566f1c1c4dbb1086897c0fccd9c4e51d9
This reading
1440 sampler samples over 144.3s; headroom broker consume 33%, clickhouse ingest (native) 47%; server-side: 3075 insert(s) into default.sensor_events over 144.3s, 2940000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
reused infra
Other measurements
  • ClickHouse CPU 1425.67 s
  • ClickHouse CPU per written row 0.486 µs
  • ClickHouse CPU wait 27.73 s
  • Bytes inserted 193.66 GB
  • Rows per insert 954k
  • Rows written server-side 2940.00M
  • GC pause max not measured
  • GC pause p99.9 not measured
  • GC pause total not measured
  • JVM heap committed peak not measured
  • JVM heap configured not measured
  • JVM heap live peak not measured
  • Ch merge duration 3740.78 s
  • Ch rows merged 2251.79M
  • Ch settle 2.00 s
  • Control plane gc pause max not measured
  • Control plane gc pause p999 not measured
  • Control plane gc pause p99 not measured
  • Control plane gc pause total not measured
  • Control plane jvm heap committed peak not measured
  • Control plane jvm heap configured not measured
  • Control plane jvm heap live peak not measured
  • Nr throttled not measured
  • Peak shmem not measured
  • Window resolution 0.00

Full profile for Spate — every arm, its configuration, and how to tell us we got it wrong.

2ClickHouse Kafka table engineDistributed forwardnative · 26.3.17.4 · 2026-08-251.13Mrange 0.2%18.61Mrange 4.1%16.52range 3.9%20.93 GBrange 4.7%0tiesno spread (3 reps)Detail for ClickHouse Kafka table engine Distributed forward
ClickHouse Kafka table engine · Distributed forwardclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
distributed · num_consumers 32 · block_msgs 16384 · flush_ms 5000 · poll_timeout_ms 500
Provenance
26.3.17.4 · 2026-08-25 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:99637c712a9e241707fc20eba16d92d848d5fb78806cbdec7557a54075a489a6
This reading
1622 sampler samples over 162.6s; headroom broker consume 29%, clickhouse ingest (native) 42%; server-side: 1660 insert(s) into default.sensor_events over 162.6s, 2940000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
cpu cap throttled · reused infra
Other measurements
  • ClickHouse CPU 939.47 s
  • ClickHouse CPU per written row 0.320 µs
  • ClickHouse CPU wait 6.08 s
  • Bytes inserted 189.43 GB
  • Rows per insert 1.77M
  • Rows written server-side 2940.00M
  • GC pause max not measured
  • GC pause p99.9 not measured
  • GC pause total not measured
  • JVM heap committed peak not measured
  • JVM heap configured not measured
  • JVM heap live peak not measured
  • Ch merge duration 4118.30 s
  • Ch rows merged 2409.07M
  • Ch settle 2.00 s
  • Control plane gc pause max not measured
  • Control plane gc pause p999 not measured
  • Control plane gc pause p99 not measured
  • Control plane gc pause total not measured
  • Control plane jvm heap committed peak not measured
  • Control plane jvm heap configured not measured
  • Control plane jvm heap live peak not measured
  • Nr throttled not measured
  • Peak shmem not measured
  • Window resolution 0.00

Full profile for ClickHouse Kafka table engine — every arm, its configuration, and how to tell us we got it wrong.

3Apache FlinkRowBinaryrowbinary_nt · 2.2.1 · 2026-09-11547krange 4.3%5.74Mrange 2.8%10.41range 3.8%22.44 GBrange 0.2%0tiesno spread (3 reps)Detail for Apache Flink RowBinary
Apache Flink · RowBinaryclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
rowbinary-nt · parallelism 32 · slots 32 · process_mib 21504 · network_memory_max 9223372036854775807b · runtime_image flink:2.2.1-java17 · jvm_opts · avro_mode specific · linger_ms 1000 · max_rows 12500 · buffered_rows 25000 · inflight 1 · max_batch_bytes 16777216 · poll_records 500 · partition_fetch_bytes 1048576 · fetch_bytes 52428800 · connections 10 · network_buffer_bytes 300000
Provenance
2.2.1 · 2026-09-11 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:0097f1fa0cbe1f943378f403f5785504d73ab3de6e83cd2902f0395722a76869
This reading
5103 sampler samples over 512.4s; GC: G1 on JVM 17.0.20+8 (release), 749 pause(s) totalling 28142.8ms, max 73.9ms (Young (Normal) (G1 Evacuation Pause)); heap 17568 MiB committed of 17568 MiB configured; control plane GC: Serial on JVM 17.0.20+8 (release), 6 pause(s) totalling 155.8ms, max 69.7ms (Full (Metadata GC Threshold)); heap 1299 MiB committed of 1344 MiB configured; headroom broker consume 9%, clickhouse ingest (rowbinary_nt) 52%; server-side: 237508 insert(s) into default.sensor_events over 512.4s, 2940000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
cpu cap throttled · reused infra
Other measurements
  • ClickHouse CPU 5058.80 s
  • ClickHouse CPU per written row 1.721 µs
  • ClickHouse CPU wait 188.10 s
  • Bytes inserted 189.77 GB
  • Rows per insert 12k
  • Rows written server-side 2940.00M
  • GC pause max 73.9 ms
  • GC pause p99.9 73.9 ms
  • GC pause total 28.09 s
  • JVM heap committed peak 18.42 GB
  • JVM heap configured 18.42 GB
  • JVM heap live peak 12.19 GB
  • Ch merge duration 12533.89 s
  • Ch rows merged 6955.76M
  • Ch settle 2.00 s
  • Control plane gc pause max 68.3 ms
  • Control plane gc pause p999 68.3 ms
  • Control plane gc pause p99 68.3 ms
  • Control plane gc pause total 153 ms
  • Control plane jvm heap committed peak 1.36 GB
  • Control plane jvm heap configured 1.41 GB
  • Control plane jvm heap live peak 29 MB
  • Nr throttled 99.00
  • Peak shmem 0 B
  • Window resolution 0.00

Full profile for Apache Flink — every arm, its configuration, and how to tell us we got it wrong.

4Kafka Connect + clickhouse-kafka-connectclickhouse-kafka-connect · RowBinary + MVrowbinary · 4.3.1-v1.5.0 · 2026-09-12179krange 8.7%4.83Mrange 6.5%25.57range 7.2%68.12 GBrange 0.0%0no spread (3 reps)Detail for Kafka Connect + clickhouse-kafka-connect clickhouse-kafka-connect · RowBinary + MV
Kafka Connect + clickhouse-kafka-connect · clickhouse-kafka-connect · RowBinary + MVclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
rowbinary-mv · tasks 32 · buffer_count 200 · poll_records 200 · buffer_flush_ms 1000 · client_version V2 · heap_mib 63488 · jvm_opts -XX:+UnlockExperimentalVMOptions -XX:ObjectAlignmentInBytes=16 -XX:G1NewSizePercent=60 -XX:ConcGCThreads=12 -XX:+AlwaysPreTouch -XX:+ExitOnOutOfMemoryError
Provenance
4.3.1-v1.5.0 · 2026-09-12 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:7d14c5a8c3d28fc5eae90252bd9dd4d9ac5f1f535f2a4564a8626ab9109c6a31
This reading
5981 sampler samples over 600.5s; GC: G1 on JVM 21.0.11+10-LTS (release), 904 pause(s) totalling 21677.4ms, max 49.4ms (Young (Normal) (G1 Evacuation Pause)); heap 63488 MiB committed of 63488 MiB configured; headroom broker consume 8%; UNPROVEN: this arm installs its own ClickHouse objects (arm DDL), so every insert also pays server-side work — a materialized view's flatten, filters and derived columns — that the "rowbinary" ingest ceiling, measured as direct inserts into the bare target, does not describe. It is deliberately NOT gated against that ceiling: same format over a different shape is the same unmeasured substitution this gate already refuses across formats.; server-side: 196933 insert(s) into default.sensor_events+default.sensor_batches_landing over 600.5s, 2980000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
cpu cap throttled · headroom unproven · reused infra
Other measurements
  • ClickHouse CPU 2323.67 s
  • ClickHouse CPU per written row 0.780 µs
  • ClickHouse CPU wait 2.36 s
  • Bytes inserted 528.88 GB
  • Rows per insert 15k
  • Rows written server-side 2980.00M
  • GC pause max 41.7 ms
  • GC pause p99.9 41.7 ms
  • GC pause total 21.67 s
  • JVM heap committed peak 66.57 GB
  • JVM heap configured 66.57 GB
  • JVM heap live peak 1.47 GB
  • Ch merge duration 16232.88 s
  • Ch rows merged 8840.13M
  • Ch settle 14.51 s
  • Control plane gc pause max not measured
  • Control plane gc pause p999 not measured
  • Control plane gc pause p99 not measured
  • Control plane gc pause total not measured
  • Control plane jvm heap committed peak not measured
  • Control plane jvm heap configured not measured
  • Control plane jvm heap live peak not measured
  • Nr throttled 4.00
  • Peak shmem 0 B
  • Window resolution 0.00

Full profile for Kafka Connect + clickhouse-kafka-connect — every arm, its configuration, and how to tell us we got it wrong.

5VectorJSONEachRowjson_each_row · 0.57.0 · 2026-08-2666krange 1.0%2.10Mrange 0.9%31.87range 0.2%51.41 GBrange 12.0%0tiesno spread (3 reps)Detail for Vector JSONEachRow
Vector · JSONEachRowclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
json-each-row · threads 32 · chunk_size_events 1000 · sources 8 · batch_events 262144 · batch_timeout_secs 1 · request_concurrency 32 · buffer_events 524288 · compression none
Provenance
0.57.0 · 2026-08-26 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:d03d7bfb33262451368f353850eef3e20bc49fb3c92f63f120b7f89a565ee340
This reading
13958 sampler samples over 1399.8s; headroom broker consume 3%, clickhouse ingest (json_each_row) 20%; server-side: 14077 insert(s) into default.sensor_events over 1399.8s, 2940000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
cpu cap throttled · reused infra
Other measurements
  • ClickHouse CPU 7704.16 s
  • ClickHouse CPU per written row 2.620 µs
  • ClickHouse CPU wait 6.04 s
  • Bytes inserted 191.54 GB
  • Rows per insert 209k
  • Rows written server-side 2940.00M
  • GC pause max not measured
  • GC pause p99.9 not measured
  • GC pause total not measured
  • JVM heap committed peak not measured
  • JVM heap configured not measured
  • JVM heap live peak not measured
  • Ch merge duration 10198.86 s
  • Ch rows merged 10837.56M
  • Ch settle 2.00 s
  • Control plane gc pause max not measured
  • Control plane gc pause p999 not measured
  • Control plane gc pause p99 not measured
  • Control plane gc pause total not measured
  • Control plane jvm heap committed peak not measured
  • Control plane jvm heap configured not measured
  • Control plane jvm heap live peak not measured
  • Nr throttled not measured
  • Peak shmem not measured
  • Window resolution 0.00

Full profile for Vector — every arm, its configuration, and how to tell us we got it wrong.

Spate — run by the vendor of this benchmarkRowBinaryinfra-boundrowbinary · 0.2.0 · 2026-08-261.66Mrange 0.1%10.24Mrange 4.2%6.15range 4.2%15.75 GBrange 1.4%0no spread (3 reps)Detail for Spate RowBinary
Spate · RowBinaryclose
Why this number and not twice it
Binding constraint not yet stated. Rule 6 of the fairness contract requires this sentence and no descriptor currently carries it.
Configuration
rowbinary · threads 32 · shards 32 · inflight 4 · linger_ms 500 · max_rows 262144
Provenance
0.2.0 · 2026-08-26 · 3 reps · harness v2 · corpus d2-60d7e5bb2a82 · sha256:285c47a5618498972148b8a8c7b7d99566f1c1c4dbb1086897c0fccd9c4e51d9
Why this number is disowned
2864 sampler samples over 287.2s; headroom broker consume 16%, clickhouse ingest (rowbinary) 91%; INFRA-BOUND: above 70% of a measured ceiling, so this number describes the shared infrastructure rather than the system; server-side: 18366 insert(s) into default.sensor_events over 287.2s, 2940000000 rows written; excludes background merges; gate window 1145324 batches, quiesced at 2940000000 rows
Flags
reused infra
Other measurements
  • ClickHouse CPU 4306.23 s
  • ClickHouse CPU per written row 1.465 µs
  • ClickHouse CPU wait 3308.88 s
  • Bytes inserted 189.45 GB
  • Rows per insert 160k
  • Rows written server-side 2940.00M
  • GC pause max not measured
  • GC pause p99.9 not measured
  • GC pause total not measured
  • JVM heap committed peak not measured
  • JVM heap configured not measured
  • JVM heap live peak not measured
  • Ch merge duration 8354.32 s
  • Ch rows merged 3786.27M
  • Ch settle 2.00 s
  • Control plane gc pause max not measured
  • Control plane gc pause p999 not measured
  • Control plane gc pause p99 not measured
  • Control plane gc pause total not measured
  • Control plane jvm heap committed peak not measured
  • Control plane jvm heap configured not measured
  • Control plane jvm heap live peak not measured
  • Nr throttled not measured
  • Peak shmem not measured
  • Window resolution 0.00

Full profile for Spate — every arm, its configuration, and how to tell us we got it wrong.

Ranked positions are given only to realistic arms that passed the infrastructure-headroom limit. 1 arm is shown without one. Every column has its own scale and no scale is shared with any other group.

The systems

Who runs this

Spate is measured here, and Spate is mine. That is a conflict of interest, and the only useful response to one is to make it impossible to hide.

  • No published number is reported by the system that produced it. Throughput is SELECT count() against ClickHouse. CPU and memory are cgroup v2 counters read by a sidecar container. Correctness is a query against the rows that actually landed.
  • Every competitor configuration is in the repository, in full — and so is the search that chose it.
  • Where Spate loses, that is published with the same prominence as where it wins. A comparison containing only wins is read as marketing and convinces nobody.

If you think an arm is configured badly, that is a bug and I want the pull request.

How to read a number here

Every result carries the version of the system that produced it, the exact image digest, the environment it ran on, and the date. None of that is optional and none of it is typed in by hand — a run whose image digest cannot be read is recorded as failed rather than published.

Three things invalidate comparison outright, and this site refuses to draw records across them rather than quietly averaging:

If this differsThen
harness_versionThe measurement protocol changed. Not comparable.
dataset_versionThe corpus or schema changed. Not comparable.
env_idDifferent hardware. Not comparable.

Softer differences — a ClickHouse patch release, a compiler version — are recorded and shown as a footnote rather than treated as disqualifying.

Runs only ever append. There is no code path in the driver that truncates a results file, and re-running one system does not re-run or overwrite any other. A number later found to be wrong is corrected in a commit of its own, so what changed and why is in the repository's history.

The table shows each arm's most recent reading, so a re-measurement supersedes the one before it rather than sitting beside it and being ranked against it. That is a choice about display and not about retention: every sitting ever taken is still in results/, and nothing selected against here has been deleted. If an arm's latest sweep produced no number, it is listed as a gap rather than falling back to the last figure it managed.

What this does not tell you

Read the limitations before citing anything here. The short version: one workload, one machine, one shape of data, and a benchmark whose author has a stake in the outcome.