Topic 6.1
The Membership Accelerator and Cache Penetration
In one line
Put a Bloom filter of valid IDs in front of the cache and database: "definitely absent" returns 404 immediately, "maybe" continues to Redis and then the database. It neutralises cache penetration from random or nonexistent IDs, where caching can't help because every key is new.
Think of it like this
A receptionist with a list of every employee's name. Visitors asking for someone not on the list are turned away at the desk; only plausible requests are sent upstairs to check if the person is actually in.
Key ideas
- 01
Cache penetration: requests for keys that exist nowhere miss Redis (nothing to cache) and hit the database every time. Random IDs defeat negative caching too, because each ID is new.
- 02
Flow: validate the ID format → Bloom filter: absent → 404 (no cache, no DB) → maybe → Redis (including cached negatives) → database → populate cache (or a short-TTL negative entry on a false positive).
- 03
Math of protection: with 1% FPR, of 1 million requests/sec for random nonexistent IDs, only ~10,000/sec reach Redis and the database, and negative caching absorbs repeats among those.
- 04
Correctness rule: every valid ID must be in the filter before it can be requested (insert into the filter as part of creation), or valid items get false 404s (false negatives).
- 05
Layers work together: WAF and rate limiting stop abusive clients, the filter stops impossible IDs, Redis serves hot data, the database remains authoritative.
Code & diagrams
public Optional<Product> get(long id) {
if (id <= 0) return Optional.empty(); // cheap validation
if (!bloom.mightContain(id)) { // definitely not a product
metrics.counter("bloom.negative").increment();
return Optional.empty();
}
String cached = redis.opsForValue().get("product:" + id);
if ("__none__".equals(cached)) return Optional.empty(); // negative cache
if (cached != null) return Optional.of(json.read(cached));
Optional<Product> p = repo.findById(id);
if (p.isEmpty()) {
metrics.counter("bloom.false_positive").increment(); // observed FP
redis.opsForValue().set("product:" + id, "__none__", Duration.ofSeconds(60));
} else {
redis.opsForValue().set("product:" + id, json.write(p.get()), Duration.ofMinutes(10));
}
return p;
}Interview problem
The problem
Random-ID attack: 10 million requests/sec
GET /product/{id} is attacked with 10 million random product IDs per second; most don't exist and every request reaches PostgreSQL. Design WAF → rate limiter → Bloom filter → Redis → database, explaining false positives, false negatives, cache misses, database fallback, filter rebuilds and each layer's job.
You're given
- 100M real products
- Attack: 10M req/s of random IDs
- Database capacity ~20K queries/s
When it breaks
New products created without updating the filter
What you see
Customers get 404 for newly launched products until the next rebuild: the filter produced false negatives.
Fix & prevent
Update the filter in the create path (or via outbox/CDC with a readiness gate), and fall back to the database for IDs newer than the filter's snapshot.
Explain it without notes
Why doesn't negative caching alone solve cache penetration?
Practice
Compute requests reaching the database for 2M random-ID requests/sec at filter FPR 1% and 0.1%, before negative caching.
Trade-offs
- ↔
A lower FPR costs a little memory and saves a lot of database load; the filter must be kept complete to avoid false negatives.
Done when you can
I can design a layered defence against cache penetration with a Bloom filter at its core.