Redis and Memcached compared by mechanism: threading, slab vs allocator memory, expiry and eviction, persistence, clustering, meta commands, operations and licensing, with a recommendation.
Memcached is a multi-threaded, in-memory cache for opaque values with a fixed memory budget and very little to configure. Redis is a data-structure server that executes commands on one thread and adds persistence, replication, clustering, scripting and many data types. For plain key-to-blob caching both are fast enough that the choice rarely decides your latency. What decides it is whether you need anything beyond get and set, how you want to scale across cores and machines, and what should happen when a node dies.
"Redis" here includes Valkey, its BSD-licensed fork, except where noted.
Summary
Memcached
Redis / Valkey
Threading
Worker threads (4 by default) execute commands in parallel
One thread executes commands; optional I/O threads handle sockets
Each Redis cache pattern with code, guarantees and failure modes, then the stale-set race, why delete beats update, versioned writes, outbox and CDC invalidation, and what to use for which data.
Memcached starts a listener thread that accepts connections and hands each one to a worker thread. Every worker runs its own event loop and executes commands for its connections, and the workers share the cache through locks. The default is 4 workers (-t). Background threads handle LRU maintenance, the expiry crawler and slab page moves. One process therefore uses several cores without sharding, and because nearly every operation is O(1) no single request holds the others up for long.
Redis executes every command on one main thread. That is what makes each command atomic, and it is also the main operational hazard: one slow command (KEYS, a DEL on a huge collection, a long Lua script, SMEMBERS on a set with millions of members) delays every other client. Since Redis 6, io-threads can move socket I/O and protocol parsing to other threads. It is off by default (io-threads 1), and command execution stays on the main thread either way. Valkey 8 redesigned its I/O threading to do more of this work in parallel.
To execute on more cores, Redis runs more processes. A 16-core machine is one Memcached process, or several Redis shards with their own memory limits, ports and failure handling.
Memory management
Memcached takes a fixed budget (-m, 64 MB by default) and manages it with a slab allocator. Memory is divided into 1 MB pages. Each page is assigned to a slab class and cut into equal chunks. Chunk sizes grow from class to class by a factor (-f, default 1.25), and an item goes into the smallest class whose chunk fits its key, value and header. Consequences:
There is no external fragmentation, and memory use stays flat for the life of the process.
Space is lost inside chunks: an item slightly larger than one chunk size occupies the next size up.
Each slab class has its own LRU. If value sizes shift after the cache has filled, pages are stuck in classes that no longer need them, and one class evicts while another holds idle memory. Current versions move pages between classes automatically (slab_reassign and slab_automove are on by default, as stats settings shows), which largely fixes this.
Items larger than slab_chunk_max (512 KB by default) are stored as a chain of chunks.
Redis allocates each key and value through a general-purpose allocator (jemalloc by default on Linux). Each key carries metadata: an entry in the main hash table, an object header with the type, encoding and LRU or LFU bits, and extra space if it has a TTL. For many small values this overhead can exceed the payload. Because allocation sizes vary freely, memory can fragment after heavy churn: mem_fragmentation_ratio in INFO memory shows resident memory against used memory, and activedefrag yes compacts in the background on jemalloc builds. maxmemory is optional and limits data, not the process, so resident memory can exceed it. How do I fix Redis "OOM command not allowed when used memory > maxmemory"? covers what happens at that limit.
Memcached trades some waste per item for a stable footprint. Redis trades per-key overhead and a footprint you must monitor for flexible structures.
Data model
A Memcached item is a key (up to 250 bytes, no whitespace or control characters), a value of opaque bytes, a 32-bit flags field for the client's use, an expiry and a CAS value. The commands store, fetch, delete, append to, increment and compare-and-swap whole values. The server never interprets them.
Redis keys and values are binary-safe, and values have types: strings, hashes, lists, sets, sorted sets, streams, bitmaps, HyperLogLog and geospatial indexes, with JSON, time series and probabilistic structures built in from Redis 8. Commands operate on parts of a value on the server: increment one hash field, add to a sorted set, pop from a list. For a cache this matters when an object has a field you update often, or when the "cache" is really a derived structure such as a leaderboard. For whole-object caching it does not matter.
Expiry and eviction
Memcached
Redis
TTL granularity
Seconds, accurate to about one second
Milliseconds
TTL encoding
Relative seconds up to 30 days; larger values are read as an absolute Unix timestamp
Relative (EX, PX) or absolute (EXAT, PXAT)
Removing expired items
On access, and by the LRU crawler thread
On access, and by an active cycle that samples keys with a TTL
The 30-day rule is a classic trap: a Memcached TTL of 2,592,001 seconds is read as a timestamp in 1970, so the item is expired the moment it is stored.
When memory is full, Memcached evicts by default. Its LRU is segmented into HOT, WARM and COLD queues per slab class: new items enter HOT, items read again move to WARM, and eviction takes from the tail of COLD, so an item fetched once by a scan does not push out items that are read repeatedly. -M turns eviction off and returns an error instead.
Redis refuses writes by default (noeviction) and has one policy per instance, chosen with maxmemory-policy. It keeps no ordered list: each eviction samples maxmemory-samples keys (default 5) across the whole keyspace and removes the best candidate, by idle time for LRU or by a decaying frequency counter for LFU. The policies are covered in Redis caching: how it works and how to set it up properly.
Item size limits
Memcached rejects items over 1 MB (SERVER_ERROR object too large for cache) unless you raise -I, which accepts 1 KB to 1 GB. A Redis string can be up to 512 MB.
Neither limit is a target. Large values cost network transfer and serialization on every read, and in Redis a multi-megabyte value also occupies the main thread while it is copied or freed. Compress large values, split them, or cache only the parts you read.
Persistence
Memcached has no persistence. A crash or an unclean restart empties the cache. Two features are sometimes mistaken for it. Warm restart (-e with a file on a RAM disk, version 1.5.18 and later) lets the cache survive a clean stop and start of the process, for example a binary upgrade. It does not survive a crash, and writes issued while the process is down are missed. Extstore (1.6.0 and later) keeps keys in RAM and moves values to flash. It is a capacity feature.
Redis offers RDB snapshots and an append-only file, separately or together. For a cache this buys a warm start. It costs a fork() for each snapshot or AOF rewrite, with copy-on-write memory growth during it. Many pure caches turn both off.
Replication, high availability and clustering
Memcached servers do not know about each other. The client library holds the server list and maps each key to a server, normally with consistent hashing so that adding or removing a server remaps only a share of the keys. When a node dies, its share of the cache is gone and those requests fall through to the database until the entries are rebuilt elsewhere. Every client must use the same server list and hash function, or two clients will look for the same key on different servers.
Since 1.6.23 Memcached also includes a proxy mode, enabled at build time, in front of pools of servers. Clients connect to the proxy, which routes by key prefix, hashes within a pool and can send a request to several pools.
Redis replicates asynchronously from a primary to replicas. Sentinel monitors a primary and promotes a replica when it fails. Redis Cluster shards keys across primaries using 16384 hash slots, redirects clients to the right node, and promotes replicas itself. Two properties follow. A failover can lose writes that the old primary acknowledged but had not yet replicated. And multi-key commands in a cluster work only when all keys hash to the same slot, which you arrange with hash tags ({user:42}:profile, {user:42}:settings).
Protocol and client features
Redis clients speak RESP2 or RESP3. On top of basic commands the server provides:
Pipelining: many commands in one round trip.
Transactions (MULTI/EXEC, with WATCH for optimistic locking).
Lua scripts and functions (functions from Redis 7.0), executed atomically on the server.
Pub/sub and streams for messaging.
Client-side caching (CLIENT TRACKING, Redis 6 and later): the server remembers which keys a client read and pushes an invalidation when one changes, so the client can keep hot values in its own memory.
Memcached has the classic text protocol and the newer meta protocol (mg, ms, md, ma, mn). The binary protocol is deprecated. Multi-key get and pipelining are supported. Meta commands return item metadata on request (remaining TTL, time since last access, whether the item was hit before, the CAS value), support quiet mode for efficient pipelining, and build stampede control into the server. Against a local Memcached:
ms product:42 5 T300 # set a 5-byte value with a 300 second TTL
hello
HD
mg product:42 v t # value and remaining TTL
VA 5 t300
hello
md product:42 I T30 # invalidate: mark stale, keep serving for 30 seconds
HD
mg product:42 v c # first client after the invalidation
VA 5 c3 X W
hello
mg product:42 v c # every other client
VA 5 c3 Z X
hello
X means the item is stale. W tells exactly one client that it won the right to recache, and Z tells the others that someone already has. The N flag on mg does the same for a miss, and R hands out a win when the remaining TTL drops below a threshold. That is stale-while-revalidate and single-flight rebuilding without a lock key. In Redis you build it yourself with SET NX and Lua. See Redis cache patterns compared.
Operations and observability
Memcached has few settings. stats gives hits, misses, evictions and connection counts, stats slabs and stats items break memory and evictions down per slab class, stats settings shows the running configuration, and watch streams events such as evictions. Authentication is SASL or an auth file (-Y), and TLS is available when built with it (1.5.13 and later). There is no per-user authorization. UDP has been off by default since 1.5.6. Keep Memcached off public networks.
Redis has more to observe and more to configure: INFO sections for memory, stats, replication and per-command statistics, SLOWLOG, the latency monitor, MEMORY USAGE and redis-cli --bigkeys. Most settings change at runtime with CONFIG SET, and ACLs define users with allowed commands and key patterns. The larger surface means more to manage: eviction policy, persistence, replication buffers, fork behavior and slow commands.
Licensing and forks
Memcached is BSD 3-Clause licensed.
Redis was BSD 3-Clause through version 7.2. In March 2024 Redis announced that, starting with 7.4, it would be dual-licensed under the Redis Source Available License v2 (RSALv2) and the Server Side Public License v1 (SSPLv1). Neither is an OSI-approved open source license. With Redis 8, the GNU AGPLv3, which is OSI-approved, was added as a third option, so Redis 8 and later can be used under RSALv2, SSPLv1 or AGPLv3 at your choice. Versions 7.2 and earlier remain BSD.
Valkey is the fork created in response. The Linux Foundation announced it in March 2024, it continues from Redis 7.2.4, and it is BSD 3-Clause. It is protocol-compatible with Redis 7.2 and has developed separately since, so features added to Redis after 7.2 are not automatically in Valkey, and the reverse. Should I switch from Redis to Valkey, and how hard is it? covers the migration.
Managed services
Both are widely available as managed services. AWS ElastiCache offers Valkey, Memcached and Redis OSS engines, Google Cloud Memorystore offers Valkey, Redis and Memcached, and Azure offers managed Redis. Managed Redis and Valkey generally include replication, automatic failover and backups. Managed Memcached is a set of independent nodes, because that is all Memcached is.
Recommendation
Choose Memcached when the job is a disposable key-to-blob cache and nothing more, you want one process to use a whole machine without sharding, and you value a fixed memory footprint with very little to operate. Its meta protocol also gives you stampede protection that Redis leaves to the application.
Choose Redis or Valkey when you need at least one of: data structures updated in place, atomic multi-step operations, a warm replica or automatic failover, persistence across restarts, pub/sub or streams, server-pushed invalidation for client-side caching, or per-user access control. Also choose it when the same team will soon want rate limiters, queues or locks: running one system well is cheaper than running two.
Between Redis and Valkey, the commands a cache uses are the same. Decide on license, provider support, and any dependence on features added to either project after 7.2.
Do not choose on benchmark headlines. Results depend on value size, connection count, pipelining and thread configuration more than on the product, so measure your own workload.
Common mistakes
Running Redis as a cache with default settings. No maxmemory and noeviction is a datastore configuration.
Using different hash functions or server lists across Memcached clients, so services cannot see each other's entries.
Hashing with hash(key) % N. Adding one server then remaps nearly every key. Use consistent hashing.
Treating a Redis replica as a guarantee. Replication is asynchronous.
Assuming "Redis is open source" or "Redis is not open source" without checking the version. 7.2, 7.4 and 8 have three different license situations.
Checklist
List what you need beyond get, set and delete. If nothing, Memcached is a serious candidate.
Decide what a node failure may cost: a cold share of the cache (Memcached) or a failover with a warm replica (Redis).
Check value sizes against 1 MB, and measure memory per item with real data in both.
With Redis, set maxmemory and an eviction policy, and decide on persistence deliberately.
With Memcached, use consistent hashing with identical client configuration everywhere, and watch evictions per slab class.
Confirm the license of the exact version, and which engines and versions your provider runs.
Load-test with your own value sizes, concurrency and client library.
What to cache, string vs hash, key naming, how TTLs and expiry work, maxmemory and every eviction policy, a working cache-aside example, and the commands to measure and secure a Redis cache.
Writes fail and reads still work. Understand Redis eviction policies, find what's filling memory, and pick the right fix depending on whether Redis is a cache or a datastore.