|
This version is still in development and is not considered stable yet. For the latest stable version, please use Korvet 0.19! |
Benchmarks
This page documents performance benchmarks for Korvet with Redis Enterprise as the storage backend.
If you are sizing a deployment, start with the pipeline saturation benchmark: it runs daily on a production-grade Kubernetes topology with dedicated node groups per component, ramps load until a component saturates, and reports which component is the bottleneck. The other benchmarks on this page were captured on developer or single-host setups and are best read as component-level characterizations.
Pipeline Saturation Benchmark (EKS)
This benchmark measures the peak sustainable produce throughput of a realistic production pipeline: Logstash producers sending JSON events over the Kafka protocol to a multi-broker Korvet deployment on EKS, backed by a Redis Enterprise cluster using the OSS Cluster API. It runs daily against the latest build; the results below are from the 2026-08-11 run.
Test Environment
| Component | Configuration |
|---|---|
Korvet brokers |
3 replicas (StatefulSet, one broker per node), 6 vCPU / 12 GB each, 6 GB heap, on m5.2xlarge nodes |
Redis Enterprise |
3 nodes (r6i.2xlarge, 7 vCPU / 52 GB per pod), 24 master shards, 120 GB database, OSS Cluster API, sparse shard placement |
Topic |
96 partitions (4× the shard count), LZ4 compression, Redis-only retention |
Producers |
Logstash with the Kafka output plugin, 1 pod per c5.xlarge node, ramped 1 → 16 replicas; |
Events |
~700-byte JSON log events, LZ4-compressed on the wire |
Each ramp step measures a 120-second window after a 60-second warm-up, collecting broker metrics (Prometheus actuator), Redis Enterprise per-shard/per-node CPU (REST API), and producer rates. A step is saturated when a component crosses its limit (CPU ≥ 90%, produce p99 > 250 ms, backpressure or connection-pool timeouts) or throughput plateaus.
Performance Results
Peak throughput: 642,352 msg/s (46.5 MB/s compressed) at 16 producers — bottleneck: Redis Enterprise shard CPU.
| Producers | msg/s | MB/s | p50 (ms) | p99 (ms) | Korvet CPU avg | RE max shard CPU |
|---|---|---|---|---|---|---|
1 |
74,506 |
5.5 |
2.1 |
5.4 |
8% |
4% |
2 |
150,043 |
11.1 |
2.3 |
6.4 |
16% |
8% |
4 |
286,401 |
21.3 |
2.8 |
9.1 |
32% |
13% |
6 |
409,528 |
30.3 |
3.6 |
13.8 |
48% |
23% |
9 |
528,756 |
39.1 |
5.5 |
24.7 |
65% |
24% |
12 |
597,657 |
43.9 |
7.7 |
37.6 |
74% |
28% |
16 |
642,352 |
46.5 |
10.1 |
59.0 |
79% |
92% |
Key Findings
-
Throughput scales near-linearly with load until saturation: 74.5k msg/s per producer at step 1, still 40k msg/s per producer at the 642k peak
-
Produce latency stays low well past half load: p99 under 25 ms up to ~530k msg/s; latency only climbs as the hottest shard approaches saturation
-
The bottleneck at peak is Redis Enterprise shard CPU (hottest shard at 92%), not the brokers (79% average) — adding shards and nodes raises the ceiling
-
A single broker ceilings at ~200k msg/s on internal contention regardless of CPU; 3 brokers measured ~2.6× a single broker, so scale brokers horizontally rather than vertically
-
Partitions must significantly exceed shard count: stream keys land on shards by slot hash, so equal counts leave some shards double-loaded and others empty; 4× (96 partitions on 24 shards) evens placement and spreads partition leadership across brokers
Deployment Sizing Guidance
Use the measured topology as a unit of capacity and scale each component by the reported bottleneck:
| Target sustained ingest | Korvet brokers | Redis Enterprise |
|---|---|---|
Up to ~150k msg/s |
1 broker (6 vCPU, 12 GB) |
6+ shards across 3 nodes |
Up to ~400k msg/s |
2–3 brokers |
12+ shards across 3 nodes |
Up to ~600k msg/s |
3 brokers |
24 shards across 3 nodes (the measured configuration) |
Beyond 600k msg/s |
+1 broker per ~200k msg/s |
Add shards and nodes; with the OSS Cluster API, throughput scales roughly linearly with shard count |
Additional sizing rules from this benchmark:
-
Partitions: provision at least 4× the shard count, and enough to spread leadership across all brokers.
-
Redis memory: size the database for
peak rate × retention window × average message size × replication factor, plus headroom for shard skew and trim-tick overshoot (the storage worker trims retention in ticks, so the working set carries up to one tick interval of extra data at high ingest). At 582k msg/s with 60 s retention and ~700-byte events, peak measured usage was ~51 GB. -
Producer batching: keep Kafka batch sizes small (16 KB); broker request service time grows superlinearly with request size — 256 KB batches measured ~2.4× slower than 16 KB.
-
Headroom: treat these numbers as a floor for the measured hardware; leave margin for consumer traffic, rebalances, and failover.
These figures cover produce-side ingest with Redis-only retention. Tiered (S3/Iceberg) offload sustains roughly an order of magnitude less throughput than saturation ingest — see the remote storage archival benchmark — so size retention and offload capacity separately if you enable remote storage.
Optimal Configuration Benchmark
This benchmark demonstrates the best throughput configuration for Korvet with Redis Enterprise.
CI benchmarks exercise both the netty (default) and reactor-netty transport stacks. The results shown here use the default netty stack. reactor-netty stack benchmarks are run nightly for client-facing workloads (Produce, Consume, GroupConsume) to track transport-layer performance across both pipelines.
|
Test Environment
-
Redis Enterprise: 16 shards, running locally
-
Korvet: Single instance (macOS, Apple Silicon)
-
Kafka Tools:
kafka-producer-perf-testfrom Apache Kafka -
Topic Configuration: 16 partitions (1× the number of shards)
-
Record Size: 1 KB (1024 bytes)
-
Total Messages: 8,000,000 (1,000,000 per producer)
Configuration
| Parameter | Value |
|---|---|
Producers |
8 |
Batch Size |
1000 messages (1.07 MB) |
Redis Connection Pool Size |
8 |
Acks |
1 |
Compression |
none |
Linger |
0ms |
Performance Results
| Metric | Value |
|---|---|
Aggregate Throughput |
380,952 records/sec |
Throughput (MB/sec) |
372.02 MB/sec |
Total Messages |
8,000,000 |
Duration |
21 seconds |
Average Latency (range) |
249-411 ms |
95th Percentile Latency (range) |
657-2136 ms |
Redis Enterprise Metrics
| Metric | Value |
|---|---|
Total CPU (all 16 shards) |
79% |
Per-Shard CPU |
2-8% |
Data per Shard |
112-172 MB |
Data Distribution |
Even across all shards |
Key Findings
-
High throughput with low CPU usage: Achieved 372 MB/sec with only 2.35% Korvet CPU usage
-
Excellent scalability headroom: Both Korvet and Redis Enterprise operating well below capacity
-
Even load distribution: Data and CPU load distributed evenly across all 16 Redis shards
-
Optimal batch size: 1000 messages per batch provided the best balance of throughput and latency
Running This Benchmark
To reproduce this benchmark, use the provided benchmark script from the korvet-dist repository:
git clone https://github.com/redis-field-engineering/korvet-dist.git
cd korvet-dist/samples/benchmark/scripts
./run-comprehensive-benchmark.sh
The script will:
-
Start Korvet with the specified Redis pool size
-
Create a topic with 16 partitions
-
Run 8 concurrent producers, each sending 1,000,000 messages
-
Collect metrics from Korvet (via actuator) and Redis Enterprise (via API)
-
Generate a detailed report with throughput, latency, and resource usage
Results are saved to /tmp/korvet-benchmark-<timestamp>/.
Single Shard Benchmark
This benchmark demonstrates Korvet performance with a single Redis shard, providing a baseline for comparison with multi-shard configurations.
Test Environment
-
Redis Enterprise: 1 shard (~1 GB maxmemory), running locally
-
Korvet: Single instance (macOS, Apple Silicon)
-
Kafka Tools:
kafka-producer-perf-testfrom Apache Kafka -
Topic Configuration: 1 partition (matching the single shard)
-
Record Size: 1 KB (1024 bytes)
Configuration
| Parameter | Value |
|---|---|
Producers |
1 (baseline) / 8 (concurrent) |
Batch Size |
1000 messages (1.07 MB) |
Redis Connection Pool Size |
16 |
Acks |
1 |
Compression |
none |
Linger |
0ms |
Performance Results
Comparison: 16 Shards vs 1 Shard
| Metric | 16 Shards | 1 Shard | Ratio |
|---|---|---|---|
Database Memory |
~16 GB |
~1 GB |
16× |
Topic Partitions |
16 |
1 |
16× |
Throughput (rec/s) |
380,952 |
168,641 |
2.26× |
Throughput (MB/s) |
372.02 |
164.68 |
2.26× |
Per-shard throughput |
23,809 |
168,641 |
0.14× |
Key Findings
-
Single shard achieves ~44% of 16-shard aggregate throughput: 168,641 vs 380,952 records/sec
-
Higher per-shard efficiency with fewer shards: A single shard processes 168,641 rec/s vs 23,809 rec/s per shard in the 16-shard setup
-
Memory efficiency: ~1.15 KB per message in Redis Streams (776 MB for 674,564 messages)
-
Single producer baseline: 151,860 rec/s provides a clean baseline without concurrency overhead
Remote Storage Archival Benchmark
This benchmark measures the throughput of archiving sealed Redis stream segments to Apache Iceberg tables on S3.
Test Environment
-
EC2 Instance: c5.2xlarge (8 vCPU, 16GB RAM) in us-west-1
-
S3 Bucket: Same region (us-west-1) for optimal network performance
-
Redis: Docker container on same instance
-
Message Size: ~100 bytes (binary payload)
-
Compression: Iceberg default Parquet compression
Single Stream Results
Archiving from a single Redis Stream to S3:
| Messages | Archive Time | Throughput | Parquet Files |
|---|---|---|---|
1,000,000 |
31.3s |
31,970 msg/s |
100 @ 186ms avg |
Multi-Stream Results (4 Partitions)
Archiving from 4 Redis Streams in parallel to S3:
| Messages | Archive Time | Throughput | Parquet Files |
|---|---|---|---|
1,000,000 |
12.5s |
80,239 msg/s |
100 @ 212ms avg |
4,000,000 |
34.7s |
115,347 msg/s |
400 @ 192ms avg |
Scaling Summary
| Configuration | Throughput | vs Single Stream |
|---|---|---|
1 stream |
32k msg/s |
baseline |
4 streams (1M messages) |
80k msg/s |
2.5× |
4 streams (4M messages) |
115k msg/s |
3.6× |
Key Findings
-
Single stream peaks at ~32k msg/s: Bottleneck is S3 PUT latency for Parquet files
-
Near-linear scaling with streams: 4 streams achieves 115k msg/s (3.6× single stream)
-
Parquet writes average ~190ms: Same-region S3 provides consistent low latency
-
Excellent compression: SNAPPY on this payload achieves ~50:1 compression ratio (~2 bytes/message stored)
-
Same-region S3 is critical: Cross-region throughput drops ~50%
Redis Flex (Auto-Tiering) Benchmark
This benchmark evaluates Korvet performance with Redis Flex (Auto-Tiering), which uses NVMe flash storage to extend Redis capacity beyond RAM.
Test Environment
-
Redis Enterprise: 1× i4i.xlarge (4 vCPU, 32GB RAM, 937GB NVMe)
-
Database Config: 100GB capacity, 10GB RAM (10% ratio), 8 shards
-
Korvet Client: c7i.4xlarge (16 vCPU, 32GB RAM)
-
Kafka Tools:
kafka-producer-perf-testfrom Apache Kafka -
Record Size: 1 KB (1024 bytes)
-
Region: us-west-2 (all instances in same VPC)
Test Configuration
| Parameter | Value |
|---|---|
Instance Type (Redis) |
i4i.xlarge (NVMe-backed) |
Instance Type (Client) |
c7i.4xlarge |
Shards |
8 (1.25GB RAM per shard) |
Redis Pool Size |
256 |
Producer Batch Size |
128KB ( |
Linger |
5ms ( |
Acks |
1 |
Performance Results
| Metric | Korvet → Redis Flex | Direct Redis (XADD) |
|---|---|---|
Peak Throughput |
150,784 rec/s (147 MB/s) |
130,690 rec/s (128 MB/s) |
Sustained Throughput |
110,000 rec/s (107 MB/s) |
103,000 rec/s (100 MB/s) |
Average Latency |
201 ms |
< 1 ms |
P99 Latency |
510 ms |
28 ms |
Data Structure Comparison
We compared Redis Streams (XADD) vs simple key-value (SET) operations on Redis Flex:
| Operation | Throughput | Notes |
|---|---|---|
SET (1KB values) |
153,000 ops/sec |
Simple key-value, flash-friendly |
XADD (Streams, 1KB payload) |
103,000 ops/sec |
Stream data structure overhead |
Korvet → XADD |
110-150k rec/sec |
Near-native XADD performance |
Key Findings
-
Korvet matches native Redis Streams performance: Korvet achieved 110-150k rec/sec, matching or exceeding direct XADD benchmarks
-
Flash eviction is the bottleneck for sustained writes: RAM fills faster than NVMe can drain at very high throughput
-
Larger RAM buffers help: 8 shards (1.25GB RAM/shard) outperformed 48 shards (208MB RAM/shard) by avoiding OOM errors
-
Client instance sizing matters: Upgraded from t3.medium (2 vCPU) to c7i.4xlarge (16 vCPU) to eliminate client-side bottleneck
OOM Behavior
At sustained throughput above ~150k rec/sec with 1KB payloads, Redis Flex may return OOM errors when the RAM buffer fills faster than flash eviction can drain. This is inherent to Redis Streams on flash storage, not specific to Korvet.
| Scenario | Throughput | Result |
|---|---|---|
Burst (1M records) |
150k rec/s |
✅ Success |
Sustained (2M+ records) |
150k rec/s |
⚠️ OOM after ~1.3M records |
Sustained (unlimited) |
110k rec/s |
✅ Success |
Mitigation: For sustained high-throughput workloads on Redis Flex:
-
Use fewer shards with larger RAM buffers (e.g., 8 shards vs 48)
-
Increase RAM-to-disk ratio (e.g., 15-20% instead of 10%)
-
Throttle producer throughput to ~100k rec/sec per instance
-
Use multiple Redis Flex clusters for horizontal scaling
Configuration Recommendations
For optimal throughput:
-
Batch size: Use 1000 messages per batch for best balance of throughput and latency
-
Producers: 8 concurrent producers provides excellent throughput with manageable latency
-
Redis pool size: Match pool size to number of producers (8) for optimal connection utilization
-
Partitions: Use 1-2× the number of Redis shards (16 partitions for 16 shards)
-
Redis shards: Match the number of shards to available CPU cores
-
Rebalance delay: Configure
korvet.broker.rebalance-delayappropriately (default 3s) to allow all consumers to join before rebalancing -
Replication: Disable replication for write-heavy workloads (if durability requirements allow); conversely, for workloads that need Kafka
acks=all-like guarantees, see the replication-acknowledged produce option in the durability model
Running Your Own Benchmarks
Using the Benchmark Script
The korvet-dist repository contains a script to run benchmarks with various configurations.
git clone https://github.com/redis-field-engineering/korvet-dist.git
cd korvet-dist/samples/benchmark/scripts
./run-comprehensive-benchmark.sh
Configuration Options
Edit the script to customize benchmark parameters:
# Test parameters
TOPIC="benchmark-test"
PARTITIONS=16
RECORD_SIZE=1024
NUM_RECORDS=1000000
# Parameter arrays
PRODUCERS=(8) # Number of concurrent producers
BATCH_SIZES=(1000) # Messages per batch
POOL_SIZES=(8) # Redis connection pool size
What the Script Does
-
Starts Korvet with the specified Redis pool size
-
Flushes Redis to ensure clean state
-
Creates topic with specified number of partitions
-
Runs producers using
kafka-producer-perf-test -
Collects metrics:
-
Korvet CPU and memory (via Spring Boot Actuator at port 8080)
-
Redis Enterprise CPU and memory (via REST API at port 9443)
-
Producer throughput and latency
-
-
Generates report with detailed results
Output
Results are saved to /tmp/korvet-benchmark-<timestamp>/:
-
SUMMARY.txt: Summary table of all test results -
producers-<N>_batch-<B>msg_pool-<P>.txt: Detailed results for each test
Example summary output:
Producers Batch(msg) Pool Total Msgs Duration(s) Throughput(rec/s) Throughput(MB/s) 8 1000 8 8000000 21 380952 372.02
Running Storage Tier Benchmarks
Korvet ships JUnit-based micro-benchmarks alongside its integration tests. These
run under the Gradle integrationTest task (not test) and use Testcontainers,
so they require a running Docker engine.
Tiered read coordinator benchmark
RedisTieredGroupReadCoordinatorBenchmark in the korvet-storage-tiered-redis
module measures group-read coordination throughput across a range of batch sizes
against a Redis container. It is gated behind the korvet.benchmark.pel system
property so it is skipped during normal test runs:
./gradlew :korvet-storage-tiered-redis:integrationTest \
--tests "RedisTieredGroupReadCoordinatorBenchmark" \
-Dkorvet.benchmark.pel=true
Segment-scale benchmark
SegmentScaleBenchmarkTest in the korvet-benchmark module measures performance
characteristics at worst-case segment counts. A partition holding a full 25 GB
Redis Enterprise shard at the default 128 MB segment.bytes reaches approximately
200 sealed segments. This benchmark deterministically builds a 200-segment manifest
(one sealed segment per storage worker tick) and records trend scenarios for:
-
Produce send-to-ack latency: End-to-end produce performance with a large segment manifest
-
ListOffsets bounds resolution: Time to resolve earliest and latest offsets across 200 segments
-
Consume-from-earliest throughput: Standalone consumer reading through the entire partition
Each build round writes 96 records of 1 KiB per partition — comfortably above the
test topic’s 64 KiB segment.bytes budget — then waits for the storage worker tick
to seal the over-budget segment, so each round yields exactly one sealed segment
per partition. Results are keyed by segment count
(e.g., …/s200) for trend tracking across releases.
This pillar is included in the nightly benchmark run and skipped in baseline/reactor mode like other worker-dependent benchmarks. To run it manually:
./gradlew :korvet-benchmark:benchmark \
--tests "*.e2e.SegmentScaleBenchmarkTest" \
-Dkorvet.benchmark.segmentsPerPartition=200
The segment count is configurable via korvet.benchmark.segmentsPerPartition
(default 200). Lower values (e.g., 50) complete faster for smoke testing; higher
values stress-test metadata and offset resolution at extreme scale.
The remote/S3 archival and end-to-end remote-read numbers reported above
were produced with purpose-built harnesses on EC2. The reusable benchmark
scaffolding for capturing results — BenchmarkConfig, ScenarioResult, and
BenchmarkResult — lives in korvet-server/src/integrationTest under
com.redis.korvet.benchmark. A packaged, repeatable S3/remote-read benchmark
entry point is planned; the throughput figures in this page should be treated as
illustrative of what the storage tier can achieve rather than as a turnkey test
you can run as-is.
|
Result scaffolding
The BenchmarkResult type captures version, Git commit, and environment metadata
alongside per-scenario ScenarioResult entries, which makes results
comparable across releases. A captured result is shaped like this:
{
"korvetVersion": "0.5.0-ea1",
"timestamp": "2026-03-30T17:30:00Z",
"gitCommit": "abc1234",
"environment": {
"javaVersion": "25",
"osName": "Linux",
"availableProcessors": 4,
"maxMemoryMb": 4096
},
"config": {
"messageCount": 10000,
"recordSizeBytes": 1024,
"partitions": 1,
"iterations": 3,
"batchSizes": [100, 500, 1000]
},
"scenarios": [
{
"type": "STANDALONE_CONSUMER",
"batchSize": 100,
"avgLatencyMs": 250,
"p95LatencyMs": 320,
"throughputMsgPerSec": 400.0
}
]
}
Interpreting Results
-
Standalone vs Consumer Group overhead: Consumer group reads include additional coordination (JoinGroup, SyncGroup, OffsetFetch) which adds latency
-
ListOffsets Earliest latency: High values indicate slow remote-tier metadata lookups (Parquet footer reads against the manifest’s oldest REMOTE segment)
-
Throughput scaling: If throughput doesn’t scale linearly with batch size, there may be per-request overhead dominating
-
P95 vs Avg: Large gaps indicate object-store tail latencies or GC pauses
Timeout Recommendations
Based on production experience with S3-backed remote storage, configure consumer timeouts appropriately:
// For remote storage reads, increase timeouts
props.put(ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, "60000"); // 60s
props.put(ConsumerConfig.DEFAULT_API_TIMEOUT_MS_CONFIG, "120000"); // 2min
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, "30000"); // 30s