Kafka consume fast path¶
Applies to: the native Kafka source's consume path
The finding¶
Per-thread profiling showed librdkafka's delivery thread, not the app thread, capped native consume ~30% below the Java client. Its top non-I/O costs were per-message bookkeeping that the JVM sidesteps via TLAB + bulk GC and CRC intrinsics.
Three compounding levers¶
Together these took 10M-msg raw consume from 3.33M/s (0.73x the Java client) to 5.34M/s (1.21x):
- mimalloc symbol-aliasing — the opt-in
mimalloccargo feature link-aliases the libc allocation symbols to mimalloc inside the library only, covering librdkafka's per-message op calloc (broker thread) + free (app thread), which no Rust#[global_allocator]can reach, and the Rust side's own allocator churn — with no process-wide override. check.crcs=false— now follows librdkafka's own default; its software CRC32C on ARM (no hardware path outside x86 SSE4.2) taxed the delivery thread ~13.5% (+22%).- Bulk-drain via
rd_kafka_consume_callback_queue— bulk-moves the backlog under one queue lock instead of locking per message against the enqueuing broker thread (+11%), withmax_recordsenforced viard_kafka_yield.
Downstream effects¶
The split reader's background decode thread is gone: with consume this fast, inline decode won on
every format (Flink already pipelines fetcher vs. task thread). The reader also now primes broker
metadata before assign() — a cold assign otherwise parks partitions in leader-query for ~0.5s
until the periodic refresh.
Measured¶
Net: the end-to-end Nexmark Kafka ladder's source rung runs at 2.2–3.4x stock Flink with the
mimalloc build (JSON 2.20–2.26x, Avro 2.99–3.38x, protobuf 2.29–2.36x; ~2–2.6x on the default
build).