Posts Caches
Redis vs Valkey vs Dragonfly vs KiviDB
A week after the last run, the same 64-core Graviton3 with KiviDB 1.2.0, which reworked how it stores a key, and Dragonfly 2.1. Memory adds random-byte values, the next million keys and each engine's total RSS beside the ranked number.
KiviDB 1.2.0 cut its memory per key by 44% and came within 8 bytes a key of Dragonfly 2.1, which kept the p99 and memory sections, while KiviDB kept pipelining by 31% and out-served Dragonfly on small values in every pass.
Small value throughput
SET/GET mix (1:10 ratio) with 100-byte values, pipeline depth 1. RESP2, no TLS.
Dragonfly 1.42M ops/s at the lowest p99 (0.41 ms); KiviDB 1.51M (0.45 ms), the most ops/s on every pass; Valkey 177K (2.10 ms); Redis 176K (2.11 ms)
Test 1 Small value throughput
The mean of each candidate's samples: the statistic the scorecard ranks this section by.
Mean of 10 passes per candidate; the whisker spans the fastest and slowest pass.
SET/GET mix (1:10) at pipeline depth 1 with 100-byte values, 200 connections. The section ranks on p99, and Dragonfly's mean was 41 µs lower than KiviDB's, right at the noise threshold: twice the standard error of that difference is also 41 µs, with KiviDB's p99 swinging from 0.311 to 0.527 ms across passes and Dragonfly's from 0.383 to 0.439. KiviDB's throughput lead is well outside its noise: its slowest pass, 1.49M ops/s, beat Dragonfly's fastest, 1.43M. Valkey and Redis were within 1% of each other at about an eighth of that throughput, and client CPU stayed under 35% on every pass.
Large value throughput
SET/GET mix (1:10 ratio) with 10 KB values, pipeline depth 1. RESP2, no TLS.
Dragonfly 398K ops/s at the lowest p99 (0.83 ms); KiviDB 398K (0.99 ms), both at the network ceiling; Valkey 107K (3.53 ms); Redis 106K (3.56 ms)
Test 1 Large value throughput
The mean of each candidate's samples: the statistic the scorecard ranks this section by.
Mean of 10 passes per candidate; the whisker spans the fastest and slowest pass.
SET/GET mix (1:10) at pipeline depth 1 with 10 KB values, 200 connections. Dragonfly and KiviDB delivered 397,518 and 397,525 ops/s, 29.6 Gbit/s of values into the client against the instances' 30 Gbit/s rating, so throughput can't separate them. Dragonfly's p99 was the steadier, 0.743 to 0.919 ms across passes against KiviDB's 0.751 to 1.311, and its 0.16 ms lead in the mean is outside the noise. Both ran 3.7 times the throughput of Redis and Valkey at about a quarter of their p99, and Redis and Valkey were within 1% of each other.
Pipeline effect
SET/GET mix (1:10 ratio) with 100-byte values, comparing pipeline depth 1 vs 30. Candidate statistic: pipeline-30 throughput.
KiviDB 22.4M ops/s at pipeline 30, a floor with the client at 72% CPU; Dragonfly 17.1M; Redis 1.48M; Valkey 1.37M
Test 1 Pipeline depth 1
SET/GET mix at pipeline depth 1 (reuses small-value throughput data)
Median of 10 passes per candidate; the whisker spans the lowest and highest pass.
Test 2 Pipeline depth 30
SET/GET mix at pipeline depth 30
Median of 10 passes per candidate; the whisker spans the lowest and highest pass.
SET/GET mix (1:10) with 100-byte values at pipeline depth 30, 200 connections. KiviDB's slowest pass, 20.9M ops/s, beat Dragonfly's fastest, 17.3M, and its median led by 31%. That number is a floor: the client averaged 72% CPU in KiviDB's cell (70 to 73% per pass), where 50 memtier threads can keep at most 50 of its 64 vCPUs busy, and KiviDB's passes ran faster when the client got more CPU (r = 0.91 over ten passes). Dragonfly's cell ran the client at 34%. Pipelining raised throughput 14.9 times for KiviDB, 12.1 for Dragonfly, 8.4 for Redis and 7.7 for Valkey, and Redis led Valkey by 8%.
Memory efficiency
Baseline-corrected memory after loading 1M keys with 100-byte values into a fresh engine. RSS delta is the primary metric (allocator-neutral). Candidate statistic: memtier's default values, 100 ASCII bytes; a second load of random bytes shows what packing them is worth. Two more views of the ASCII cell: the next million keys' own RSS, which no startup allocation absorbs, and the absolute RSS after the first million.
Dragonfly 135 MB RSS delta (142 B/key); KiviDB 142 MB (149 B/key); Redis 158 MB (166 B/key); Valkey 163 MB (171 B/key)
Test 1 ASCII values
1M keys with memtier's default 100-byte values, all ASCII, into a fresh engine
One measurement per candidate.
Test 2 Random bytes
1M keys with 100-byte values of random bytes, which no engine packs, into a fresh engine
One measurement per candidate.
Test 3 ASCII values, next million
The same load again on the next 1M keys: RSS over the first million's, which the segments and pages an engine allocates at startup can no longer absorb
One measurement per candidate.
Test 4 ASCII values, RSS after 1M keys
The engine's whole RSS after the first million keys, idle overhead included: what a container limit sees
One measurement per candidate.
1,000,000 keys with 100-byte values loaded into a fresh engine; the ranked statistic is the used_memory_rss delta over the empty engine's baseline, on memtier's ASCII values. Dragonfly used 5% less than KiviDB, 15% less than Redis and 17% less than Valkey, and KiviDB used 10% less than Redis and 13% less than Valkey. The delta leaves out what an engine allocates before the first key: Dragonfly's idle RSS was 280 MB at 64 threads, against 12 to 24 MB for the other three, so after the first million its total RSS was 415 MB to KiviDB's 161, Valkey's 175 and Redis's 182. The next million cost Dragonfly 135 bytes per key of RSS and KiviDB 146, and by their own used_memory 147 each. With random bytes, which Dragonfly can't pack 7 bits a byte, its used_memory per key rose from 130 to 146, level with KiviDB's 147.
Test conditions: rig, corpus, protocol
- Machine
- AWS c7g.metal (engine) + c7g.16xlarge (client)
- Chip
- AWS Graviton3
- Cores
- 64 physical (engine)
- Memory
- 128 GiB (engine), 128 GiB (client)
- OS
- Ubuntu 24.04 LTS arm64 (arm64)
- Runtime
- Docker (redis:8.10.3, valkey/valkey:9.1.3, dragonflydb/dragonfly:v2.1.0, kividb/kividb:v1.2.0) with Docker's default seccomp profile plus the io_uring syscalls and memlock unlimited; memtier_benchmark 2.5.1; RESP2, no TLS
- Warmups
- 2 unmeasured passes
- Measured
- 10 passes per tool
- Process
- Container per engine, network_mode host, seccomp: Docker's default profile plus io_uring_setup, io_uring_enter and io_uring_register, memlock unlimited: Dragonfly 64 proactor threads and KiviDB 62 worker threads (its default, cores − 2), both on io_uring, on 64 physical cores; Redis and Valkey single-threaded I/O (default io-threads 1); 16 GiB maxmemory for all four
- Cache
- Preloaded keyspace, 100% hit ratio on GET workloads; working sets sized below 50% of maxmemory; eviction flags set for parity of intent, never triggered
- Output
- memtier_benchmark 2.5.1: ops/s, p50/p99/p99.9 latency, client CPU per 10 s pass; memory via INFO MEMORY: baseline-corrected used_memory and used_memory_rss (process RSS) deltas for the first and second million keys, the absolute RSS after the first million, and random-byte values on a second fresh engine
What did we learn?
KiviDB 1.2.0 cut its memory per key by 44% and came within 8 bytes a key of Dragonfly 2.1, which kept the p99 and memory sections, while KiviDB kept pipelining by 31% and out-served Dragonfly on small values in every pass.
| Candidate | Small value throughput (ms, lower is better) | Large value throughput (ms, lower is better) | Pipeline effect (M ops/s, higher is better) | Memory efficiency (MB, lower is better) |
|---|---|---|---|---|
| Redis | 2.11 | 3.56 | 1.48 | 158 |
| Valkey | 2.10 | 3.53 | 1.37 | 163 |
| Dragonfly | 0.41 | 0.83 | 17.09 | 135 |
| KiviDB | 0.45 | 0.99 | 22.38 | 142 |
KiviDB 1.2.0 keeps each key and its value in one block behind an 8-byte slot, and its RSS per key fell from 267 bytes to 149, under Redis's 166 and Valkey's 171. Dragonfly 2.1 measured 142 on the ranked number, the RSS a fresh engine adds for its first million keys, which leaves out what it allocates before the first: Dragonfly idled at 280 MB at 64 threads to KiviDB's 18, so after a million keys KiviDB's RSS was 161 MB in all and Dragonfly's 415. By their own used_memory, the two cost the same for random bytes and for the next million, about 147 bytes a key; Dragonfly's lower 130 on memtier's ASCII values comes from packing them 7 bits a byte. KiviDB's throughput stayed within about 3% of 1.1.2's. It served 7% more 100-byte requests than Dragonfly, its slowest pass above Dragonfly's fastest, and 31% more at pipeline depth 30, a floor with the client at 72% CPU, and Dragonfly's p99 led at both value sizes, at 100 bytes by 41 µs, right at the noise threshold. Medians of ten passes; the table shows means.
What this does not prove
- KiviDB's pipeline-30 cell ran the client at 70 to 73% CPU, where 50 memtier threads can keep at most 50 of its 64 vCPUs busy, and its passes ran faster when the client got more CPU, so its 22.4M ops/s is a floor.
- At 10 KB, Dragonfly and KiviDB both delivered 398K ops/s, 29.6 Gbit/s of values into the client against the instances' 30 Gbit/s rating, so that section's throughput measures the network.
- Latency is closed-loop at 200 connections and measured at the client, so each engine's p99 comes at its own throughput. KiviDB's 100-byte p99 swung from 0.31 to 0.53 ms between passes, and Dragonfly's 41 µs lead in the mean sits right at the noise threshold, so the mark on that chart could go either way on another run.
- The memory section ranks on the RSS a fresh engine adds for its first million keys, which leaves out what it allocates before the first. Dragonfly 2.1 idled at 280 MB at 64 threads, up from 112 MB in 2.0.2, and its first-million delta rose from 118 to 142 bytes per key while its used_memory stayed at 130. The section also charts the next million's delta, random bytes and the total RSS after a million keys.
- memtier's default value is 100 ASCII bytes, which Dragonfly packs 7 bits a byte. By its used_memory, packing saves it 16 bytes a key, the gap between its ASCII and random-byte cells; KiviDB, Redis and Valkey store both the same.
- Against the 2026-10-29 post: the same rig, AMI and client sizing on a different pair of instances. Redis and Valkey, which didn't change, served 0.2 to 2.8% fewer ops/s than then, and KiviDB's throughput stayed within about 3% between 1.1.2 and 1.2.0.
- Docker's default seccomp profile blocks io_uring, so this run allowed io_uring_setup, io_uring_enter and io_uring_register on top of it, as the 2026-10-29 run did. That post's second run shows what the stock profile cost KiviDB 1.1.2; this run didn't repeat it for 1.2.0.
- Dragonfly ran 64 proactor threads, its default on 64 cores, and KiviDB its default 62 worker threads (cores minus two), both on io_uring, read from their startup logs. Redis and Valkey ran their default single-threaded I/O (io-threads 1); turning on their I/O threads is a different benchmark.
- Persistence was off for all four. For KiviDB that's its default: no AOF and no snapshot schedule.
- KiviDB 1.2.0 turns active defrag on by default, and it was left on, as shipped. KiviDB also checks GitHub for a newer release at startup, before it listens; that was left on too.
- The official Redis 8.10.3 image loads its bundled Search, JSON, TimeSeries and Bloom modules, and they were left on as shipped.
- Dragonfly 2.1 is the first release under the Dragonfly Source-Available License, which needs a commercial license past 100 vCPUs of production use across all instances. A benchmark isn't production use, and the engine host has 64.
- The rig was one c7g.metal engine host and one c7g.16xlarge client in a single cluster placement group in us-east-1, speaking RESP2 without TLS. maxmemory was 16 GiB for all four, and eviction never triggered: working sets stay at or below half of it.
Rerun it yourself: the runner and configuration are in the repository, and the methodology page covers what every run holds constant. Think a result is wrong? Open an issue with your rig and your samples.
Redis is a registered trademark of Redis Ltd. Valkey is a trademark of The Linux Foundation. Dragonfly is a trademark of DragonflyDB Ltd. KiviDB is a trademark of KIVIDB PRIVATE LIMITED.