Benchmarks
Measured on an Apple M5 (10 cores), loopback, with redis-benchmark -n 100000 -c 50.
| Scenario | SET QPS | GET QPS | p50 Latency |
|---|---|---|---|
| Baseline (no eviction) | 62,189 | 65,231 | 0.42ms |
Pipelined -P 16 | 350,877 | 378,787 | 1.06ms |
| LFU (16MB cap) | 57,339 | 61,690 | 0.42ms |
| AHE (16MB cap) | 59,559 | 50,787 | 0.42ms |
Reading the numbers
- Pipelining is the biggest lever — batching commands cuts per-request overhead and multiplies throughput.
- QPS and latency only tell you how fast the engine serves requests. They say nothing about what an eviction algorithm is actually for: keeping the working set cached. The table below closes that gap.
Hit ratio by eviction policy
This is the metric a cache eviction algorithm is judged on, and the one the raw QPS numbers cannot show: under a realistic access pattern, how much of the working set stays cached. Measured end-to-end against a live server (the same method as the QPS runs above) with a working set of 100,000 distinct keys and a cache capped at 5,000 entries (~590 KiB) — i.e. the cache holds only 1/20th of the keyspace, so eviction is under constant pressure.
| Policy | zipf (stable skew) | shift (rotating hot set) | mixed (hot set + scan) | scan (pure sequential) |
|---|---|---|---|---|
allkeys-lru | 59.5% | 52.4% | 56.3% | 0.0% |
allkeys-lfu | 59.4% | 51.1% | 58.0% | 0.0% |
allkeys-ahe | 59.5% | 52.3% | 56.8% | 0.0% |
allkeys-random | 57.1% | 52.4% | 54.5% | 0.0% |
Higher is better. Each cell replays the same seeded workload against a fresh server pinned to that policy; the value is the server's own keyspace_hits / (keyspace_hits + keyspace_misses) over the whole run (cold start included). Reproduce with scripts/bench-hit-ratio.sh; the harness lives in examples/hit_ratio_bench.rs.
Methodology
- Read-through client: a
GETmiss populates the key with aSET, exactly like a real cache fill. Eviction only engages once the cache is full, which is the regime that matters. - Realistic inter-request spacing (≈1 ms). FerrumKV's LRU and AHE are time-aware: recency is measured against a 600-second horizon and the AHE controller feeds on the observed hit ratio. A tight in-process loop finishes in milliseconds, flattening every recency signal and disabling the adaptive loop — so AHE would silently collapse to plain LFU and the comparison would be meaningless. Driving a live server with real spacing lets recency and the adaptive loop behave exactly as they do in production.
- One fresh server per
(policy, pattern)so counters never leak across workloads. - Patterns:
zipf(stable Zipfian skew — the easy case),shift(the hot band rotates every epoch — a non-stationary workload),mixed(a small stable hot set plus a periodic full scan — OLTP-like), andscan(pure sequential — the honest "every policy is hopeless here" control). A fifth pattern,ttl, is also available (--patterns ttl) and mixes a durable hot set with a short-TTL ephemeral set to exercise TTL-aware eviction; in our measurements it converges with LRU/LFU at realistic cache sizes and does not reliably beat LFU at very tight caches, so it is opt-in rather than part of the headline matrix above. - Single representative run; figures vary ~±1 pp across runs because the engine's internal LFU/LRU sampling RNG is seeded from the wall clock.
What the numbers say
- Stable skew (
zipf): every policy converges to ~59%; AHE matches the leaders. - Rotating hot set (
shift): LFU's sticky frequency counters collapse to 51.1% while LRU and AHE hold at ~52.3–52.4% — AHE tracks LRU and avoids LFU's worst case. - Mixed (
mixed): LFU leads (58.0%) and LRU dips to 56.3%; AHE sits at 56.8% — near LRU, well clear ofrandom's 54.5% floor. - Pure scan (
scan): every policy is pinned at ~0%, confirming AHE does not magic away a hopeless access pattern.
The takeaway: AHE is the no-regret choice. On each workload it tracks the better of LRU and LFU, and it never suffers either policy's worst-case collapse (LFU on a shifting hot set, LRU under a scan-heavy mix). That adaptivity — not a fixed bias toward recency or frequency — is the point of the algorithm.
The full QPS methodology and raw output live in benches/redis-benchmark.md.