Skip to content

Cache Penetration

1. What is it? (2-3 lines)

Cache Penetration occurs when incoming requests query non-existent keys (keys that exist in neither the cache nor the underlying database). Because the key is never found in cache, every request "penetrates" through the cache layer directly to query the primary database.

2. Why does it exist?

Under normal operation, a cache miss reads from DB and populates the cache. However, for non-existent keys (e.g. user_id = -999 or malicious non-existent product IDs), the database returns empty result / null. If application code doesn't cache null or filter invalid keys, repeated queries continuously bypass the cache layer, allowing malicious attackers to overload the primary database with random invalid key scans (DDoS exploit).

3. Where is it used?

  • Security & DDoS Vulnerabilities: Malicious web scrapers scanning non-existent IDs.
  • E-Commerce / Social Platforms: Querying deleted or non-existent usernames, products, or orders.

4. How does it work? (simple flow)

  1. Malicious Query: Attacker sends 10,000 req/sec for random non-existent keys (GET /users/invalid_uuid_999).
  2. Cache Miss: Application checks Redis \(\to\) Key invalid_uuid_999 not found (cache miss).
  3. Database Penetration: Application queries PostgreSQL \(\to\) DB scans table \(\to\) Returns null / 0 rows.
  4. No Cache Population: Because DB returned null, standard code skips caching \(\to\) Next request penetrates again!
  5. Database Overload: DB CPU spikes to 100% processing fruitless disk scans for non-existent keys.

5. Solutions & Mitigations

  • Cache Null / Empty Values: Store empty/null values in cache with a short TTL (e.g. 1-5 minutes): redis.set("invalid_uuid_999", "NULL", EX=300). Subsequent requests hit cache and return null in 0ms!
  • Bloom Filters: Place a Bloom Filter in front of the cache containing all valid keys. If Bloom Filter returns "Definitely Not in Set", reject request immediately without checking Cache or DB!
  • Strict Input Validation & Regex Filtering: Reject malformed IDs at API Gateway before hitting cache.

6. Advantages & Limitations of Solutions

  • Cache Null Values: Simple application fix; but consumes Redis memory if attackers send millions of unique random keys.
  • Bloom Filter: Extremely space-efficient (\(O(1)\) memory); requires updating Bloom Filter whenever new valid keys are inserted into DB.

7. Common interview questions (10)

  1. What is the fundamental difference between Cache Penetration, Cache Breakdown (Stampede), and Cache Avalanche?
  2. How does a Bloom Filter prevent Cache Penetration for millions of non-existent keys?
  3. What are the risks of Caching Null Values to fix Cache Penetration and how do short TTLs mitigate them?
  4. What is a Cuckoo Filter and why might it be preferred over a Bloom Filter for dynamic datasets?
  5. How can API Gateways (Kong / Nginx) prevent Cache Penetration via input format validation?
  6. What happens if a new valid record is created in the DB after an empty null cache entry was written?
  7. How does an attacker execute a Cache Penetration DDoS attack against a REST API?
  8. How do you combine Bloom Filters with Redis (RedisBloom module) for low-latency membership checks?
  9. When is Caching Null Values superior to using a Bloom Filter?
  10. How does Cache Penetration impact database read replica CPU usage?

8. Common misconceptions

  • Misconception: Cache Penetration is solved by auto-scaling Redis. Fact: Redis returns cache miss instantly; the bottleneck occurs entirely on the primary database processing invalid reads.
  • Misconception: Caching null values requires long TTLs. Fact: Null values should use very short TTLs (1-5 mins) to prevent memory bloat and stale data when valid records are created later.

10. 30-second cheat sheet

  • Problem: Querying Non-Existent Keys \(\implies\) Cache misses every time \(\implies\) Every request penetrates to DB.
  • Solution 1 (Cache Null): Store redis.set(key, "NULL", EX=300) for non-existent keys with short 5-min TTL.
  • Solution 2 (Bloom Filter): Check Bloom Filter first. If "Definitely Not in Set" \(\implies\) Reject immediately!
  • Solution 3 (API Validation): Validate input format at Gateway before querying cache/DB.