What Facebook's Memcache Taught Me About Systems Thinking

8 min read Original article ↗

A deep dive into Facebook's memcache architecture and the hard lessons it teaches about real-world system design, performance, and failure.


What Facebook’s Memcache Taught Me About Systems Thinking

“The probability of reading transient stale data is a tunable parameter.” - Scaling Memcache at Facebook (NSDI, 2013)

There’s a moment in every engineer’s life when a seemingly simple component like a cache, suddenly becomes the most complex piece in the stack. For me, that moment arrived reading Facebook’s paper on scaling Memcache. I didn’t expect a key-value store to challenge my understanding of systems architecture. But this paper wasn’t about cache keys or TTLs. It was about what happens when infrastructure hits the limits of scale, physics, and human reliability.
What follows isn’t a summary. It’s a set of systems insights that stayed with me, principles that go deeper than code and that I now see everywhere.

1. Keep the Core Dumb. Push Complexity Outward.

Facebook’s memcached servers are intentionally brain-dead. They don’t coordinate. They don’t know each other exists. There’s no replication, gossip protocol, or consensus layer.
Instead, all intelligence like request routing, error handling, retries, batching is pushed into the client. The client might be an embedded library, or a proxy called mcrouter, which looks like a memcached server but simply forwards requests based on consistent hashing and configuration state.

At first, this seems backwards. Wouldn’t smart servers reduce coordination overhead? Wouldn’t a central cache service simplify consistency?

But at their scale, the opposite holds true.
By keeping memcached dumb and stateless:

  • They can deploy and upgrade it independently.
  • Failure recovery becomes local: just restart or replace the box.
  • There’s no server-side coordination failure to debug at 2 a.m.

The lesson here is architectural, not technical:

Complexity doesn’t vanish. But if you push it to the edge into clients it becomes easier to version, test, and evolve.

Dumb, fast, and replaceable beats smart and brittle.

2. Systems Thinking Begins Where Feature Thinking Ends

Most of us reach for a cache to “improve performance”. That’s a feature mindset. But Facebook engineers were solving something else: survival at scale.

Example: they used UDP for GET requests. Not TCP. UDP doesn’t guarantee delivery or order. Why use it?
Because:

  • It has less overhead per connection.
  • It avoids memory-hungry TCP state on the server.
  • It allows each thread to talk directly to memcached with minimal coordination.

Yes, they dropped 0.25% of packets. They didn’t retry. They just treated those as cache misses and moved on. At a billion QPS, a few dropped packets don’t matter. A congested server does. Another example: leases. A lease is a token that a client gets on a cache miss. Only that client is allowed to re-populate the value. Everyone else waits briefly.

Why? Because without this, when a hot key is evicted, thousands of clients might simultaneously hit the database to fetch and repopulate it. That’s called a thundering herd. You don’t want that during peak load.
Leases rate-limit writes back into cache. They also prevent stale sets, cases where two clients race to set different values for the same key, and the loser overwrites the winner.

These are not elegant solutions. They are pragmatic design decisions. They’re what you build when you’re no longer solving “how do I cache this” but “how do I keep the backend alive.”

Systems thinking asks not “what’s ideal”, but “what fails gracefully, what scales, and what I can fix under pressure.”

3. Consistency Is a Dial

We’re taught to think of consistency as sacred: the database is right, and the cache must reflect it. But Facebook intentionally treats consistency as a performance lever.

Their memcache infrastructure accepts that cached data can be stale, duplicated, or missing, as long as the user experience doesn’t suffer.

How do they do it?

They treat the cache as discardable. The database is the source of truth. Cache can be evicted, expired, or wrong, as long as the database eventually corrects it.

They even allow slightly stale values to serve requests after deletes. Instead of immediately purging deleted keys, they move them to a short-lived “recently deleted” buffer. If a client requests that key again soon, it might get a marked-as-stale value and proceed anyway, especially if the data is not user-critical.

In globally distributed systems, they use remote markers. If a region is lagging in replication, a client can mark a key as recently deleted in a remote region. Other clients, on seeing the marker, are redirected to the master region for fresh data.

This isn’t strong consistency. It’s controlled inconsistency. And it works.
Once you realize that consistency can be tuned, like latency or throughput, you start seeing your cache as a system of probabilities, not guarantees.

4. Don’t Just Cache for Speed, Cache for Economics

Facebook’s memcache isn’t a single cache. It’s a collection of memory pools, each tuned to a different access pattern.

Why?
Because not all keys are equal. Some are:

  • Accessed frequently but cheap to miss (e.g., user online status).
  • Rarely accessed but expensive to compute (e.g., machine learning model outputs).
  • Frequently updated and high-churn (e.g., news feed orderings).

If you put these into one common pool, the result is negative interference:

  • Hot, volatile keys evict long-lived but valuable data.
  • Rare items get flushed before they’re reused.
  • Hit rates drop, and backend load increases.

So they created segregated pools:

  • A wildcard pool for general use.
  • An app-specific pool for temporary data.
  • A replicated pool for hot data served from multiple servers.
  • A regional pool for large, rarely accessed items shared across clusters.

Each pool can be tuned, size, eviction policy, replication strategy, without hurting the others.
In other words, they cache not just for speed, but for cost-efficiency.
The question isn’t “how do I cache this?” It’s “what is the cost of a miss, and is that cost worth the memory?”

5. Failure Is Not the Exception

At Facebook’s scale, servers fail constantly. Network partitions happen. Machines go dark. It’s not an incident, it’s Tuesday.

The naive approach on cache failure is to rehash keys onto the remaining servers. But in their system, key access is skewed. A single key might account for 20% of a server’s load. Rehashing it elsewhere could overload the new server and create a cascade failure.

Their solution? Gutter pools.
Gutter servers are a small fraction (~1%) of memcached boxes. When a regular server fails, clients don’t retry the same key elsewhere. They redirect the request to the Gutter pool.

If the Gutter doesn’t have the value, it fetches from the DB and inserts it, temporarily. These entries expire quickly.
The point isn’t to serve 100% correct data. The point is to absorb the shock. To give the system a soft place to land while the real servers recover.
This is a lesson in system realism. Don’t try to prevent all failure. Design for graceful degradation.

6. Performance Lives

Most performance advice stops at averages: reduce latency, increase QPS. But real systems hurt in the tail latencies, the 95th, 99th, 99.9th percentiles.

Facebook found:

  • A popular page triggers hundreds of memcache GETs.
  • A slow server or delayed response creates fan-out latency.
  • Incast congestion can flood switches when too many responses arrive simultaneously.

To fix this, they:

  • Used batching to reduce round-trips.
  • Employed sliding windows to limit outstanding requests.
  • Tuned window size to balance between underutilization and congestion.

They also studied memory fragmentation and introduced:

  • Adaptive slab rebalancing: reclaim slabs from underused classes and give them to those under memory pressure.
  • Transient item cache: proactively purge expired items to prevent wasted memory.

All of this tuned not just throughput, but predictability. Not just average speed, but worst-case resilience.
Tail latency isn’t a technical problem. It’s an experience problem. It’s what the user actually feels.

7. Systems Thinking as Infrastructure Literacy

Reading this paper made me realize that systems thinking is not optional. It’s the difference between building something that works and something that survives.

Facebook’s memcache is full of trade-offs I wouldn’t have guessed:

  • Serving stale data to save a DB.
  • Using UDP with dropped packets on purpose.
  • Letting cache invalidations lag, on purpose, to keep systems flowing.

This is the kind of engineering that comes from pressure, not planning. From watching things break and learning what actually matters.
I came away not just with design ideas, but with a new lens on how to build systems. It questions purity, tolerates imperfection, and optimizes for the system, not the module.

I don’t need a billion QPS to apply that.


References:
[1] Scaling Memcache at Facebook