DevLift
Back to Blog

Redis vs Memcached — One Is a Swiss Army Knife, the Other Is a Scalpel

Redis does more; Memcached does one thing faster. Here's exactly when that trade-off matters — plus why Valkey is now the open-source default in 2026.

Admin
August 5, 202614 min read4 views

Redis vs Memcached — One Is a Swiss Army Knife, the Other Is a Scalpel

Your API is getting hammered. Database queries that were fast at 100 req/s are choking at 5,000. Someone says "add a cache" — and the next question is always: Redis or Memcached?

For most teams, the answer is Redis, and they never think about it again. That's usually fine. But "usually fine" isn't the same as right, and the teams that default to Redis for pure object caching sometimes pay in operational complexity they didn't need. Memcached, despite feeling like a 2009 technology, is still maintained and still deployed at very large scale — Meta's Scaling Memcache at Facebook (NSDI '13) remains the canonical account of running it as a social-graph cache.

So let's actually compare these two. Not surface-level "Redis has more features," but the real trade-offs that matter when you're picking one for a specific problem. Where I make a claim about memory or atomicity below, I built both servers and measured it — redis 7.2.4 (jemalloc) and memcached 1.6.38, same 4-core box.

Quick Decision Matrix

If you need...Choose
Simple object/page fragment caching with max throughputMemcached
Cache that survives process restartsRedis
Session storage with TTLRedis
Rate limiting, counters, leaderboardsRedis
Pub/sub messaging between servicesRedis
Job queues (simple)Redis
Fan-out to multiple consumersRedis
Multi-threaded cache across many CPU coresMemcached
Horizontal scaling via consistent hashing in clientMemcached
Pure string key-value cache, nothing elseEither (Memcached is simpler)
Values larger than 1 MBRedis (Memcached's default item cap)
A BSD-licensed, OSI-approved Redis-compatible serverValkey

The 30-Second Architecture Difference

Memcached is a distributed in-memory hash table. Period. It stores strings, and by default nothing larger than 1 MB per item (item_size_max, which I confirmed with stats settings on 1.6.38). It has a multi-threaded architecture that can saturate multiple CPU cores simultaneously. Its slab allocator manages memory in fixed-size classes — predictable and low-fragmentation. There's no persistence, no replication, no pub/sub. It's a pure cache. When the process restarts, data is gone.

Redis started as a single-threaded event loop with a richer data model. "Single-threaded" sounds limiting until you realize most Redis bottlenecks are network I/O, not CPU. Redis 6.0 added threaded I/O for reading and writing network data while keeping command execution on a single thread — and it is off by default. On a stock 7.2.4 build, CONFIG GET io-threads returns 1 and INFO server reports io_threads_active:0. You have to opt in.

The licensing story is messier than "Redis went closed." Three phases, read out of the licence file in the Redis repo at each tag:

  • 7.2 and earlier — BSD 3-Clause. At the 7.2.4 tag there is no LICENSE.txt at all; the file is COPYING, and it opens Copyright (c) 2006-2020, Salvatore Sanfilippo followed by the standard three clauses. Redis's own retrospective wording, from the 8.0 LICENSE.txt: "Redis Open Source 7.2 and prior releases remain subject to the BSDv3 clause license as referenced in the REDISCONTRIBUTIONS.txt file."
  • 7.4LICENSE.txt appears and begins "Starting on March 20th, 2024, Redis follows a dual-licensing model," with contributions "subject to the user's choice of the Redis Source Available License v2 (RSALv2) or the Server Side Public License v1 (SSPLv1), as follows". Neither is OSI-approved. This is the change people mean when they say Redis stopped being open source.
  • 8.0 and later — a tri-license: "your choice of: (a) the Redis Source Available License v2 (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the GNU Affero General Public License v3 (AGPLv3)."

So AGPLv3 in Redis 8 is an added option, not a restriction — it's a partial walk-back toward an OSI-approved licence, not a further step away. It's still copyleft, which is a genuine non-starter at some companies, but if you tell your legal team "Redis 8 is AGPL-only" you'll be wrong in a way that matters.

Valkey was forked from Redis 7.2.4 by former Redis maintainers and is hosted by the Linux Foundation, with contributors from AWS, Google Cloud, Oracle, Ericsson and others. It is BSD 3-Clause.

⚠️

Redis 8.0+ is tri-licensed (RSALv2 / SSPLv1 / AGPLv3) — you pick one, and none of the three is a permissive licence. If your company forbids copyleft and source-available terms, Valkey (BSD 3-Clause, forked from Redis 7.2.4) is the drop-in.

Threading: Where Memcached Still Wins

This is the one area where Memcached genuinely outperforms Redis on certain workloads.

Memcached is fully multi-threaded from the ground up. Every CPU core can process requests independently. If you have a 32-core machine doing nothing but simple GET / SET on string values, Memcached will saturate those cores in a way Redis historically couldn't.

Redis's threading story is more nuanced. Redis 6.0 introduced I/O threads for network reads and writes, but command execution still happens on a single thread — and, as above, io-threads ships set to 1, so an untuned Redis is doing its network I/O on the main thread too. Either way, CPU-heavy operations (large LRANGE, complex Lua scripts, big SORT) still serialise behind one core.

I'm deliberately not giving you an ops/sec table here. Any number I could produce on a 4-core box where the load generator competes with the server for CPU would say more about my client than about either cache, and the ops/sec figures that circulate for this comparison are almost never accompanied by an instance type, a pipeline depth, or a value size.

What there is is a properly specified measurement from the Valkey side. Valkey's "Unlock 1 Million RPS" post (Dan Touitou and Uri Yagelnik, 2024-08-05) reports that the new I/O threading in Valkey 8.0 "increased by approximately 230%, rising from 360K to 1.19M requests per second compared to Valkey 7.2," with average latency down 69.8% from 1.792 ms to 0.542 ms — "Tested with 8 I/O threads, 3M keys DB size, 512 bytes value size, and 650 clients running sequential SET commands using AWS EC2 C7g.16xlarge." Note the baseline is Valkey 7.2, not Memcached; this tells you the threading work paid off, not who wins a head-to-head.

In practice: unless you're running an extreme-scale read-heavy cache with nothing but string values, and you've measured it on your own hardware, this threading difference rarely determines your architecture choice.

Data Structures: Redis's Real Advantage

This is where the comparison stops being close.

Memcached stores strings. That's it. If you want a set of user IDs, you serialize a list to JSON and store it as a string. If you want a sorted leaderboard, you implement it yourself.

Redis has native types: strings, hashes, lists, sets, sorted sets, bitmaps, HyperLogLog, geospatial indexes, and streams. These aren't just convenience wrappers — they operate atomically and are implemented in C with tight memory layouts.

Counters are the one place people get this comparison wrong in Memcached's favour, so let's be precise. Memcached does have atomic counters. The protocol doc is explicit that incr and decr "are used to change data for some item in-place," and I checked: 8 concurrent clients doing 2,000 incr counter 1 each against memcached 1.6.38 landed on exactly 16,000, zero lost updates. What Memcached lacks is not atomicity, it's convenience — incr won't create a missing key ("the item must already exist for incr/decr to work; these commands won't pretend that a non-existent key exists with value 0"), it doesn't touch the TTL, and the value has to already be a decimal 64-bit unsigned integer.

# Memcached: atomic, but you must seed the key yourself
if not client.add("pageviews:article:42", "0", expire=86400):  # add = only if absent
    pass                                                      # someone else seeded it
client.incr("pageviews:article:42", 1)   # atomic, in-place; does NOT reset the TTL
 
# Redis: same guarantee, one call, and INCR creates the key at 0 for you
redis.incr("pageviews:article:42")
redis.expire("pageviews:article:42", 86400)  # 24h TTL

The version that is broken is the read-modify-write people reach for when they forget incr exists — and this is worth seeing, because it fails silently:

# DON'T: lost updates under any concurrency at all
cur = int(client.get("pageviews:article:42") or 0)
client.set("pageviews:article:42", str(cur + 1))

Same harness, same 8 clients, same 16,000 intended increments, this pattern instead: expected 16000 actual 3524 lost 12476. The exact survivor count moves by a hundred or two between runs; the loss is ~78% every time. It loses those writes and reports success on every one of them. The race is in the pattern, not in Memcached.

# Memcached: "leaderboard" — serialize/deserialize every time
scores = json.loads(client.get("leaderboard") or "[]")
scores.append({"user": user_id, "score": score})
scores.sort(key=lambda x: x["score"], reverse=True)
client.set("leaderboard", json.dumps(scores[:100]))
# ^ not atomic, no range queries, breaks under concurrency
 
# Redis: sorted set — atomic, O(log N) insert, efficient range queries
redis.zadd("leaderboard", {user_id: score})
top_10 = redis.zrevrange("leaderboard", 0, 9, withscores=True)
# Redis: rate limiting with sliding window
pipe = redis.pipeline()
now = time.time()
window_key = f"ratelimit:{user_id}"
pipe.zremrangebyscore(window_key, 0, now - 60)  # drop events older than 60s
pipe.zadd(window_key, {str(now): now})
pipe.zcard(window_key)
pipe.expire(window_key, 60)
_, _, request_count, _ = pipe.execute()
 
if request_count > 100:
    raise RateLimitExceeded()

Counters aside, you can fake all of this in Memcached with serialization and client-side logic — and the leaderboard example above is exactly what that looks like, complete with the lost-update window. You pay serialization overhead, you give up range queries, and you're maintaining code that Redis hands you as a primitive.

Persistence: The Cache That Survives

Memcached is purely in-memory. Restart the process, lose everything. For a pure cache in front of a database, that's fine — the DB is the source of truth. But it means your cache is always cold after a deployment.

Redis supports two persistence mechanisms:

RDB (Redis Database) — periodic snapshots. Redis forks the process and writes a point-in-time snapshot to disk. Fast, compact, but you can lose data between snapshots if the process crashes.

AOF (Append-Only File) — logs every write command. On restart, Redis replays the log to reconstruct state. Slower writes, larger files, but much less data loss (configurable: always, everysec, or no).

# redis.conf
appendonly yes
appendfsync everysec   # flush to disk every second — lose max 1 second of data

Most teams use RDB for backups and AOF for durability, with appendfsync everysec as a reasonable balance.

This matters for session stores. If your cache holds user session data and the server restarts, Memcached gives your users a cold session (logged out). Redis can persist that data across restarts. For high-traffic apps, warming a Memcached cluster after a restart is a real operational headache.

If you're using your cache as a session store, persistence matters. Memcached after a restart = everyone gets logged out.

Pub/Sub and Streams

Memcached has no messaging. None.

Redis has two mechanisms:

Pub/Sub — fire-and-forget channel messaging. Publishers don't know who's subscribed. Subscribers miss messages they weren't connected for.

# Publisher
redis.publish("order.created", json.dumps({"order_id": "ord_123", "total": 99.00}))
 
# Subscriber
pubsub = redis.pubsub()
pubsub.subscribe("order.created")
for message in pubsub.listen():
    if message["type"] == "message":
        handle_order(json.loads(message["data"]))

Redis Streams — persistent, consumer-group-aware log (like a lightweight Kafka for simple cases). Messages are retained. Multiple consumer groups can read independently. Consumer groups track which messages each group has acknowledged.

# Producer
redis.xadd("orders", {"order_id": "ord_123", "total": "99.00"})
 
# Consumer group reads
redis.xgroup_create("orders", "fulfillment-service", id="0", mkstream=True)
messages = redis.xreadgroup("fulfillment-service", "worker-1", {"orders": ">"}, count=10)
for stream, entries in messages:
    for entry_id, data in entries:
        process_order(data)
        redis.xack("orders", "fulfillment-service", entry_id)

This isn't a Kafka replacement at scale, but for lightweight inter-service events where you'd otherwise bolt on a queue, Redis Streams is a practical option.

Memory: Slab vs. jemalloc

Memcached's slab allocator is a deliberate design choice, not a limitation. Memory is divided into slab classes (size buckets). Items are stored in the nearest bucket that fits. This bounds fragmentation but rounds every item up, and at the default growth_factor 1.25 the low classes are 96, 120, 152 and 192 bytes — so a total item of 121 bytes occupies 152 and the remaining 31 bytes are unreachable.

Redis uses jemalloc by default and has background defragmentation (activedefrag yes, which requires jemalloc). jemalloc has size classes too, so Redis doesn't allocate exactly what's needed either; its classes are just much finer-grained than a slab class, and it pays a per-key overhead for the dict entry, the key SDS and the value object.

The received wisdom is that this makes Memcached the leaner one for uniform small items. I measured it, and on this workload it isn't true. Same box, 200k / 150k / 60k keys of a fixed size loaded into each server, comparing process RSS growth (not the servers' self-reported numbers, which count different things):

key = 13 bytes, uniform sizes, redis 7.2.4 (jemalloc) vs memcached 1.6.38 (-t 4)
 
value size   redis RSS/key   memcached RSS/key   raw payload/key
    10 B         96.1 B           105.7 B             23 B
   100 B        181.6 B           205.6 B            113 B
  1000 B       1127.4 B          1203.0 B           1013 B

Redis came out 6–12% lower at every size, including the uniform-small-item case that's supposed to be Memcached's home turf. That's the slab rounding showing up: I checked with stats items, and a 13-byte key with a 100-byte value lands in class 4, chunk_size 192 — so 113 bytes of payload occupy 192 bytes and the rest is waste.

Two caveats on that table, since it's RSS and not an accounting statement. Re-running it moves the Redis rows by a few bytes per key (the 10 B row landed on 90.5 and 96.1 on two runs) because jemalloc's arenas don't have to come back the same way; the Memcached column and the whole 1000 B row reproduced to a tenth of a byte. And the gap is a handful of percentage points, not a factor — small enough that your own value-size distribution decides it.

So don't pick Memcached to save RAM on plain strings — measure your own value distribution first. What slab allocation actually buys you is predictability: bounded, up-front waste instead of fragmentation that drifts as the working set churns. That's a real operational property. It just isn't a smaller number.

Clustering and Horizontal Scaling

Both support horizontal scaling, but the model is different.

Memcached relies on client-side consistent hashing. There's no server-side cluster concept. The client distributes keys across nodes. Adding a node changes the hash ring and causes cache misses until the cluster re-warms. There's no built-in replication — losing a node means losing its data.

Redis Cluster (added in Redis 3.0) is server-side sharding across 16,384 hash slots. Nodes know about each other, detect failures, and perform automatic failover via Raft-like leader election. You get replication (primary + replicas) built in.

Rendering diagram...

For most teams, Redis Cluster's built-in replication and automatic failover is worth the additional complexity. Memcached's approach works if you're comfortable with client-side key distribution and don't need replication.

Eviction Policies

Both support TTL-based eviction. Redis goes further.

Memcached evicts when a slab class is full, and its LRU is smarter than the folklore: since 1.5 it runs a segmented LRU with hot/warm/cold queues plus a background crawler. On 1.6.38, stats settings shows lru_segmented yes, lru_maintainer_thread yes, lru_crawler yes, hot_lru_pct 20, warm_lru_pct 40. Because eviction is per slab class, a hot class can be evicting while another sits half empty — which is the flip side of that predictable allocation.

Redis has eight eviction policies. (I checked by feeding it a bogus value; it enumerates exactly these in the error.)

  • noeviction — return errors when memory is full
  • allkeys-lru — evict any key, least recently used first
  • volatile-lru — evict only keys with TTL, LRU order
  • allkeys-lfu — evict by least frequently used (better for skewed access patterns)
  • volatile-lfu — LFU for keys with TTL
  • allkeys-random — random eviction
  • volatile-random — random eviction from keys with TTL
  • volatile-ttl — evict keys closest to expiration first
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lfu  # you almost certainly want to set this

Both of those lines matter, because the defaults are not cache defaults. On a stock 7.2.4 build, CONFIG GET maxmemory returns 0 (unlimited) and CONFIG GET maxmemory-policy returns noeviction. That combination means Redis will grow until the OS or your container limit kills it, and if you set maxmemory and forget the policy, writes start failing with an OOM error instead of evicting anything. Memcached's default is different in kind rather than better in degree: start it with no -m at all and stats settings reports maxbytes 67108864 and evictions on, so it caps itself at 64 MB and evicts. That default is almost certainly wrong for your box — but it fails by throwing away cold keys, not by refusing writes or eating the machine.

⚠️

maxmemory-policy defaults to noeviction. A Redis you intended as a cache will, out of the box, either eat all your RAM or start rejecting writes — never quietly evict. Set both maxmemory and a policy explicitly.

LFU (Least Frequently Used) is often better than LRU for caching — it keeps hot keys in memory even if they weren't accessed in the last few seconds. This matters for data that's accessed in bursts.

When to Use Redis

  • Session storage — persistence means sessions survive restarts
  • Rate limiting — sorted sets and atomic increments make sliding window limiters trivial
  • Leaderboards and counters — sorted sets + INCR are built for this
  • Job queues — Redis Lists work for simple queues; Redis Streams for more complex patterns
  • Feature flags / config — hashes let you store structured config; pub/sub propagates changes
  • Distributed locks — Redlock algorithm, or simpler SET key value NX EX ttl
  • Real-time analytics — HyperLogLog for cardinality estimates, sorted sets for time-series rankings
  • Lightweight event streaming — Redis Streams when Kafka is overkill
  • You want persistence — when cache warmup time on restart is a real cost

When to Use Memcached

  • Pure object/fragment caching — storing serialized HTML fragments, precomputed API responses, or ORM results where you just need fast string GET/SET, all under 1 MB
  • Predictable memory behaviour — bounded, up-front slab waste rather than fragmentation that drifts with the working set. Predictable, per the numbers above, is not the same as smaller.
  • Multi-core throughput on a single box — Memcached is multi-threaded by default; Redis's I/O threading is opt-in and its command execution is single-threaded regardless. Whether that gap matters to you is a measurement, not a given.
  • Simple operational model — Memcached is genuinely simpler to operate: no persistence to tune, no cluster rebalancing, fewer configuration knobs, and a default that evicts rather than one that refuses writes
  • You're already running it at scale — the Meta deployment documented at NSDI '13 is the existence proof that this scales. If it works, it works.

When to Use Both

Some teams run both. Common pattern:

  • Memcached for high-volume page/fragment caching — stateless, high eviction rate, pure throughput
  • Redis for sessions, queues, pub/sub, and any workload needing data structures or persistence

This isn't unusual, and the operational cost of running two cache systems is real — but if you're at a scale where the throughput difference matters, you probably already have the infra team to manage it.

The Valkey Question

If you're adopting "Redis" fresh in 2026, you should seriously consider Valkey instead:

  • BSD 3-Clause licence — none of the RSALv2 / SSPLv1 / AGPLv3 choices to take to legal
  • Drop-in Redis protocol compatibility (RESP2/RESP3)
  • Multi-threaded I/O introduced in Valkey 8.0 — see the measured 360K → 1.19M RPS figure above, against a Valkey 7.2 baseline
  • AWS's ElastiCache for Valkey announcement (October 8, 2024) prices it "33% lower" for Serverless and "20% lower" for node-based "than other supported engines"
  • Active maintainers from AWS, Google, Oracle, Ericsson, and others

Check the version before you pin anything: Valkey has moved fast. 8.0 landed in September 2024, 9.0.0 in October 2025, and as of this writing 9.1.1 (July 2026) is current, with the 8.0.x, 8.1.x and 7.2.x lines still receiving releases.

💡

Valkey is not a competitor to Redis the company — it's a fork of Redis 7.2.4, the last BSD-licensed release. It supports the existing Redis clients, commands, and data structures. The migration path from Redis 7.2 is typically zero-code; from Redis 8 you may be leaving behind commands added after the fork, so diff the command set rather than assuming.

The Real Takeaway

Memcached is better at exactly one thing: being a fast, simple, multi-threaded string cache. It's genuinely excellent at that. If that's your entire use case, Memcached is lower overhead.

Redis (or Valkey) wins everywhere else: sessions, queues, pub/sub, data structures, persistence, replication, cluster failover, and operational feature set. For new projects, that's almost always the right default.

The teams that still choose Memcached in 2026 know exactly why, and the reason is always something they measured on their own hardware — a slab profile that fits their value distribution, a ceiling on single-core command execution, an operational model with fewer knobs to get wrong at 3am. Notice that none of those are things you can settle by reading a comparison table, including this one. Everyone else should probably be running Redis or Valkey.

Comments (0)

No comments yet. Be the first to share your thoughts!

Related Articles

Shopify replaced Redis with MySQL SKIP LOCKED for inventory reservations. DoorDash replaced Redis Cluster with stateless Kvrocks. The lesson isn't "Redis is bad" — it's about matching infrastructure to access patterns.
AdminAugust 5, 202612 min read
Prisma 7 eliminated the Rust binary and closed the performance gap. So why are teams still choosing Drizzle? The real answer is about SQL transparency, bundle size, and who owns complexity.
AdminAugust 5, 202610 min read
Client-side validation is UX. Server-side validation is security. And a schema is not an auth guard — here's the fix that still ships an account takeover, and how to catch it in review.
AdminAugust 5, 20269 min read