Benchmarks
The short version: on production traces with values above a few hundred bytes, Swytch beats Redis Cluster on throughput and tail latency at matched topology and matched durability settings, while moving one to four orders of magnitude less data over the network. On traces with tiny values (under ~200 bytes), Redis wins on raw speed: its per-key overhead is smaller than ours, and at that value size per-key overhead dominates total cost. The shape of your data decides which regime you’re in. Both are shown below.
Every number below comes from trace-bench, an internal harness (not yet open source) that replays real
production traces against Redis and Swytch in turn, on identical hardware, with identical operations in
identical order. Runs marked verified execute with --verify: every read is checked against the last
acknowledged write, and any wrong answer is counted. Traces are real production captures from the
CMU PDL twemcache workload dataset.
Beyond the usual throughput/latency/hit-rate numbers, the harness tracks two counters most benchmarks don’t:
- Invalid: a read returned a wrong value; something other than the last acknowledged write (or a legal concurrent write). This is a consistency violation.
- Vanished: a key was written, acknowledged, and later read back as a miss while the cache was under its memory budget. The data was silently dropped. This is a durability violation.
Both are zero for both systems on every run below: neither system dropped or corrupted data in these benchmarks. Behavior under failure injection (node kills, crash recovery) will be published separately.
trace-bench --calculate profiles a trace before running it. These four span the space:
| Trace | Source | Value p50 | Unique keys | Write ratio | Perfect hit rate | Live data | 50% of traffic from |
|---|---|---|---|---|---|---|---|
alibabaBlock_277 | Alibaba block I/O | 16 KB | 17.6k | 0.5% | 99.5% | 402 MB | 1.9% of keys |
cluster17 | 646 B | 75.4k | 2.3% | 97.7% | 31.8 MB | 0.14% of keys | |
cluster44 | 46 B | 1.65M | 2.7% | 97.4% | 65.3 MB | 0.08% of keys | |
w01 | CloudPhysics | 4 KB | 686k | 77% | 23.1% | 5.34 GB | 35% of keys |
“Perfect hit rate” is the ceiling any cache can reach on the trace: the fraction of reads whose key was ever written. Misses beyond that are requests for keys that don’t exist; no cache of any size can serve them. When both systems sit at this ceiling, neither is losing data.
The closest to a like-for-like comparison we can construct: both systems sharded across 4 nodes, both persistent (Swytch
Cloud
enabled; Redis appendfsync always), same trace, same 100-way concurrency, verified. ~650 million
operations over a 7-day cluster17 replay.

| Redis Cluster | Swytch | |
|---|---|---|
| Operations | 649.5M | 649.5M |
| Hit rate | 99.89% | 99.90% |
| Throughput | 188,519/s | 266,198/s (+41%) |
| GET p50 | 895.9 µs | 396.7 µs |
| GET p99 | 1.9 ms | 4.7 ms |
| SET p50 | 894.1 µs | 665.9 µs |
| SET p99 | 2.0 ms | 20.8 ms |
| Invalid / Vanished | 0 / 0 | 0 / 0 |
| Network total | 454.2 GB | 642.3 MB (47× less at peak) |
| Peak RSS | 231 MB | 2.15 GB |
| Peak CPU | 390% | 936% |
Swytch is 41% faster in aggregate and 2.3× faster at the median read, at matched topology and durability. The costs are equally real: Swytch’s SET tail is where synchronous replication and the cloud send path show up (p99 20.8 ms vs 2.0 ms), and it spends more CPU and RAM doing causal-consistency work Redis doesn’t do. The 47× network difference is architectural: Swytch replicates ~100-byte effect metadata, not values.
On Redis memory: the RSS above is what Redis used, not what it needs provisioned. AOF persistence
rewrites via fork(), which under write load can double resident memory (copy-on-write touches every
modified page). You must reserve roughly 2× Redis’s working RSS or persistence fails (see the w01 section
below for what happens when it can’t). Swytch persists by streaming an append-only effect log; there is no
fork and no 2× reservation.
Same 4-node sharded + cloud configuration, full traces end-to-end, no failure injection, verified.

| Redis | Swytch | |
|---|---|---|
| Throughput (steady state) | 165k/s | 256k/s (+55%) |
| GET p50 | 841 µs | 377 µs |
| Hit rate | 99.89% | 99.90% |
| Invalid / Vanished | 0 / 0 | 0 / 0 |
| Network (sustained) | ~980 Mbps | ~0 |

| Redis | Swytch | |
|---|---|---|
| Throughput (steady state) | 152k/s | 203k/s (+34%) |
| GET p50 | 1,525 µs | 720 µs |
| Hit rate | 99.926% | 99.925% |
| Invalid / Vanished | 0 / 0 | 0 / 0 |
| Network (sustained) | ~25 Gbps | ~0 |
Note the network line. Serving 16 KB values at 150k ops/s pushes 25 Gbps out of Redis, sustained, for two simulated weeks. If those bytes cross an availability-zone boundary, they’re a line item on your cloud bill. Swytch’s line is flat zero: values never cross the wire.

| Redis | Swytch | |
|---|---|---|
| Throughput | 211k/s | 173k/s (−18%) |
| GET p50 | 688 µs | 578 µs |
| Hit rate | 98.43% | 98.66% |
| Invalid / Vanished | 0 / 0 | 0 / 0 |
| Total RSS (4 nodes) | 0.73 GB | 18.9 GB |
At 46-byte values, Swytch’s per-key structures (causal DAG, key index, effect headers) are larger than the values they manage, and Redis’s minimal per-key overhead wins. This workload favors Redis’s design and the results reflect that. If your values are this small and this cheap to regenerate, a durable cache is likely the wrong tool entirely; the source of truth can answer faster than any cloud tier can. Swytch earns its overhead when the data is expensive to lose, expensive to fetch, or expensive to get wrong.
Saturation benchmarks measure ceilings. Nobody runs production at their ceiling. These runs pin both systems to an identical 100,000 ops/s and measure what each costs to deliver the same work: the number that actually matters for capacity planning.

| Redis | Swytch | |
|---|---|---|
| GET p50 | 35.3 µs | 51.1 µs |
| GET p95 | 521.8 µs | 193.9 µs |
| GET p99 | 1.07 ms | 24.1 ms |
| SET p50 | 44.1 µs | 94.2 µs |
| SET p99 | 1.05 ms | 679.5 µs |
| Peak CPU | 164% | 648% |
| Peak RSS | 124 MB | 606 MB |

| Redis | Swytch | |
|---|---|---|
| GET p50 | 42.5 µs | 58.0 µs |
| GET p95 | 313.6 µs | 198.7 µs |
| GET p99 | 1.32 ms | 1.10 ms |
| SET p95 | 2.21 ms | 1.28 ms |
| Network (sustained) | 15.9 Gbps | 0 |
| Peak CPU | 215% | 448% |

| Redis | Swytch | |
|---|---|---|
| GET p50 | 33.4 µs | 76.3 µs |
| GET p95 | 55.3 µs | 23.5 ms |
| GETs under 100 µs | 99.2% | 62.0% |
| Peak CPU | 178% | 908% |
The pattern across all three: at matched load, the median-latency gap nearly closes (35 vs 51 µs on cluster17; both are “fast”), Swytch wins the p95 mid-tail on medium and large values, and Swytch’s p99+ carries a cloud-fetch tail on the small fraction of reads that reconstruct from the durable tier. The bill is CPU: roughly 2–5× Redis’s, scaling inversely with value size. On an application server with idle cores, that’s capacity you already own. On tiny values, it’s the reason to use Redis instead.
Swytch’s latency distribution is bimodal by design: a read is either resident (memory-speed) or it reconstructs from cloud (bounded, ~10–25 ms). There is no smeared middle. Writes are the opposite story: Swytch appends an immutable effect and wins SET tails even while synchronously replicating, because there is no read-modify-write and no fsync stall on the write path. It’s a write-optimized store that happens to read at cache speed when the working set is resident.
CloudPhysics w01 is 77% writes, 686k unique keys, and 5.34 GB of live data: the most write-intensive trace in the set.
With appendfsync always, Redis did not complete the run. Under sustained write load, the AOF rewrite fork()
needed to double Redis’s resident memory; the box didn’t have it; Redis crashed, restarted, and came back
missing data: 39 acknowledged writes were gone (verified), 1.5M client errors during the outage, and a
cold cache afterward. The persistence mechanism caused the failure it exists to prevent. To an uptime
monitor this appears as a brief restart. The verifier recorded it as silent data loss.
With appendfsync everysec (and 1 Redis node vs 2 Swytch nodes at the same per-node RAM, because Redis
Cluster refuses to form with fewer than 3 masters):

| Redis (1 node, everysec) | Swytch (2 nodes, cloud) | |
|---|---|---|
| Hit rate | 23.60% | 23.71% (trace ceiling: 23.73%) |
| Invalid / Vanished | 0 / 0 | 0 / 0 |
| SET p99 | 1.53 ms | 1.10 ms |
| SET p99.9 | 46.4 ms | 31.7 ms |
| GET p50 | 51 µs | 25.2 ms |
| Peak RSS | 11.57 GB | 8.44 GB |
Two results stand out. First, Redis used more memory than Swytch; the one workload where memory pressure actually bites inverts the usual RSS comparison, before even counting the 2× fork reservation. Second, both systems sit exactly at the trace’s computed hit ceiling: every miss is a request for a key that was never written. Swytch’s 25 ms GET p50 is the cost of an undersized cache serving most reads from the durable tier. Size the cache to the working set and those reads return to microseconds. That’s the knob: perfect hit rate, paid for in tail latency, at whatever RAM budget you choose.
| Server | Hetzner dedicated, 64 GB RAM, 6 CPUs for the system under test |
| Topology | 4-node Swytch sharded + Swytch Cloud vs 4-node Redis Cluster, unless noted |
| Persistence | Swytch Cloud (durable tier) vs Redis AOF (always unless noted) |
| Concurrency | 100 workers |
| Replay | Sequential mode: each system sees identical operations in identical order |
| Verification | --verify: every read checked against last acknowledged write; invalid and vanished counted |
| Charts | Per-second samples, resampled onto uniform sim-time bins (median per bin; max per bin for spike series), final partial second dropped |
Sequential replay is the control that makes cross-system comparison fair, with one known artifact: a slow request briefly gates the trace behind it, so saturation throughput on cloud-heavy runs understates what a concurrent production workload would see. The throttled runs do not have this artifact: both systems comfortably deliver the pinned rate, so the measured latency and CPU reflect service cost rather than queueing.
The internal trace-bench harness isn’t open source yet, but Swytch ships with built-in micro-benchmarks
and speaks the standard tooling.
Everything in “The traces” table above comes from the calculator. When trace-bench is released you’ll be
able to profile your own captures the same way:
./trace-bench --trace your-trace.zst --calculate
It reports value-size distribution, unique keys, write ratio, the perfect-hit-rate ceiling, live data size (what you’d be billed for on Swytch Cloud), key hotness, and peak write rate: enough to predict which regime you’re in before running anything.
go test -bench=. -benchmem ./cache/
| Benchmark | Description |
|---|---|
BenchmarkCloxCacheGet | Parallel GET on 10k keys |
BenchmarkCloxCachePut | Parallel PUT operations |
BenchmarkCloxCacheMixed | 80% read / 20% write, reports hit rate and evictions |
BenchmarkCloxCacheZipf | Zipf distribution (theta=0.99), realistic hotspots |
BenchmarkCloxCacheContention | High contention on 100 hot keys |
BenchmarkCloxCacheSizes | Small → XLarge cache scaling |
BenchmarkCloxCachePointers | Pointer-type values |
Each has a sync.Map variant for comparison.
go test -bench=. -benchmem ./redis/
Exercises the full Redis command pipeline: parsing, execution, response writing. Covers SET/GET, INCR, list ops (LPUSH/RPUSH/LPOP/RPOP/LRANGE/LINDEX), hash ops (HSET/HGET/HGETALL/HINCRBY), mixed and parallel workloads, 10 KB values, and LPUSH scaling from 1k to 10M elements.
go test -bench=. -benchmem ./effects/
Benchmarks for the causal effect resolution layer.
Swytch is wire-compatible with the standard tool:
redis-benchmark -p 6379 -n 100000 -c 50
redis-benchmark -p 6379 -t set,get -n 1000000 -c 100
redis-benchmark -p 6379 -t set,get -n 1000000 -P 16
redis-benchmark -p 6379 -t set,get -d 256 -r 1000000
Real production traces used above are available from the CMU PDL twemcache workload dataset.