When a scraper floods an item-lookup API with random, never-issued IDs, why does negative caching stop helping?
answer
- Pays off on repeats only
- Written once, never read
- Rate times TTL
- Tombstones push out hot entries
- Reject before the cache
basics
~20 sNegative entries pay off only when the same missing key repeats. Unique random IDs each miss once, so the database is still hit, and tombstones that are never read again fill memory and can evict hot entries.
solid answer
~50 sA tombstone saves work only on the **second** request for the same key. With unique random IDs there is no second request, so every call still reaches the database and also writes a tombstone nobody reads. Live tombstones grow to roughly **request rate x negative TTL**: about 300,000 at 5,000 requests per second with a 60-second TTL. In a shared cache under LRU they can **evict warm positive entries**, which lowers the hit ratio for real users and adds even more database load. Better defences stop the request before the cache. **Key-format validation** (length, character set, range, or a signature embedded in the ID) rejects malformed or forged IDs almost for free, and a **bloom-filter guard** rejects well-formed IDs that were never issued. Keep tombstones in a **separate, memory-capped pool** so a flood cannot crowd out positives. Throttling the scraper itself is a rate-limiting job.
go deeper
Remember that caching 'not found' only helps when the same missing ID is asked for again, which random IDs never are.
Estimate the live tombstone count as rate times TTL, and explain how those entries push useful data out of a shared cache.
Lay out the layered defence for a scraped lookup API: validation, signed IDs, a filter guard and a capped tombstone pool, each with what it stops.
Judge how far to invest in cache-layer defences versus identifying and throttling abusive clients, given the traffic mix and the ID scheme you can change.
## When negative caching works A **negative entry** (tombstone) caches the fact that a key has no database row, so a repeated request for that key is answered from the cache. It works well when the set of missing keys is **small and repeated**: - deleted items still referenced by popular old links; - a buggy client asking for the same wrong ID in a loop; - a handful of IDs that pages request before the items are published. In each case the first request pays the database cost and later ones hit the tombstone. ## Why unique IDs defeat it A scraper, or an attacker, that generates **random, never-issued IDs** almost never repeats one. For each request: 1. The cache misses, because this key was never seen. 2. The database is queried and returns no row. 3. A tombstone is written, and it will never be read again. The database load is exactly what it would be with no negative caching, and the cache has gained useless entries. The hit rate on tombstones is close to zero, so the mechanism costs something and saves nothing. ## The memory bill At steady state, the number of live tombstones is about **arrival rate x negative TTL**, because each lives for one TTL and new ones keep arriving. Assuming roughly 100 bytes per tombstone (key, sentinel and per-entry overhead; an illustrative figure): | Unique misses per second | Negative TTL | Live tombstones | Approximate memory | |---|---|---|---| | 5,000 | 60 s | 300,000 | 30 MB | | 50,000 | 60 s | 3,000,000 | 300 MB | | 5,000 | 3,600 s | 18,000,000 | 1.8 GB | The last row shows why a long negative TTL is dangerous here: it multiplies memory without saving a single query. On top of the memory, the cache takes one useless tombstone write per request: 5,000 writes per second in the first row, each later expired or evicted. ## How it hurts real users In a shared cache with a fixed memory limit, tombstones compete with real data: - **Eviction pressure.** Under LRU, a constant stream of freshly written tombstones counts as recently used, so any positive entry touched less recently than the newest tombstones becomes an eviction candidate, including **warm entries** real users still need. - **Falling hit ratio.** Real items that were evicted now miss and reload from the database. - **Compounding load.** The database now serves the flood *and* the extra reloads for real traffic. So in this scenario negative caching can make things **worse** than not caching absence at all. ## Defences that stop the request earlier 1. **Key-format validation.** Before touching the cache, reject IDs with the wrong length, characters or prefix. For sequential numeric IDs, also reject values above the highest issued ID. This costs almost nothing and stops malformed and out-of-range floods. 2. **Signed or checksummed public IDs.** If the public ID carries a short keyed hash (a MAC) of the internal ID, a forged ID fails verification without any lookup. Opaque random 128-bit IDs make guessing a *real* item practically impossible, but on their own they do not stop a flood of well-formed fakes. 3. **Bloom-filter guard.** A filter of every existing ID rejects well-formed but never-issued IDs, letting through only a small false-positive fraction. 4. **Separate tombstone pool.** Keep negative entries in their own namespace or pool with a hard memory cap, or cap tombstone writes per time window, so a flood can never evict positive entries. If tombstones are dropped, the cost is only extra misses that the earlier guards should already absorb. 5. **Throttle the client.** Identifying and limiting the enumerating client belongs to rate limiting, not to the cache. ## Sequential versus random enumeration Sequential walks behave differently. A scraper walking IDs 1 to N over a mostly-issued range hits real items (which cache normally) and gaps left by deleted items. Tombstones help only if the walk is repeated within the TTL. Range validation stops the walk past the highest issued ID. A bloom filter stops the never-issued gaps, but not deleted IDs, which a standard filter still reports as possibly present. For random floods, validation and the filter do the real work, and negative caching should be capped so it cannot make things worse.
- Do random 128-bit public IDs solve the flood by themselves?No. They make guessing a real item practically impossible, but a scraper can still generate endless well-formed random IDs that pass format validation and miss everywhere. You still need a bloom-filter guard, or a keyed signature inside the ID, to reject those fakes before the database.
- How would you cap the damage tombstones can do to the positive cache?Put negative entries in their own namespace or pool with a fixed memory limit and its own eviction, or cap tombstone writes per time window and skip writing when over budget. Positive entries then keep their memory whatever the flood does. The worst case of dropped tombstones is extra database misses, which the earlier guards should already absorb.
saying these in an interview costs you the question
- Negative caching protects against any volume of nonexistent-key requests
- Tombstones are tiny, so millions of them cannot matter
- A longer negative TTL makes a random-ID flood cheaper
- Random public IDs alone stop a flood of misses
- Format validation is pointless because the database rejects bad IDs anyway