Failure modes & the real LSM read path
Published
Why this, first. The senior gap is knowing how a Bloom filter earns its keep in a real database — and how it silently rots if you ignore load.
Failure mode 1 — saturation: the FP rate is not constant
Size for n; keep inserting past it; bits fill, and the FP rate climbs toward 1.
(1−e−kn/m)k
Failure mode 2 — the LSM payoff, and when it breaks
When the filter saturates, “no” nearly vanishes — the guard stops guarding and reads hit disk.
Failure mode 3 — bad hashes, no deletes, unbounded growth
| Failure mode | Symptom | Fix |
|---|---|---|
| Weak / correlated hashes | FP rate worse than theory | Independent, well-mixed hashes 2 |
| No-delete (standard filter) | Deleted keys stay “present” | Periodic rebuild or counting variant 2 |
| Unbounded growth | Set outgrows the array | Re-size / shard / scalable filter |
A standard Bloom filter cannot remove a bit safely — clearing it could un-set a bit shared by a live key.
A Bloom filter’s accuracy decays with load — size for peak n and rebuild, or it
silently rots.
Retrieval practice
Answer from memory — feedback is instant.
Q1. In an LSM read, a Bloom "no" lets the engine…
Q2. Skipping disk on "no" is always safe because…
Q3. As the filter fills past its design n, the FP rate…
Q4. A standard filter can't delete keys, so over time…
Rehearse out loud
Interviewer: "You're paging on a rising false-positive rate in a Bloom-fronted cache in production — diagnose and fix." ~60 seconds, from memory — then reveal and compare.
First hypothesis: saturation — the set grew past the n
we sized for, so more bits are set and the FP rate climbed toward 1 (it’s
(1−e−kn/m)k
, not a constant). I’d confirm by comparing current item count and bits-set fraction against
the design. Second:
no-delete staleness — a standard filter can’t remove keys, so
churned/deleted entries accumulate and keep matching. Fix: rebuild the
filter from the live set on a schedule (or compaction), resize for the new
peak
n, and if deletes are frequent switch to a counting variant.
I’d also verify the hashes are independent — correlated hashes push FPR above theory.
Why this scores: names saturation as primary cause, ties it to the load-dependent FPR formula, and adds the no-delete staleness angle with rebuild/resize/counting fixes.
I’m your teacher — ask me anything. Want the math behind the saturation curve, how RocksDB sizes its per-SSTable filters, or the next lesson (Staff: variants & scale)? Say so.
Primary source (read this next)
📖
RocksDB Wiki — Bloom Filter1 . The per-SSTable filter and the disk-skip read path, in production.
Interview — pick your bar
Answer out loud in ~60s, then reveal. Core = recall · Senior = trade-offs & failure modes · Staff = synthesis under ambiguity · System Design = open design round (a different axis, not a harder level).
In an LSM-tree read, what does a Bloom "no" let the engine do, and why is that safe?
What does a Bloom "maybe" cost in the LSM read path?
k bits are set, so the engine must actually read the SSTable and verify the key → that read is the slow path (microseconds–milliseconds, orders of magnitude over the RAM check) → some "maybes" are true hits; a few are false positives that read disk and find nothing — wasted I/O bounded by the FP rate → so the filter doesn't make hits faster, it makes the common miss free → the value scales with how miss-heavy the workload is.As a filter fills past its design n, what happens to the false-positive rate?
(1−e−kn/m)k, which rises with n → as you insert past the n you sized for, more bits get set, so more random lookups trip "all bits set" → the rate climbs steadily toward 1 → crucially nothing errors — the structure keeps answering, just less accurately → this is the saturation failure mode.Why can't a standard Bloom filter delete keys, and what does that cause over time?
You're paged on a rising false-positive rate in a Bloom-fronted cache in production. Diagnose and fix.
n, so bits-set fraction and the FP rate climbed (it's (1−e−kn/m)k, not a constant); confirm by comparing current item count and bits-set fraction to design → second no-delete staleness — churned/deleted keys accumulate and keep matching → fix: rebuild from the live set on a schedule (or on compaction), resize for the new peak n, switch to a counting variant if deletes are frequent → also verify hashes are independent — correlated hashes push FPR above theory → structure: name the primary cause, tie it to the load-dependent formula, then list rebuild/resize/counting fixes.What happens to the LSM read path once the filter saturates — why is it operationally dangerous?
Your measured FP rate is worse than the formula predicts. What are the likely causes?
n exceeds the design n, so the real bits-set fraction is higher than assumed → staleness — no-delete churn means the live set is smaller than the inserted set, inflating effective load → wrong k — too many hashes for the array fills it faster, raising FPR → the formula is also a slightly optimistic approximation (independence assumption), so expect a small gap even when everything's right → diagnose by measuring bits-set fraction and comparing to expected for the current n.Why is the Bloom filter's silent degradation harder to operate than a loud failure?
Counting Bloom to enable deletes vs scheduled rebuild — make the principal call.
How would you design alerting so a Bloom-fronted read path can't silently rot in production?
n vs design n, and a sampled measured FP rate (probe with known-absent keys) → alert when bits-set fraction crosses the design threshold or measured FP exceeds target, before disk reads spike → track verify-path hit rate: a falling ratio of true-hits among "maybes" signals saturation or staleness → tie alerts to an automated/runbook response: rebuild, resize, or shard → the staff move is making the silent decay observable and giving it a remediation path, so growth is a planned capacity event, not an incident.An LSM store's read latency is creeping up over weeks. Walk your root-cause approach and where the Bloom filter fits.
- Map the path: where does "no" save the expensive work, and how often is the answer "absent"?
- Size from peak
nand apchosen from the cost of the slow path. - Saturation plan: rebuild/shard thresholds on bits-set fraction, not on incidents.
- Churn plan: rebuild, time-windowed roll, or counting/cuckoo if deletes are real.
- Hash quality: independent, well-mixed hashes; validate measured vs theoretical FPR.
- Observability: bits-set fraction, measured FP rate, disk-skip / verify-hit rate.
- Failure modes: saturation, staleness, correlated hashes, read-amp from too many segments.
Design the Bloom layer for a read-heavy LSM key-value store so it stays healthy as data grows for years.
p (tighter for hot upper levels; numbers illustrative), so the FP rate per table stays at design even as total data grows → the certain "no" skips the SSTable read; "maybe" reads and verifies → guard against read-amp: as levels multiply, a query probes many filters, so monitor disk-skip rate and compaction backlog together → keep filters in RAM, spill/cache cold ones, budget aggregate bits/key across the fleet → observability: per-level bits-set fraction, measured FP, fraction of lookups skipping disk → trade-off: RAM for filters vs I/O saved on misses; tune per level rather than one global setting.Design a Bloom-fronted dedup cache that won't silently rot — given the set churns and grows.
n plus headroom → instrument bits-set fraction, measured FP, and verify-hit rate, with alerts that fire before the FP rate is bad and trigger a rebuild/roll → pick p from the cost of the verify path × query rate → trade-off: rebuild simplicity + brief staleness vs counting's 4× RAM for live deletes; favor rolling rebuilds when entries naturally expire, counting only when they don't.📄 Keep handy: Bloom filters reference
Next ▸ Lesson 4: Variants & scale (Staff) — plannedThese notes reflect my current understanding and are updated as I learn, build, and discover better explanations.