Bekzod Erkinov
Listen to Article
Loading...Table of contents · 4 sections
Redis vs Memcached Benchmark: Throughput and Memory Measured
This Redis vs Memcached benchmark answers one question: for your value size, which cache costs less memory, and does the faster one on paper actually win on a small box? On one shared i7-8550U laptop under Docker Desktop, throughput did not separate them, and memory flipped with value size: Memcached used 27% more per key at 64 B and 11-12% less from 1 KB up. Memcached -t 4 also showed about a third of Redis's median GET latency (~0.6 vs ~1.9 ms), but that is latency under CPU contention in a VM, so the absolute values are inflated and only the ratio is worth noting.
The numbers below come from one Docker setup on one noisy laptop, so each table shows the run-to-run range next to the median. For the "which one should I pick" reasoning, read When to use Redis vs Memcached in 2026; this post is the measurement side.
Benchmark setup
Everything ran on 2026-10-07 on a Windows 11 laptop (Intel i7-8550U, 4 cores / 8 threads, 8 GB visible to Docker) under Docker Desktop, with the CPU already busy with other work. All images are pinned to tags that exist on Docker Hub (redis:8.10.2, memcached:1.6.45, redislabs/memtier_benchmark:2.5.2; I checked each tag against the Docker Hub API on 2026-10-07). These are the versions that produced every number in this post:
Redis server v=8.10.2 sha=00000000:1 malloc=jemalloc-5.3.0 bits=64
memcached 1.6.45
memtier_benchmark v=2.5.2 libevent=2.1.12-stable
Load: 40 connections, 1 SET per 10 GETs, random keys over 100,000, 15 s per run, 3 runs per config, keys prefilled first (otherwise you benchmark misses). Memcached needs --protocol=memcache_binary; memtier speaks the Redis protocol by default.
Caveats, all of which apply to every table:
- Not a pure engine comparison. Memcached
-t 4runs four worker threads; default Redis runs commands on one. Redis's benchmark docs say it is "mostly, a single-threaded server from the POV of commands execution" and that comparing one instance to a multi-threaded store is not fair. The-t 1and--io-threadsrows exist to separate thread count from engine. - Shared silicon.
--cpuset-cpus 0-3for servers and4-7for memtier index the Docker Desktop VM's vCPUs. I did not verify how those map to the laptop's four physical cores, so the two sides may well compete for the same cores, and the VM adds a virtual network hop. Trust shapes and ratios, not absolute ops/s. - Not covered: pipelining, TTLs, eviction pressure, write-heavy loads.
The throughput script
Runnable with bash and Docker (RUNS=1 SECS=5 bash tput.sh for a quick pass). A bare --save is how this word-split config list gives Redis an empty save schedule.
#!/bin/bash
# Throughput + latency. Servers on CPUs 0-3, memtier on CPUs 4-7, same Docker network.
RUNS=${RUNS:-3}; SECS=${SECS:-15}
REDIS=redis:8.10.2; MEMC=memcached:1.6.45; MT_IMG=redislabs/memtier_benchmark:2.5.2
docker network create bnet >/dev/null 2>&1
mt() { docker run --rm --network bnet --cpuset-cpus 4-7 $MT_IMG memtier_benchmark "$@"; }
KEYS="--key-minimum=1 --key-maximum=100000"
# name | port | memtier protocol flag | memtier threads | clients/thread | server image and command
cfgs=(
"redis-default|6379||4|10|$REDIS redis-server --save --appendonly no"
"redis-io3|6379||4|10|$REDIS redis-server --save --appendonly no --io-threads 3"
"redis-io3-mt3|6379||3|13|$REDIS redis-server --save --appendonly no --io-threads 3"
"memcached-t1|11211|--protocol=memcache_binary|4|10|$MEMC memcached -t 1 -m 1024"
"memcached-t4|11211|--protocol=memcache_binary|4|10|$MEMC memcached -t 4 -m 1024")
for cfg in "${cfgs[@]}"; do
IFS='|' read -r name port proto th cl srv <<< "$cfg"
for size in 64 1024; do
docker rm -f srv >/dev/null 2>&1
docker run -d --name srv --network bnet --cpuset-cpus 0-3 $srv >/dev/null; sleep 2
mt -s srv -p $port $proto --threads=4 --clients=10 $KEYS --data-size=$size \
--key-pattern=P:P --ratio=1:0 -n allkeys --hide-histogram >/dev/null 2>&1 # prefill, GETs must hit
for run in $(seq $RUNS); do
mt -s srv -p $port $proto --threads=$th --clients=$cl $KEYS --data-size=$size \
--key-pattern=R:R --ratio=1:10 --test-time=$SECS --hide-histogram 2>&1 |
awk -v n=$name -v s=$size -v r=$run '/^Totals/{t=$2} /^Gets/{g50=$6;g99=$7} /^Sets/{s50=$6;s99=$7}
END{printf "%s %sB run%d ops=%s get_p50=%s get_p99=%s set_p50=%s set_p99=%s\n",n,s,r,t,g50,g99,s50,s99}'
done
done
done
docker rm -f srv >/dev/null 2>&1
Redis vs Memcached throughput and latency
The redis.conf comments (8.0 branch) advise I/O threads only on machines with at least 4 cores, leaving a spare ("if you have a four cores boxes, try to use 3 I/O threads"), and say to run the benchmark itself threaded to match. Hence --io-threads 3, and a redis-io3-mt3 row with memtier at 3 threads (3 x 13 = 39 connections).
RUNS=3 SECS=15 bash tput.sh on the pinned images, 30 runs in total. Medians of 3; latencies in ms:
| Config | Value | Ops/s (median) | Ops/s range | GET p50 | GET p99 | SET p50 | SET p99 |
|---|---|---|---|---|---|---|---|
| Redis default | 64 B | 17,014 | 13,068-19,317 | 1.89 | 11.9 | 1.91 | 11.8 |
Redis --io-threads 3 |
64 B | 7,544 | 5,852-8,093 | 1.43 | 40.4 | 1.44 | 39.7 |
Redis io-threads 3, memtier --threads 3 |
64 B | 8,724 | 8,665-13,455 | 1.61 | 31.9 | 1.67 | 31.2 |
Memcached -t 1 |
64 B | 9,836 | 8,755-10,854 | 1.62 | 30.3 | 1.65 | 30.3 |
Memcached -t 4 |
64 B | 11,507 | 9,452-12,995 | 0.59 | 37.6 | 0.60 | 37.9 |
| Redis default | 1 KB | 14,262 | 12,771-21,255 | 1.85 | 17.8 | 1.85 | 17.7 |
Redis --io-threads 3 |
1 KB | 9,115 | 5,763-11,738 | 1.67 | 33.0 | 1.68 | 32.5 |
Redis io-threads 3, memtier --threads 3 |
1 KB | 10,152 | 7,854-12,063 | 1.53 | 29.4 | 1.53 | 29.1 |
Memcached -t 1 |
1 KB | 12,685 | 10,140-16,015 | 1.71 | 22.3 | 1.71 | 21.4 |
Memcached -t 4 |
1 KB | 14,305 | 9,881-18,967 | 0.57 | 31.9 | 0.57 | 32.3 |
The raw per-run lines behind this table are saved as bench/tput.txt next to the scripts.
What the data supports:
- Throughput didn't pick a winner. At 1 KB, Memcached
-t 4(9.9k-19.0k) and default Redis (12.8k-21.3k) overlap almost completely, with the same median to within 0.3%. At 64 B default Redis was ahead (13.1k-19.3k against 9.5k-13.0k for Memcached-t 4), even though Memcached had four worker threads to Redis's one. - Memcached
-t 4had the lowest GET latency in every run. Its GET p50 was 0.55-0.62 ms in all six runs; default Redis was 1.82-1.95 ms in all six. Its p99 was not better: per-run GET p99 was 21-45 ms for Memcached-t 4against 4.6-25 ms for default Redis (the medians are about 32-38 ms against 12-18 ms). --io-threads 3made Redis slower here. At 64 B all three--io-threads 3runs (best 8.1k) came in below the worst default-Redis run (13.1k), and the median was still lower at 1 KB. With the clients and servers sharing eight logical CPUs, extra Redis I/O threads compete with memtier for cores.- Matching memtier
--threadsto 3 changed little. The ranges overlap with the plain io-threads row. - The p99 column is mostly scheduler noise. Tails of 10-45 ms against a 1-2 ms median come from server and client threads contending for CPU; I wouldn't rank anything on it.
Redis vs Memcached memory usage per key
I loaded 100,000 sequential keys (memtier-1 to memtier-100000, about 13 bytes on average) into a fresh container per size. "cgroup" is container memory from docker stats (MemUsage) minus the idle baseline, divided by 100,000. MemUsage is rounded to a few significant digits in MiB, which is worth roughly 0.1-1 MiB (1-10 B/key) of error, and depending on the cgroup version and setup it may exclude page cache, which doesn't matter for these in-process stores but means the numbers aren't comparable with other tools. Both servers held all 100,000 keys with zero evictions.
| Value size | Redis (cgroup B/key) | Memcached (cgroup B/key) | Memcached vs Redis |
|---|---|---|---|
| 64 B | 128 | 163 | +27% |
| 256 B | 378 | 402 | +6% |
| 1 KB | 1,350 | 1,203 | -11% |
| 4 KB | 5,225 | 4,593 | -12% |
The memory script and its output
#!/bin/bash
# Memory per key: fresh container per value size, 100,000 keys loaded once each.
REDIS=redis:8.10.2; MEMC=memcached:1.6.45; MT_IMG=redislabs/memtier_benchmark:2.5.2
docker network create bnet >/dev/null 2>&1
mt() { docker run --rm --network bnet --cpuset-cpus 4-7 $MT_IMG memtier_benchmark "$@"; }
LOAD="--threads=2 --clients=5 --key-minimum=1 --key-maximum=100000 --key-pattern=P:P --ratio=1:0 -n allkeys --hide-histogram"
cg() { docker stats --no-stream --format '{{.MemUsage}}' srv | awk '{print $1}'; } # container total (cgroup)
for size in 64 256 1024 4096; do
for sys in redis memcached; do
docker rm -f srv >/dev/null 2>&1
if [ $sys = redis ]; then
docker run -d --name srv --network bnet $REDIS redis-server --save --appendonly no >/dev/null; port=6379; proto=""
else
docker run -d --name srv --network bnet $MEMC memcached -m 1024 >/dev/null; port=11211; proto="--protocol=memcache_binary"
fi
sleep 2; base=$(cg)
mt -s srv -p $port $proto $LOAD --data-size=$size >/dev/null 2>&1; sleep 2
if [ $sys = redis ]; then
echo "redis size=$size keys=$(docker exec srv redis-cli dbsize) used_memory=$(docker exec srv redis-cli info memory | tr -d '\r' | awk -F: '/^used_memory:/{print $2}') cgroup_idle=$base cgroup_loaded=$(cg)"
else
st=$( (printf 'stats\r\nquit\r\n'; sleep 1) | docker run --rm -i --network bnet alpine nc srv 11211 | tr -d '\r')
echo "memcached size=$size keys=$(echo "$st" | awk '/curr_items/{print $3}') item_bytes=$(echo "$st" | awk '/ bytes /{print $3}') evictions=$(echo "$st" | awk '/ evictions/{print $3}') cgroup_idle=$base cgroup_loaded=$(cg)"
fi
done
done
docker rm -f srv >/dev/null 2>&1
Output of that exact script on the pinned images, saved as bench/mem-pinned.txt. The table is (loaded - idle) MiB x 1,048,576 / 100,000; for Redis at 64 B, (19.07 - 6.891) MiB = 128 B/key:
redis size=64 keys=100000 used_memory=14200200 cgroup_idle=6.891MiB cgroup_loaded=19.07MiB
memcached size=64 keys=100000 item_bytes=13588895 evictions=0 cgroup_idle=2.758MiB cgroup_loaded=18.27MiB
redis size=256 keys=100000 used_memory=38199672 cgroup_idle=6.398MiB cgroup_loaded=42.49MiB
memcached size=256 keys=100000 item_bytes=32788895 evictions=0 cgroup_idle=2.047MiB cgroup_loaded=40.4MiB
redis size=1024 keys=100000 used_memory=134201592 cgroup_idle=6.598MiB cgroup_loaded=135.3MiB
memcached size=1024 keys=100000 item_bytes=109588895 evictions=0 cgroup_idle=2.039MiB cgroup_loaded=116.8MiB
redis size=4096 keys=100000 used_memory=518198808 cgroup_idle=6.832MiB cgroup_loaded=505.1MiB
memcached size=4096 keys=100000 item_bytes=416788895 evictions=0 cgroup_idle=2.762MiB cgroup_loaded=440.8MiB
Where the bytes go
The counters below are measured; the explanations after them are my interpretation. I took the servers' own stats on freshly loaded containers (stats slabs / stats for Memcached, MEMORY STATS for Redis; saved as bench/slabs.txt):
| Value size | Memcached item bytes/key |
Slab chunk size | total_malloced/key |
Redis keys.bytes-per-key |
|---|---|---|---|---|
| 64 B | 136 | 152 | 157 | 84 |
| 1 KB | 1,096 | 1,184 | 1,185 | 1,284 |
| 4 KB | 4,168 | 4,544 | 4,561 | 5,124 |
- Memcached stores items in fixed slab chunks, 1 MB pages at a time, and all 100,000 items landed in one slab class each time. At 64 B an item is 136 B but its chunk is 152 B, and
total_mallocedrounds up to whole pages (15 pages, 157 B/key), so the container's 163 B/key is mostly chunk size plus page rounding. At 1 KB and 4 KB the chunk padding is 88 and 376 B per key. - Redis reports
keys.bytes-per-keyof 84 at 64 B, and 1,284 and 5,124 at 1 KB and 4 KB. My inference is that Redis's smaller per-key overhead explains the 64 B win over Memcached's item header plus chunk rounding; I didn't read either source or inspect allocator internals, so treat the cause as unconfirmed. The measured container numbers above don't depend on it.
The practical consequence: at 4 KB, total_malloced (4,561 B/key) is about 9% above the bytes counter (4,168). Sizing a Memcached box from bytes under-provisions; budget from total_malloced or the container's cgroup number. A short load test also can't show fragmentation after weeks of churn.
Redis vs Memcached: which to pick
- Values under about 256 B: Redis was smaller (Memcached +6% to +27% per key), and throughput didn't favor Memcached.
- Values of 1 KB and up: Memcached saved 11-12% per key and had about a third of Redis's median GET latency with
-t 4on this contended box. A modest saving, not a reason to add a second system if Redis is already in your stack. - Decision rule: if your median value is under 256 B, size from Redis. Over 1 KB, a 10% memory saving on a 10 GB cache is about 1 GB, so measure both on your own data with the scripts below before deciding. The decision post covers persistence, data structures and the rest.
Next steps: test your own value size
- In
tput.shchangefor size in 64 1024to your real value size(s), and inmem.shchangefor size in 64 256 1024 4096likewise. Use your own key naming if key length is far from 13 bytes. - Run
RUNS=3 SECS=15 bash tput.sh | tee tput.txtandbash mem.sh | tee mem.txton a machine with no other load. - Compare the min-max ranges, not the medians. If ranges overlap, as default Redis and Memcached
-t 4did at 1 KB, throughput isn't a reason to switch. - Compare GET p50 per config, and memory per key from
cgroup_loadedminuscgroup_idle. Checkkeys,curr_itemsandevictionsin the output first.
Keep reading
Complete Solution: Scaling a Node.js Application with Kubernetes and Docker
29 min · 1,000 views
Data ScienceMastering CI/CD Pipelines with Jenkins and Docker: A Deep Dive into Automated Deployment and Testing
14 min · 982 views
AI WorkflowsDecentralized Finance Protocol Comparison: Uniswap V3 vs SushiSwap vs Curve Finance - Performance, Security, and Use Cases
16 min · 889 views
Bekzod Erkinov
AuthorFounder of NextGenBeing. Software engineer working with Laravel, Python, and cloud infrastructure. Writes about patterns that actually hold up in production. Based in Tashkent, Uzbekistan.
Get the AI-Assisted Developer's Field Guide
The workflow, prompts, and tools I use to ship faster with AI — free when you subscribe. Plus new deep-dives in your inbox. No spam, unsubscribe anytime.
Comments (0)
Please log in to leave a comment.
Log InRelated Articles
Decentralized Finance Protocol Comparison: Uniswap V3 vs SushiSwap vs Curve Finance - Performance, Security, and Use Cases
Feb 28, 2026
Claude Code Headless: Automate Dev Chores with claude -p
Oct 6, 2026
PostgreSQL Hidden Features for Better Performance: Battle-Tested Optimizations from Production
May 23, 2026