Skip to content

Benchmarks

How the Recached server compares to Redis 7.2.5 and Valkey 9.1.0 under redis-benchmark, measured July 2026 on Recached v0.1.8.

TL;DR

Pipelined (-P 16), Recached sustains 408k–546k requests/sec — ahead of Redis on 6 of 7 commands and ahead of Valkey on all 7, on the same 4-core machine. Unpipelined — one command per round-trip, the traffic shape of typical application cache calls — Recached runs at 46–96% of Redis with sub-millisecond p50 latency on every common command.

Recached's design goal is not to beat Redis at raw server throughput — it is to remove the network round-trip entirely for browser reads, which no server-side cache can do. These numbers cover the server half (server-native) so you know what to expect when you point existing Redis clients at it.

Environment

HardwareIntel Core i5-8259U (4 cores / 8 threads, 2.3 GHz), 8 GB RAM
OSmacOS (Darwin 24.6.0)
Recachedv0.1.8, cargo build --release (thin LTO, jemalloc)
Redis7.2.5 (Homebrew)
Valkey9.1.0 (Homebrew)
Load generatorredis-benchmark from Redis 7.2.5

Methodology:

  • Servers ran one at a time on localhost, with the load generator on the same machine.
  • Persistence disabled everywhere: RECACHED_SAVE_INTERVAL=0 for Recached; --save '' --appendonly no for Redis and Valkey.
  • Each server got a 10k-request warm-up, then FLUSHDB, then the measured run: 100,000 requests, 50 parallel connections, 64-byte values, randomized keys (-n 100000 -c 50 -d 64 -r 100000), tests back-to-back in suite order.
  • Reproduce with scripts/benchmark.sh.

One caveat: this is a 4-core laptop and the benchmark tool competes with the servers for cores — absolute numbers on server hardware will be higher for all three systems. Recached's multi-threaded runtime has the most headroom to gain from more cores; Redis and Valkey process commands on a single thread.

Pipelined (-P 16)

Pipelining batches 16 commands per round-trip, measuring raw server-side command throughput rather than round-trip handling. Requests per second; bold marks the best result per row.

CommandRecached rpsRedis rpsValkey rps
SET421,941375,940294,118
GET546,448512,821483,092
INCR448,430421,941413,223
LPUSH473,934409,836386,100
SADD421,941462,963378,788
HSET408,163324,675287,356
ZADD414,938197,628221,239

This is where multi-threading pays: Recached spreads 50 connections across all cores, while Redis and Valkey execute commands on one. p50 latency stays around 0.7–1.0 ms and p99 under 5.1 ms across the suite.

These numbers are new in v0.1.8. In v0.1.7, pipelined throughput collapsed after the first test of a run (INCR 13.5k, LPUSH 9.8k rps, with multi-second stalls). Profiling traced it to per-command costs that deep pipelines amplify — chiefly per-op metrics-registry lookups whose global-recorder contention across 8 worker threads caused the decay and the stalls, plus a full Command clone per execution and a fresh allocation per response. v0.1.8 caches the counter handles, moves the command instead of cloning it (cloning only when a WebSocket peer, replica, AOF, or watched key actually consumes the write), and serializes responses into a reused per-connection buffer.

No pipelining (one command per round-trip)

Requests per second; p50/p99 latency in milliseconds.

CommandRecached rpsRecached p50 / p99Redis rpsRedis p50 / p99Valkey rpsValkey p50 / p99
SET51,7060.46 / 1.0657,1100.46 / 1.0256,6570.46 / 0.97
GET58,0720.44 / 0.6661,5760.42 / 0.6460,5690.42 / 0.84
INCR52,5490.46 / 1.0561,8050.42 / 0.6059,7370.43 / 0.87
LPUSH49,1160.52 / 0.8461,8430.42 / 0.5561,4250.43 / 0.70
RPOP45,9140.46 / 1.6462,5000.42 / 0.6163,4520.42 / 0.55
SADD51,6260.44 / 1.5362,2280.42 / 0.5462,4220.42 / 0.59
HSET28,1450.74 / 5.2761,5760.43 / 0.6362,5390.43 / 0.62
SPOP33,7720.63 / 4.7162,9720.42 / 0.5863,6540.43 / 0.88
ZADD36,0100.54 / 4.4662,1890.43 / 0.6562,4220.44 / 0.77
MSET (10 keys)34,4590.66 / 2.6735,8171.16 / 2.2240,9331.04 / 1.58
LRANGE_10011,6631.96 / 7.6918,4061.34 / 1.7518,5671.34 / 1.63
LRANGE_3005,2613.55 / 10.517,4582.81 / 5.037,5412.74 / 4.78
LRANGE_5003,4573.43 / 8.874,3933.70 / 6.814,4173.70 / 6.63
LRANGE_6003,1723.58 / 8.223,7374.37 / 8.193,8054.30 / 8.05

Unpipelined, the localhost round-trip dominates and single-command latency decides the table: strings, counters, lists and sets run at 74–96% of Redis; HSET, SPOP and ZADD trail at 46–58%. Everything stays under 0.75 ms at p50.

SPOP on large sets — fixed in v0.1.8

In v0.1.7, SPOP selected random members by iterating and cloning the entire set — O(n) per pop — which collapsed to 823 rps against the ~100k-member set this suite builds. v0.1.8 backs sets with an index-addressable structure (IndexSet), making SPOP/SRANDMEMBER O(1) per member: the same large-set workload now runs at ~22,000 rps, in line with the other set commands.

What's still on the list

  • HSET at P1 is the biggest remaining outlier (46% of Redis despite beating Redis pipelined) — single-command hash-write latency deserves its own investigation.
  • RESP parsing allocates a String per argument. Moving commands to byte-slice arguments is the deepest remaining refactor and the main lever left for unpipelined latency.
  • LRANGE builds the full reply Value before serializing; serializing straight from the store would cut the remaining gap on large range reads.

Reproducing

bash
# Build the server
cargo build --release -p server-native

# Terminal 1 — recached, persistence off
RECACHED_BIND=127.0.0.1 RECACHED_SAVE_INTERVAL=0 ./target/release/server-native

# Terminal 2 — run the suite (needs redis-benchmark on PATH)
scripts/benchmark.sh

# Then stop recached and repeat against Redis / Valkey:
redis-server --port 6390 --bind 127.0.0.1 --save '' --appendonly no
PORT=6390 scripts/benchmark.sh

Benchmark results from other hardware — especially many-core servers, where the multi-threaded runtime has the most to gain — are very welcome. Open an issue or PR with your --csv output and machine details.

Released under the MIT License.