Redis Caching Strategies for High Traffic
The Database Bottleneck
As your application scales, the relational database (PostgreSQL, MySQL) is always the first component to fail. Databases store data on physical disk drives. Even with NVMe SSDs, reading from a disk and joining massive tables requires significant CPU and IOPS overhead. If a viral news article brings 50,000 concurrent users to your site, hitting the database 50,000 times a second to fetch the exact same article text will instantly melt the server. The solution is Redis: an in-memory data structure store that holds data entirely in RAM, allowing for sub-millisecond retrieval times.
Pattern 1: The Cache Aside (Lazy Loading)
This is the industry standard for 95% of use cases. In the Cache Aside pattern, the application is responsible for communicating with both Redis and the database.
- The application asks Redis: "Do you have the data for Article #123?"
- Cache Hit: Redis says yes, returns the data instantly, and the request is complete.
- Cache Miss: Redis says no. The application performs the heavy query against PostgreSQL, retrieves the data, saves a copy into Redis with a Time-To-Live (TTL) expiration, and returns the data to the user.
The beauty of this pattern is resilience. If the Redis server crashes entirely, your application doesn't die; it simply falls back to querying the database directly (though it will be slower).
Pattern 2: Write-Through Caching
The Cache Aside pattern has a fatal flaw: Data Staleness. If the article title is updated in the database, Redis still holds the old title until its TTL expires. For financial or highly dynamic data, this is unacceptable.
In a Write-Through pattern, whenever the application updates a record, it simultaneously writes the update to both the primary database and the Redis cache in the same transaction. The cache is always 100% perfectly synced with the database. The downside is a slight performance penalty on Write operations, but it guarantees perfect Read consistency.
The Thundering Herd (Cache Stampede)
Consider a nightmare scenario: You cache the front page of an e-commerce site with a 5-minute TTL. The site gets 10,000 requests per second. At exactly minute 5, the cache expires. In the exact millisecond the cache is empty, 10,000 users hit the application. All 10,000 requests experience a Cache Miss. All 10,000 requests instantly query the PostgreSQL database simultaneously, creating a "Thundering Herd" that crashes the database instantly.
How to prevent it:
- Mutex Locking: When a Cache Miss occurs, the application attempts to acquire a Redis Lock. Only the first request gets the lock and queries the database. The other 9,999 requests are forced to wait 50 milliseconds and check the cache again.
- Probabilistic Early Expiration (PER): The application randomly decides to refresh the cache before the TTL actually expires. If a request comes in when the TTL has 5 seconds remaining, there is a small random chance the application will trigger a background thread to update the cache early, preventing the herd entirely.