The indivisible key
Simulate a modelNeeds Python with NumPy
Problem
Partitioning by key sends every event of a key to one instance, so a key carrying a large share of the traffic sets a floor on the busiest instance's load that no number of instances can lower: the maximum speedup is 1 ÷ that share. Salting splits the hot key into sub-keys spread over several instances, which works for aggregates that can be combined afterwards (counts, sums) and not, without extra work, for those that cannot (a median).
Simulate hash partitioning with 8, 32 and 128 instances. Give one key 30% of the traffic and spread the rest over 100,000 keys. Compute the load of the busiest instance as a share of the total. Then apply salting (8 sub-keys for the hot key) and recompute. Show that the salted result is valid for count but not for the median without extra work.
The question. Does adding instances lower the busiest instance's share, and what does salting change?
Predict
The busiest instance never goes below 30% of the traffic, whatever the number of instances, so the maximum speedup is about 1 ÷ 0.3 = 3.3. With salting, the hot key's load is spread and the busiest instance falls close to the average.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
the-indivisible-key.py # /// script # requires-python = ">=3.10" # dependencies = ["numpy"] # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """The indivisible key. Hash partitioning over 8, 32 and 128 instances when one key carries 30% of the traffic and the rest is spread over 100,000 keys: the busiest instance's share of the total. Then the hot key salted into 8 sub-keys. Finally a count and a median over the salted sub-keys, combined without a second pass. """ import argparse, json import numpy as np p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--events", type=int, default=2_000_000, help="Events.") p.add_argument("--hot-share", type=float, default=0.3, help="Share of traffic on the hot key.") p.add_argument("--keys", type=int, default=100_000, help="Other keys.") p.add_argument("--salts", type=int, default=8, help="Sub-keys for the hot key.") p.add_argument("--seed", type=int, default=0, help="Random seed.") p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() rng = np.random.default_rng(args.seed) hot = rng.random(args.events) < args.hot_share keys = np.where(hot, 0, rng.integers(1, args.keys + 1, args.events)).astype(np.uint64) salt = np.where(hot, rng.integers(0, args.salts, args.events), 0).astype(np.uint64) def h(x): z = x * np.uint64(0x9E3779B97F4A7C15); z ^= z >> np.uint64(31); z *= np.uint64(0xBF58476D1CE4E5B9); return z ^ (z >> np.uint64(29)) rows = [] with np.errstate(over="ignore"): for n in (8, 32, 128): plain = np.bincount((h(keys) % np.uint64(n)).astype(np.int64), minlength=n) salted = np.bincount((h(keys * np.uint64(1000) + salt) % np.uint64(n)).astype(np.int64), minlength=n) rows.append({"instances": n, "busiest_pct": plain.max() / args.events * 100, "salted_busiest_pct": salted.max() / args.events * 100, "even_pct": 100 / n, "max_speedup": args.events / plain.max(), "salted_max_speedup": args.events / salted.max()}) values = rng.lognormal(3, 1, args.events) hv, hs = values[hot], salt[hot] parts = [hv[hs == s] for s in range(args.salts)] cnt_ok = sum(len(x) for x in parts) == len(hv) true_med = float(np.median(hv)); med_of_meds = float(np.median([np.median(x) for x in parts])) summary = {"count_combines": "yes" if cnt_ok else "no", "true_median": true_med, "median_of_medians": med_of_meds, "median_err_pct": (med_of_meds / true_med - 1) * 100, "cap": 1 / args.hot_share} for r in rows: print(f"{r['instances']:>3} instances: busiest {r['busiest_pct']:5.1f}% (even {r['even_pct']:.1f}%), max speedup {r['max_speedup']:.1f}x; salted ×{args.salts}: busiest {r['salted_busiest_pct']:5.1f}%") print(f"salted count adds up exactly: {summary['count_combines']}; median of the {args.salts} partial medians {med_of_meds:.2f} vs true {true_med:.2f}") if args.json: with open(args.json, "w") as f: json.dump({"summary": summary, "tables": {"instances": rows}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run the-indivisible-key.py uv run the-indivisible-key.py --help # every knob
| Knob · default | What it changes |
|---|---|
| --events2,000,000 | Events. |
| --hot-share0.3 | Share of traffic on the hot key. |
| --keys100,000 | Other keys. |
| --salts8 | Sub-keys for the hot key. |
| --seed0 | Random seed. |
Our run
Busiest instance, share of all traffic
| Instances | Busiest | Even share | Max speedup | Busiest, salted |
|---|---|---|---|---|
| 8 | 38.8% | 12.5% | 2.58× | 16.4% |
| 32 | 32.2% | 3.13% | 3.11× | 9.69% |
| 128 | 30.6% | 0.781% | 3.27× | 8.05% |
8 instances: busiest 38.8% (even 12.5%), max speedup 2.6x; salted ×8: busiest 16.4%
32 instances: busiest 32.2% (even 3.1%), max speedup 3.1x; salted ×8: busiest 9.7%
128 instances: busiest 30.6% (even 0.8%), max speedup 3.3x; salted ×8: busiest 8.1%
salted count adds up exactly: yes; median of the 8 partial medians 20.03 vs true 20.05What we found
The busiest instance carried 38.8% of the traffic with 8 instances and 30.6% with 128, never below the hot key's share; the speedup topped out at 3.27×. Salting into 8 sub-keys brought the busiest instance to 16.4% with 8 instances and 8.05% with 128: close to even at 8, still far above the even share at 128, since 8 sub-keys spread the hot key over at most 8 instances. The salted count added up exactly (yes); the median of the partial medians was 20 against a true 20, close here only because the salts split the values evenly at random.
Read it
The number of sub-keys sets how far salting spreads a hot key, so it has to grow with the cluster. And salting moves the problem into the combine step: sums and counts merge exactly, medians and distinct counts need their raw values or a mergeable sketch.