100,000 sensor readings, 12 key-range shards. Key the readings by timestamp and 100.0% of writes land in one shard while 11 sit idle — a self-inflicted hot spot. Prefix the key with the sensor id and the writes spread to all 12 shards (busiest just 1.024× the mean) — but a time-range query across all sensors now touches all 12 shards instead of 1.
Key-range sharding gives each shard a contiguous slice of the key space — the property that makes a range scan cheap, because adjacent keys live together. But choose a monotonically increasing partition key (a timestamp) and that same ordering turns against you: the maximum key always lives in the last shard, so a write-as-you-go workload funnels 100% of writes into one shard while the other 11 sit idle. A self-inflicted write hot spot. The book's fix is to prefix the key with something high-cardinality — the sensor id — so the write point scatters across the sensor-id range and load spreads evenly (busiest shard drops to 1.024× the mean). The documented cost lands immediately: a time-range query across all sensors, one contiguous scan under the timestamp key, now touches all 12 shards — one scan per sensor. Same 100,000 readings, same 12 range shards; the only thing that changes is which field the key sorts by.
Designing Data-Intensive Applications, 2nd ed. (Kleppmann & Riccomini, 2025), Chapter 7 — Sharding, §"Sharding by Key Range" (the sensor-network example):
The downside of key-range sharding is that it can lead to hot spots if the key [...] is a timestamp, [...] because all writes end up going to the same partition (the one for the current day) [...]. To avoid this problem [...] you could prefix each timestamp with the sensor name so that the [sharding] is first by sensor name and then by time.
The book warns that the fix has a price: "if you want to fetch the values of multiple sensors within a time range, you now need to perform a separate range query for each sensor name." This exercise makes both halves — the hot spot and its cost — into numbers you can predict against.
The surprise is about how key order interacts with write order. Each line notes where it shows up.
shard_by_timestamp / shard_by_sensor_key range routers.random.Random(42). Fixed workload: 100,000 readings, 1,000 sensors, 12 range shards, timestamps strictly increasing within the current month.
Two partition-key choices over the identical stream of 100,000 readings. The shards are range-based both times (each owns a contiguous slice); only the key differs.
timestamp. Shards partition time: shard m owns the contiguous range [m·MONTH, (m+1)·MONTH). Because every reading arrives with the current timestamp, and the current timestamp is always the maximum key seen so far, every write lands in the newest shard. Cheap time-range scans; a guaranteed write hot spot.f"{sensor}#{timestamp}". The key sorts by sensor first, so shards partition the sensor-id space: shard j owns a contiguous slice of the 1,000 sensor ids. The write point is now wherever the sensor lands, and sensors are drawn across the whole id range — so writes spread. Even load; but a cross-sensor time range is no longer contiguous.Both schemes route with a range boundary lookup (integer division for time, a bisect over sensor-id boundaries for the string key). The whole thing is one script, code/key_range_hot_spot.py.
cd study/ddia/ch07
python3 code/key_range_hot_spot.py
Do not read the output yet — make each prediction first.
Predict. 100,000 readings stream in with monotonically increasing timestamps, all inside the current month, across 12 shards that each own one month of time. How many of the 12 shards receive writes, and what share does the busiest hold?
One shard holds everything. Shard 11 owns the current month, the current month holds the maximum key, and every write carries a fresh maximum — so 100.0% of writes hit shard 11 and the other 11 shards never wake up. Buying 12 shards bought exactly 1× the throughput of a single machine.
Predict. Same 100,000 readings, but the key is now f"{sensor}#{timestamp}" and the shards partition the 1,000-sensor id space. Roughly how evenly do the writes spread — and how far is the busiest shard from the mean of 8,333?
All 12 shards busy, none more than 2.4% above the mean. Prefixing with the sensor id moved the write point from "the single newest key" to "wherever this sensor sorts," and 1,000 sensors spread across the id range keep every shard fed. The hot spot is gone.
Predict. Now ask a read: all readings in a 20,001-second window, across all sensors. Under the timestamp key (scheme A) and under the sensor-prefixed key (scheme B), how many of the 12 shards must the query touch?
Scheme A touches 1 shard; scheme B touches all 12. Under the timestamp key the matching readings are contiguous — one range on one shard. Under the sensor-prefixed key they are sorted by sensor, so the same 20,001 readings are smeared across every shard: the query becomes a separate range scan per sensor. The fix for the write hot spot is the cause of the read fan-out.
Key-range shards are contiguous by construction: shard m owns keys in [boundary_m, boundary_{m+1}), and that is exactly what lets a range scan read a span of keys as one sequential sweep. The trouble is that "contiguous by key order" and "hot on sequential writes" are the same coin. A timestamp key is monotonic — every new reading's key is greater than every key written before it — so the write point is pinned to the top of the key space, and the top of the key space is, by the range-partition definition, a single shard: the last one. There is no arithmetic that spreads a stream of ever-increasing keys across contiguous ranges, because "ever-increasing" means "always in the same top range." The 100.0% is not bad luck; it is forced by the two design choices meeting.
Prefixing the key with the sensor id changes what the first sort field is. Now the key is sensor#timestamp, so the shard is chosen by sensor, and the 1,000 sensor ids — drawn across the whole id range — scatter the write point over all 12 ranges. The write hot spot dissolves because the leading bytes of the key are high-cardinality and roughly uniform, which is the general recipe: a key-range write hot spot is cured by making the key's high-order part high-cardinality. But you paid for it in the currency the range scan was denominated in. A "same time window, all sensors" query selects a horizontal slice of (sensor, time) space. Under timestamp keys that slice is one contiguous run (all keys with t ∈ [t0,t1] are adjacent) → one shard. Under sensor#timestamp keys that slice is vertical to the sort order — it crosses every sensor prefix — so its keys are spread over all 12 shards, one range scan each. The same locality you destroyed to spread the writes was the locality that made the cross-sensor scan cheap.
[t0,t1] to span two calendar months and watch scheme A jump from 1 shard to 2 — the contiguity is only as tight as the window, and a year-long query would touch all 12 even under the timestamp key.hash(timestamp) (or prepend a random byte) and re-measure scenario A — the 100.0% hot spot should collapse toward the even spread of scenario B, showing the funnel was a property of sequential keys, not of range sharding.Sources: DDIA 2e, Ch. 7, §"Sharding by Key Range", §"Sharding by Hash of Key".