Skip to content
Pyrula

Benchmarks

pyrula.kafka against the common Python Kafka clients — confluent-kafka (librdkafka), aiokafka, and kafka-python. Reproducible from benchmarks/kafka/benchmark.py.

100,000 records, 1 KB payloads. Higher is better (rec/s).

Suitepyrulaconfluentaiokafkakafka-pythonvs confluent
Produce bytes159k115k93k6.1k1.38x
Produce JSON142k124k91k5.5k1.15x
Transactional produce89k78kn/an/a1.14x
Produce Avro151k138kn/an/a1.09x
Consume records147k149k117k6.3k0.99x
Consume raw bytes147k147kn/an/a1.00x
Consume + JSON decode132k130k85k5.7k1.01x
Consume Avro141k34kn/an/a4.18x

n/a = not separately benchmarked, not necessarily unsupported: kafka-python has no transaction API; aiokafka transactions and Avro-via-fastavro exist but were not measured; raw-bytes consume for both is the same path as record consume.

Produce bytes Produce JSON Transactional produce Produce Avro

Bulk produce_many_* batches across partitions and serializes in Rust (no per-record Python json.dumps). For Avro, use the fused produce_many_avro so the Rust encode overlaps the broker send.

Consume records Consume raw bytes Consume + JSON decode Consume Avro

Parity with confluent-kafka on byte and JSON consume. Avro is the standout: pyrula deframes and decodes the whole batch in parallel Rust (AvroRegistrySerde.deserialize_many_objects) against a per-record fastavro loop.

  • Apple M1 Max, single local cp-kafka broker, 12 partitions, no replication.
  • 100,000 records, 1 KB payloads. Topics pre-loaded; consumers use manual assign() and seek to zero between runs (no group-join in the measured path).
  • One warmup, best of three. A prime step consumes the first record before the clock so cold fetch latency is not billed. Same treatment for every client.
  • confluent-kafka 2.x, aiokafka 0.14, kafka-python 2.3, fastavro 1.x.
Terminal window
docker compose up -d kafka schema-registry
python benchmarks/kafka/benchmark.py \
--broker localhost:9092 --schema-registry http://localhost:8081 \
--suites P1,P2,P3,C1,C2,C3,A1,A2 --sizes 100000 --payload-bytes 1024 \
--partitions 12 --fetch-mode per_broker

Localhost removes the network; over a real link produce and consume become bandwidth and round-trip bound. The durable pyrula advantages are where Rust does real work: bulk serde, Avro, transactions, and JSON.