Datastoreshot keyskewshardingpartitioningaggregate metrics

Hot Keys: When Aggregate Metrics Hide a Saturated Node

Sharding distributes keys, not traffic. One product goes viral, forty percent of requests land on one key, and the node holding it saturates while the cluster reports comfortable average utilization across every other node.

Follow the diagnosis

Frame the diagnosis

Performance work starts from a symptom and a signal — never from a resource dashboard.

Diagnostic question
Cluster utilization looks fine on average — why is one node at 100% and latency terrible?
Symptom
p99 latency is bad while p50 is normal; cluster-wide CPU and memory look comfortable; one node in the fleet is pinned and the others are idle.
Signal
Per-node request rate and per-key request rate, compared against each other. Cluster-average utilization is the actively misleading signal: averaging one saturated node with nine idle ones produces a number that looks healthy and describes nothing real.
SymptomSignalMeasurementHypothesisEvidenceRoot CauseChangeValidationRegression Check

Distribution guarantees are about keys, not requests

Consistent hashing and range partitioning both distribute the *keyspace* evenly. Neither promises anything about how traffic distributes across that keyspace, because traffic distribution is a property of user behavior. When one product trends, one celebrity posts, or one tenant is a hundred times larger than the rest, request volume concentrates on a small number of keys and the node holding them absorbs a share of traffic wildly disproportionate to its share of keys.

The arithmetic is unforgiving. With ten nodes and 40% of requests targeting one key, that key's node receives 40% of all traffic while the other nine share 60% — roughly 6.7% each. The hot node is running at six times the fleet average. Cluster-average CPU reports something around 25% and every capacity dashboard reads green while one node drops requests.

Read the panel below for the general lesson about aggregates: an average over units with different loads answers a question nobody asked. The useful readings are the maximum, the ratio of maximum to median, and the distribution itself. Any metric that can be skewed should be published as a distribution, not a mean — the same argument Percentiles: Which One, and How Many Users Is That? makes about latency, applied to nodes instead of requests.

A ten-node cache cluster with one hot key — the aggregate is the only green numberILLUSTRATIVE
SignalValueWhat it tells youVerdict
Cluster average CPU25%Comfortable. This is the number on the capacity dashboard.normal
Max node CPU98%One node is saturated. Six times the fleet average.smoking gun
Max-to-median node request rate6.1xThe skew ratio. Any value well above 1 means aggregates are lying.smoking gun
Top key share of requests40%One key. Sharding cannot help — the key is atomic.smoking gun
p50 latency4 msRequests to the nine healthy nodes are fine.normal
p99 latency780 msRequests to the hot node queue. The tail is entirely one node.suspect
Cluster memory used38%Not a capacity problem in any aggregate sense.normal

Detecting skew before it is an incident

Per-key metrics are the obvious answer and the wrong one at full fidelity: emitting a time series per cache key is the textbook cardinality explosion that takes down the monitoring system (Cardinality: The Label That Took Down Monitoring). The practical approach is bounded. Track per-node rates always, since node count is small and stable, and compute max-to-median as a single skew series — that one number detects the condition without naming the key.

Then, to identify *which* key, sample rather than enumerate: a top-N heavy-hitter sketch (count-min sketch or a simple periodic top-K over a sampled request stream) gives the hot keys at bounded cost. Many caches offer this natively — Redis has key-pattern statistics and slow-log facilities that surface hot patterns without per-key metrics. The DSA machinery underneath is a bounded Min-Heap over a streaming frequency map (Sliding Window with Frequency Map), which is a nice reminder that the algorithms domain is directly load-bearing in operations.

The strongest early-warning signal is the skew ratio trending upward over days. Traffic concentration rarely appears instantly; it usually builds as a product trends or a tenant grows. A max-to-median ratio climbing from 1.2 to 3.0 over a week is a capacity conversation with time to plan, while the same ratio discovered at 6.1 during an incident is an emergency.

Mitigations by mechanism — the first question is whether the hot key is read-only
MitigationHow it worksWorks whenWhat it costs
Local (in-process) cacheEach application instance caches the hot value for a short TTLRead-heavy, brief staleness acceptableStaleness bounded by TTL; memory on every instance
Replicate the hot keyStore copies as key#1..key#N, read a random oneRead-heavy, many nodes availableWrites must fan out to all copies; consistency window
Split the keyShard a counter or list into N sub-keys, combine on readAggregatable values (counters, sets)Reads get more expensive; N must be maintained
Request coalescing at the edgeCollapse concurrent identical requests before they reach the storeBursty read patternsAdds a layer; same trade as Cache Stampede: Everyone Misses at Once coalescing
Dedicated capacity for the hot tenantRoute the outsized tenant to its own nodesMulti-tenant skew rather than single-key skewOperational complexity; routing rules to maintain
Rarely worksAdd more nodesNothing — the key still lives on one nodeMoney; the hot node remains hot
Rarely worksChange the hash functionRedistributes keys, not the traffic to one keyA rebalance, for no benefit

Why adding nodes does not help, and what does

The instinct on seeing a saturated node is to add capacity, and it fails here for a structural reason worth internalizing: a key lives on exactly one node, so scaling the cluster changes which node is hot without changing that a node is hot. Ten nodes to twenty halves each node's share of the *keyspace* and leaves the hot key exactly where it was, on one machine, receiving 40% of traffic. The bill doubles and the p99 does not move.

What works is one of two things: stop sending the requests to that node, or stop the key from being atomic. Local caching in each application instance is the first and is usually the fastest win for read-heavy hot keys — a one-second TTL on a value read ten thousand times per second reduces the store's load by roughly a factor of the instance count times the TTL in seconds, at the cost of up to one second of staleness. Replication of the hot key across N copies with random read selection achieves the same thing inside the store, and moves the cost to write fan-out.

For aggregatable values, key splitting is the durable answer and it is the same technique as sharding a contended counter in Low CPU, High Latency: Lock Contention — the identical structural problem, appearing in the cache layer rather than the database layer. One contended thing becomes N less-contended things, reads pay a combine cost, and the concurrency ceiling rises by roughly N. When neither applies — a single large object that must be strongly consistent and is read enormously — the honest answer is that the access pattern needs to change, which is a product conversation rather than an infrastructure one.

before: one node absorbs it allafter: served locallyon miss, random replicaload spread across the fleet40% of requests → key:viral-productNode 1 — 98% CPUIn-process cache (1 s TTL)key:viral-product#1..#8Nodes 2–10 — ~7% each
UserLLMAgentToolDataDecisionHumanGuardrail

Key points

  • Sharding distributes the keyspace evenly; traffic distribution is a property of user behavior and is frequently extreme.
  • Cluster-average utilization averages a saturated node with idle ones and produces a number that describes nothing real.
  • Track max-to-median node request rate as a standing skew metric — it detects the condition without per-key cardinality.
  • Adding nodes does not help: the hot key still lives on one node, so the fleet grows and the hot node stays hot.
  • Fixes either stop requests reaching the node (local caching, edge coalescing) or stop the key being atomic (replication, splitting).

Follow the diagnosis

The causal chain, hop by hop — and the readings that invite the wrong conclusion.

  1. 1
    Users → symptom: intermittent slow page loads; p50 is 4 ms and p99 is 780 ms, so most requests are fine and some are terrible.
  2. 2
    Cluster → false comfort: average CPU 25%, memory 38% — no aggregate signal indicates a problem.
  3. 3
    Per-node → localization: node 3 sits at 98% CPU while the other nine average 7%; the tail is entirely requests routed to node 3.
  4. 4
    Per-key → cause: one product key accounts for 40% of all requests after a social media post, and consistent hashing places it on node 3.
  5. 5
    Cause → root cause: traffic skew against an evenly distributed keyspace, so adding nodes cannot help — the key is atomic and lives in one place.
What this evidence makes people conclude — wrongly
  • "Average CPU is 25%, we have plenty of headroom." One node is at 98% and dropping requests.
  • "Add nodes to spread the load." The hot key lives on one node regardless of fleet size.
  • "The hash function is unbalanced." It balanced the keyspace correctly; traffic is what is unbalanced.
  • "p50 is fine, so most users are fine." Every user who touches the hot key is affected, and that is 40% of requests.
  • "Rebalance the cluster." Rebalancing moves the key to a different node, which then becomes the hot one.

Measure, fix, validate

An optimization is not finished until the metric that motivated it has moved.

How to measure it
  • • Per-node request rate and CPU, published as max and median rather than as a fleet average.
  • • Max-to-median ratio as a single skew series, alerted on when it trends upward over days.
  • • Top-N hot keys from a sampled heavy-hitter sketch or the store's native key statistics — bounded cost, no per-key time series.
  • • p99 latency split by node, which localizes the tail to one machine immediately.
  • • Per-tenant request share in multi-tenant systems, where skew is usually a tenant rather than a key.
What actually fixes it
  • • Add a short-TTL in-process cache in front of the store for read-heavy hot keys — the fastest win, bounded staleness, no store changes.
  • • Replicate the hot key across N copies read at random, moving the cost to write fan-out and spreading reads across the fleet.
  • • Split aggregatable values into N sub-keys combined on read — the same technique as sharding a contended counter in [[lock-contention]].
  • • Coalesce concurrent identical requests at the edge so a burst becomes one fetch, as in [[cache-stampede]].
  • • Give an outsized tenant dedicated capacity when the skew is per-tenant rather than per-key, and route explicitly.
How you know it worked
  • • Max-to-median node request rate falls toward 1, measured at the same total traffic level.
  • • p99 latency converges toward p50 — that gap was the hot node, so closing it is the proof.
  • • Confirm the previously hot node's CPU dropped and no other node rose to take its place, which would mean the key simply moved.
  • • For local caching, measure the actual staleness users see against the freshness contract, not just the load reduction.
What it costs
  • • Local caching bounds staleness by TTL and multiplies memory use across every application instance.
  • • Key replication spreads reads and makes every write fan out to N copies, with a consistency window between them.
  • • Key splitting raises the concurrency ceiling and makes reads more expensive, with N as a parameter that must be revisited as traffic changes.
  • • Dedicated tenant capacity isolates the problem and fragments the fleet, leaving idle capacity stranded per tenant.
Stop it coming back
  • A standing alert on the skew ratio, which gives days of warning as concentration builds rather than minutes during an incident.
  • Per-node dashboards that show max and median by default, with fleet averages removed from capacity views entirely.
  • A load test with a deliberately skewed key distribution, asserting the system degrades gracefully rather than pinning one node.
  • A periodic review of top-N keys, so a new hot key is noticed by a process rather than by an outage.

Accuracy

Performance numbers are conditional. These are the conditions.

What these numbers depend on
  • ILLUSTRATIVEThe ten-node cluster, 40% key share and 6.1x skew ratio are constructed to make the arithmetic legible. Real skew distributions are usually Zipf-like with a long tail rather than one dominant key.
  • ENVIRONMENT-SPECIFICWhether a hot key saturates a node depends on node capacity, request cost and whether the store is single-threaded per shard — Redis being effectively single-threaded per instance makes hot keys bite considerably harder.
  • WORKLOAD-SPECIFICLocal caching effectiveness depends on read/write ratio and acceptable staleness; it does nothing for a write-hot key.

Misconceptions

Claim
“Consistent hashing prevents hot spots.”
Reality
It distributes keys evenly. If one key receives 40% of traffic, the node holding it receives 40% of traffic no matter how good the hash is.
Claim
“Adding nodes reduces load on the hot node.”
Reality
The key lives on one node. Doubling the fleet halves each node's keyspace share and leaves the hot key exactly where it was.
Claim
“Average cluster utilization is a capacity metric.”
Reality
For anything that can be skewed it is a comfort metric. Maximum and max-to-median are the capacity metrics.

Apply it