jonnify·redis /redis-patterns/production-operations/uber-resilience/staggered-sharding

R8.05.1 · Uber: resilience & staggered sharding — dive 1

Staggered sharding: misalign the failure domains.

Uber sharded its Redis cache by a scheme deliberately different from the database’s, so when a Redis cluster goes down its missed traffic spreads across many database shards instead of concentrating on one hot shard.

Grounding: the Uber case study (How Uber Uses Integrated Redis Cache) for the sharding scheme · the BCS echo — the {q} hashtag and CRC16 slot from R8.03, not re-derived here. Valkey is the BCS engine; Uber’s Redis and Docstore are the case study.

One Redis cluster goes down. Toggle the two sharding schemes to see where its missed load lands — aligned schemes concentrate it on one hot shard; staggered schemes spread it.
Redis cluster kdown — misses fall through select a scheme to route the missed load DB shard 0 DB shard 1 DB shard 2 DB shard 3 a hot shard is a correlated failure; spread load is survivable
readout · Select a scheme. Aligned: cluster k fronts shard k, so all of k’s missed load lands on one shard — a hot shard. Staggered: k’s keys scatter by the database’s own scheme across many shards — the load fans out.

Static facts — aligned → one Redis cluster maps to one DB shard, so its failure strikes one shard · staggered → the two schemes are independent, so a cluster’s keys disperse across many shards.

§1

The aligned-sharding trap

ByteByteGo · the failure model

Put a cache in front of a database and the cache absorbs the read load. The question staggered sharding answers is: when a piece of the cache fails, where does its load go?

The naive design shards the cache the same way the database is sharded. It feels clean — Redis cluster k fronts Docstore shard k, the mapping is one to one, a key’s cache home and its database home are found the same way. The problem appears only on failure. If Redis cluster k goes down, every read that would have hit it now misses and falls through to Docstore — and because the sharding is aligned, all of that traffic lands on the one Docstore shard k that backed it. That shard now carries its own normal load plus the entire missed load of the cache cluster in front of it.

It becomes a hot shard: an overloaded database shard, a correlated failure that can take the database shard down too, which makes more of the cache miss, which piles more onto the shard. Aligned sharding makes the cache and the database fail together, one region of the keyspace at a time.

The danger is not the cluster going down — that is expected and survivable. The danger is the missed load all landing on one place, because the two stores are sharded the same way.

§2

Misalign the schemes

ByteByteGo · staggered sharding

Uber’s design refuses the alignment. The Redis cluster is sharded by a scheme different from the database’s, so the set of keys held by one Redis cluster does not correspond to the set of keys on one database shard. When a Redis cluster goes down, the keys it held are scattered — by the database’s own scheme — across many database shards. The missed load fans out instead of concentrating. No single Docstore shard inherits the whole failure; each absorbs a fraction it can carry.

The principle is general: decorrelate the failure domains. A cache cluster and a database shard are two failure domains; if their boundaries line up, a failure in one maps directly onto a failure in the other. Stagger the boundaries — make the two sharding functions independent — and a failure in one domain disperses across the other instead of striking a single matching unit.

aligned
Redis cluster k fronts DB shard k. A clean map — and a hot shard on failure.
staggered
The two sharding functions are independent. A cluster’s keys disperse across many DB shards.
the cost
No tidy one-to-one map between a cluster and a shard — which is exactly what disperses the load.
the gain
A cache failure becomes a fraction of extra load on each of many shards, not an outage on one.

Two schemes deliberately out of step. The cost is the loss of a tidy map; the gain is that a cache cluster’s failure can never concentrate into a hot shard.

§3

The BCS echo — placement is the hashtag

echo_mq/keyspace.ex · the {q} hashtag

The BCS bus places keys with a scheme too, and it is worth being precise about what that scheme is and is not. EchoMQ pins a queue’s keys to one Valkey hash slot with a hashtag. Every key of a queue is built as emq:{q}:<type> — the queue name sits inside the braces — and Valkey hashes a key only over the substring between the first { and }. So every emq:{orders}:* key hashes to the same slot, one of 16384, decided by the queue name. EchoMQ.Keyspace computes the slot client-side:

# echo/apps/echo_mq/lib/echo_mq/keyspace.ex — the result derived in full in R8.03
def slot(key) when is_binary(key), do: rem(crc16(hashtag(key), 0), 16384)

The known vector on disk is slot("123456789") == 12739. The full derivation — CRC16 over the braced substring, mod 16384, the cluster-spec algorithm — is R8.03.1; this dive reuses the result, it does not re-derive it.

The connection to staggered sharding is a contrast, not an equivalence. Uber decorrelates two schemes — the cache’s sharding and the database’s are deliberately different, so a cache failure disperses across database shards. EchoMQ correlates one scheme on purpose — a queue’s keys are deliberately pinned to one slot by the {q} hashtag, so a multi-key Lua script over one queue is always a single-slot operation and the queue’s state is co-located.

Pick a key. The slot is a function of the braced substring only — so every key of one queue gathers on one slot.
readout · Pick a key. The hashtag is the substring between the first { and }; the slot is CRC16(hashtag) mod 16384. Two keys of the same queue share a hashtag, so they share a slot.

Static facts (the same queue gathers) — {orders} → both orders keys land on one slot · {cm} → a different queue, a different slot · the braces, not the whole key, decide placement.

the application · placement decided by the scheme

The hashtag that gathers a queue

A queue’s keys all share the braced tag, so they hash to one slot — the opposite placement choice to Uber’s, made with the same deliberateness, verbatim from the keyspace:

echo/apps/echo_mq/lib/echo_mq/keyspace.ex · co-location by hashtagfrozen
# every key of one queue lands on one slot
def queue_key(queue, type), do: ["emq:{", queue, "}:", type]

# the slot is a function of the braced substring — no registry
def slot(key), do: rem(crc16(hashtag(key), 0), 16384)

Notes on ValkeyValkey divides the keyspace into 16384 hash slots; a key’s slot is CRC16(key) mod 16384, and a {...} hashtag pins every key sharing the tag to the same slot. That co-location is what keeps a multi-key Lua script over one queue legal — every key lands on one slot, so there is no CROSSSLOT error. The algorithm and the hashtag rule are the engine’s own: valkey.io/topics/cluster-spec.

the pattern

Stagger the cache’s sharding scheme against the database’s so the failure domains do not line up — a cache cluster’s failure disperses across many DB shards instead of striking one as a hot shard.

its EchoMQ application (a contrast)

The {q} hashtag deliberately gathers a queue’s keys onto one of 16384 slots — the opposite choice, for atomic multi-key Lua. Both are placement decisions the sharding function encodes on purpose.

Uber spreads to isolate a failure; echo gathers to keep a script atomic. The shared lesson is that placement is a design choice the sharding scheme encodes — decide where keys land, and decide it for the failure and consistency properties you want.

§4

Recap — placement is a design choice

next → R8.05.2

Staggered sharding is one move with a clear root: do not let the cache’s failure domains line up with the database’s. Shard the cache by a different scheme, and a cache cluster’s failure disperses across many database shards instead of striking one as a hot shard. The BCS bus makes the opposite placement choice with the same kind of deliberateness — the {q} hashtag gathers a queue’s keys onto one slot for atomic scripts. The next dive stops the bus from hammering a node that is failing: the sliding-window circuit breaker, and the connector’s backpressure cousin.

§5

References

sources · related

Sources

Related in this course