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

R8.05 · Production & Operations — module hub

Uber: resilience & staggered sharding.

Uber built an integrated Redis cache in front of Docstore — 40 million reads per second at a 99% cache hit rate. The headline is not the throughput; it is what Uber did so a sick cache never took the database down with it. Three resilience techniques worth taking apart: staggered sharding, the sliding-window circuit breaker, and graceful degradation.

Grounding: the Uber source pack — How Uber Uses Integrated Redis Cache to Serve 40M Reads/Second (ByteByteGo) — for the case study; the as-built echo/apps/echo_wire/lib/echo_mq/connector.ex (the @backoff_min/@backoff_max reconnect, the :disconnected fail-not-replay, the max_pending bound) for the applied half. The bus is Valkey-only; Uber’s own Redis/Docstore is the case study, quoted as theirs.

One integrated cache, three resilience techniques, and a failure to survive. Select a failure to light where it strikes and read the Uber technique beside its echo on the BCS bus.
Redis cacheover Docstore application40M reads/s Docstoremany shards remote regionfailover path staggered shard circuit breaker degrade
readout · Select a failure. The integrated cache spreads a downed cluster’s load across many Docstore shards (staggered sharding), short-circuits requests to an erroring node (the circuit breaker), and falls through to the remote region when one region fails (graceful degradation).

Static facts — staggered shard → misaligned schemes spread a cluster’s failure · circuit breaker → count errors per node, short-circuit a fraction, trip · degrade → a miss or short-circuit falls through to Docstore, slower but served.

§1

The system: an integrated cache over Docstore

ByteByteGo · Uber integrated cache

Docstore is Uber’s distributed database. A read against it is durable and correct but pays the full database cost; at Uber’s scale, repeating identical reads against Docstore is expensive in both latency and load. Uber’s answer was an integrated Redis cache — a layer in front of Docstore that the application reads through transparently. On a hit the value comes back from Redis in memory; on a miss the read falls through to Docstore and the result populates the cache.

The wins were the cache-aside wins at scale: lower latency, less database load, lower cost. A documented use case ran at over 6 million reads per second with a 99% cache hit rate. The title figure is the aggregate: 40 million reads per second served from the integrated cache.

But a cache in front of a database changes the failure model. When the cache is healthy it absorbs the load; when the cache is sick, the traffic it was absorbing lands somewhere — and where it lands decides whether a cache problem stays a cache problem or becomes a database outage. The rest of the module is the three things Uber did so the second never happened.

Uber’s integrated cache is the lens. The reader is building a system on a wire too — EchoMQ, backed by Valkey — so each technique below lands twice: the Uber fact, and the move it becomes on the connector. The thread is same goals, different mechanisms.

§2

The three resilience techniques

ByteByteGo · Circuit Breaker pattern

Staggered sharding — misalign the two sharding schemes. Uber sharded the Redis cluster using a scheme different from the database sharding scheme. The reason is the failure model: if Redis and Docstore were sharded the same way, a single Redis cluster going down would dump all of its missed traffic onto the one Docstore shard that backed it — a hot shard, a correlated failure that can take the database shard down too. By deliberately misaligning the schemes, a single Redis cluster’s load spreads across multiple database shards instead of concentrating on one. Misalignment turns a correlated failure into a spread one.

The sliding-window circuit breaker. When a Redis node is down, every get and set routed to it pays a latency penalty before it fails — and at millions of requests per second, that penalty compounds. Uber added a sliding-window circuit breaker per node: count errors per node in each time bucket, sum the counts over the window’s width, and once the error count crosses a threshold, short-circuit a fraction of the requests to that node. If errors keep accumulating, the breaker trips: no requests go to the node at all until the window passes and the counts age out. The circuit breaker is the canonical resilience pattern — fail fast against a known-bad dependency rather than wait for each call to time out.

Graceful degradation. A Redis miss or a short-circuited request falls through to Docstore. The read is slower than a cache hit, but it is served — degraded, not failed. The combined effect was measured: P75 latency down about 75%, P99.9 latency down over 67%, and latency spikes limited. The same use case at over 6M reads/second failed over successfully to a remote region when its primary region had trouble — the cache going away in one region degraded to the remote path rather than taking the read down.

Three techniques, three goals: do not concentrate a failure, do not keep calling a sick dependency, and stay available when a dependency is gone. Each maps onto an EchoMQ analogue below.

§3

The applied half — the BCS bus

echo_wire/connector.ex · echo_mq/keyspace.ex

Uber’s integrated cache is the lens; the system the reader is building is EchoMQ, backed by Valkey. The honest framing is same goals, different mechanisms. Uber’s three techniques aim at three goals: do not concentrate a failure (staggered sharding), do not keep calling a sick dependency (the circuit breaker), and stay available when a dependency is gone (graceful degradation). The EchoMQ connector reaches the same three goals by different means.

the pattern

Staggered sharding spreads a cache cluster’s failure across many database shards; the sliding-window circuit breaker stops hammering a node that is failing; graceful degradation falls through to the durable store so a read is served even when the cache is gone.

its EchoMQ application

Placement is the {q} hashtag — a queue’s keys hash to one of 16384 slots (R8.03). Not-hammering-a-sick-dependency is the connector’s bounded in-flight depth: max_pending (default 10_000) answers :overloaded. Staying available is fail-not-replay (:disconnected, never re-run) plus supervised reconnect with capped jittered backoff (@backoff_min 100 → @backoff_max 2_000).

One guard holds the parallel straight. The connector’s max_pending → :overloaded bound is backpressure / a bounded in-flight queue, not a circuit breaker. A circuit breaker tracks a dependency’s error rate over a window and stops calling it; the max_pending bound caps how many requests can be in flight at once and refuses the overflow. They share a goal — do not pile work onto a struggling path — but the mechanisms differ: backpressure is a depth limit, a circuit breaker is an error-rate trip. The circuit-breaker concept in this module is Uber’s; echo_mq’s analogues are backpressure, fail-fast, and reconnect-backoff.

echo/apps/echo_wire/lib/echo_mq/connector.ex · the bounded in-flight depthfrozen
# max_pending caps how many pipelines can be in flight; the overflow is refused, not buffered
def handle_call({:pipeline, _}, _from, %{pending_n: n, max_pending: max} = s) when n >= max do
  emit([:emq, :connector, :overload], %{pending: n}, %{label: s.label})
  {:reply, {:error, :overloaded}, %{s | overloads: s.overloads + 1}}
end

The worked consumer is codemojex. Every guess goes through EchoMQ.Connector, so a guess inherits these properties for free: if the wire is saturated the caller gets a fast :overloaded rather than an unbounded queue, and if Valkey drops the socket the in-flight guess fails :disconnected (a fast, honest error the caller can retry) while the connector reconnects with backoff. The game stays responsive under trouble instead of hanging on a sick wire.

Notes on ValkeyA Valkey primary failover surfaces to a client as a -READONLY error: a replica not yet promoted refuses writes with -READONLY You can’t write against a read only replica. The owned wire has no special -READONLY handler — its recovery is the general one: the socket drops and the supervised connector reconnects, re-fencing each time. Treat -READONLY as the Valkey failover signal, not an echo surface — valkey.io/topics/latency.

§4

The three dives

shard → break → degrade

The module follows the resilience arc — place the failure, then break the circuit, then degrade gracefully. Read them in order.

Three dives, one integrated cache: do not let the cache and the database fail together, stop hammering a node that is down, and keep serving when it is gone — the Uber case study and the BCS connector read side by side.

R8.05 is the second of the chapter’s case studies. Return to the Production & Operations chapter to continue, or open the Queue pillar for the depth behind the connector, the bound, and the lease.

§5

References

sources · related

Sources

Related in this course