Memcached is a high-performance, distributed in-memory key-value cache designed in 2003 by Brad Fitzpatrick at LiveJournal. It is the spiritual ancestor of Redis — simpler, multi-threaded, and laser-focused on one task: caching opaque byte strings keyed by short string keys. Memcached has no persistence, no replication, no data types beyond byte strings, and no cluster mode — deliberately, because its design goal is the smallest possible cache primitive.
Memcached servers are independent: there is no replication, no cluster membership protocol, and no persistence. Distribution happens in the client library, which hashes each key to choose a server. Consistent hashing (for example the ketama scheme) means that adding or removing one server remaps only roughly 1/N of the keys instead of nearly all of them. Losing a server is treated as a burst of cache misses, which the application absorbs by reading from the database again.
To avoid fragmentation, Memcached allocates memory in 1 MB pages and divides each page into fixed-size chunks belonging to a slab class. Each class holds items of a size range, with chunk sizes growing by a configurable factor (1.25 by default). An item goes into the smallest chunk that fits, wasting the remainder. Each slab class has its own LRU, so eviction happens within a class: if most memory went to small items early on, large items may be evicted aggressively. Newer versions rebalance pages between classes automatically, which reduces this effect.
sudo apt install memcached
memcached -d -m 1024 -p 11211 -U 0 -l 127.0.0.1 # 1 GB, TCP only, localhost
pip install pymemcache
echo stats | nc -q1 127.0.0.1 11211 | grep -E "get_hits|get_misses|evictions"
import json
from pymemcache.client.hash import HashClient
cache = HashClient([("10.0.0.11", 11211), ("10.0.0.12", 11211)],
connect_timeout=0.2, timeout=0.2, ignore_exc=True)
def get_user(user_id, db):
key = f"user:v1:{user_id}"
hit = cache.get(key)
if hit is not None:
return json.loads(hit)
row = db.fetch_user(user_id) # source of truth
cache.set(key, json.dumps(row), expire=300) # 5-minute TTL
return row
def update_user(user_id, fields, db):
db.update_user(user_id, fields)
cache.delete(f"user:v1:{user_id}") # invalidate, do not update
ignore_exc=True makes a down cache server behave like a miss rather than an error. Deleting on write, instead of setting the new value, avoids races where two writers leave stale data in the cache.
Memcached is a good fit when you need a large, simple, multi-threaded cache for opaque values, such as rendered page fragments, serialized objects, and database query results, and you can rebuild everything from the source of truth. Its multi-threaded design uses many cores on one host, and its small feature set makes behaviour predictable. Choose Redis instead when you need data structures (sorted sets, hashes, streams), persistence, replication, pub/sub, or atomic server-side operations beyond increment and compare-and-set.
-I raises it); large values waste slab space.add, early refresh, or jittered TTLs.-U 0), which has been abused for reflection attacks.