BetterSWE DigestWhat is BetterSWE?

News · Issue 14

The Cache Stampede That Ate Slack: Inside the 2022 Cascading Failure

Three healthy systems, each doing exactly its job, took Slack down for a morning. Here's how they collided.

Slack systems tipping into failure from one small change

February 22, 2022

A routine upgrade, at exactly the wrong moment

Slack systems tipping into failure from one small change

On the morning Slack's own writeup nicknamed "2-22-22," the service fell over just after 6 a.m. Pacific. The thing that set it off was a maintenance task so routine it had already run twice the week before without anyone noticing.

Consul upgrade (routine rollout)Mcrib (cache control plane)Vitess shards (overloaded)

Nothing "broke" in the usual sense. Three healthy systems, each behaving exactly as designed, interacted at peak traffic and tipped the whole datastore tier into a self-reinforcing overload. It ended up being one of three major Slack outages that year.

The cast

Four systems on one request path

The cache and datastore layers involved in the outage

Slack stores data in Vitess, a system for sharding MySQL across many machines, organized into keyspaces that are each split by a chosen key. The keyspace at the center of this story holds channel membership, sharded by user.

  client boot
      │
  Mcrouter ──consistent hash──▶ memcached fleet
      ▲                              ▲
      │ config file                  │ promotes spares
    Mcrib ──── watches ──── Consul (service discovery)
      │
      ▼
  Vitess  [ shard1 | shard2 | ... | shardN ]
Mcrib rewrites Mcrouter's config as Consul reports which cache nodes are healthy.

In front of Vitess sits a memcached tier. Mcrouter spreads cache reads across the fleet by consistent hashing, and Mcrib is the control plane that keeps Mcrouter's host list current, watching Consul and swapping in spare cache instances whenever a node drops out. Every piece here is doing sensible, careful work.

The spark

When a healthy cache node "leaves"

A cache node cycling out and being replaced by an empty spare

Slack was mid-rollout of a Consul agent upgrade, applied to 25% of the fleet at a time with slow, sequential restarts. Two earlier 25% steps had gone by cleanly, so there was no reason to expect this one to behave differently.

  1. Consul agent restarts
  2. Node deregisters from the catalog
  3. Mcrib treats the cache node as gone
  4. An empty spare is promoted in its place
  5. Cache hit rate drops

When a Consul agent restarts, it briefly deregisters and re-registers the node's services. Mcrib reads that as the memcached node going away and promotes a fresh, empty spare to cover for it, flushing rejoining nodes so no stale data survives. Sensible on its own, but as nodes cycled through this at peak traffic, a real chunk of the cache went cold at once.

The amplifier

One missing key, every shard queried

A scatter query fanning across every shard on a cache miss

When a Slack client boots, it fetches the membership of your Group DM lists. That data lives in the user-sharded keyspace, which is great for "what channels is this user in," but answering "who is in this GDM" means asking every shard, a pattern Vitess calls a scatter query.

  "who is in GDM #42?"
         │
   ┌───┬─┴─┬───┬─── … ───┐
  sh1 sh2 sh3 sh4        shN   ◀ query EVERY shard
         │
  membership is sharded by USER, not by channel
  (a channel-sharded copy exists, but lacks the columns)
Normally invisible: GDM membership is immutable, so it almost always hits the cache.

Because GDM membership never changes, it caches with a long TTL and is a near-guaranteed hit, so the scatter query almost never actually runs. With cache nodes cycling out, a single missing channel forced a full scatter across every shard. Read load climbed superlinearly with the miss rate until most queries against those tables timed out, which meant the cache could never refill. The failure now fed itself.

Metastable

Why it couldn't heal on its own

Throttling new client boots to protect already-connected users

The slow, failing boot requests exhausted resources in the database tier and started blocking users whose clients had already loaded fine. Slack's mitigation was to throttle client boot requests, a deliberate trade that sacrificed reconnecting users to protect the ones already inside.

  1. Throttle boot requests
  2. Errors drop, booted users recover
  3. Raise the limit, too far
  4. DB load spikes again
  5. Cut, then raise in small steps

The first throttle helped, so engineers raised the boot limit again, overshot, and watched load spike right back before dialing it down and creeping up in smaller increments. This is what the postmortem calls a metastable state: once a system tips into cascading overload, it stays there until something external cuts load or adds capacity.

Pausing the Consul restarts, the thing everyone suspected, no longer fixed anything. By then the overload was self-sustaining.

The takeaway

Careful systems can still collide

Why it matters

No single component failed here, which is exactly what makes it worth studying. A cache with a high hit rate had been quietly hiding an expensive scatter query for years, and the moment that cache went cold at peak load the hidden cost surfaced all at once. Cascading failures like this rarely trace to a bug; they emerge from correct components interacting under load, and they don't self-recover, so the levers that matter are shedding load and adding capacity, not chasing a root-cause switch to flip back.

The short version

  • On **Feb 22, 2022**, Slack fell over just after 6 a.m. Pacific when a routine Consul upgrade cycled memcached nodes out of service at peak traffic.
  • A cold cache exposed an inefficient **scatter query** for Group DM membership that hit every Vitess shard, and load climbed **superlinearly** with the miss rate.
  • Timed-out queries kept the cache from refilling, turning the overload into a **self-reinforcing loop**.
  • The system had entered a **metastable state**: pausing the upgrade didn't help, and recovery came only from throttling client boots and easing load back gradually.
  • The lesson engineers can carry: a healthy cache hit rate can mask a query cost you can't actually afford to pay.

Sources

  1. 1.Slack (software)
  2. 6.Slack's Incident on 2-22-22