Need the #1 custom application developer in Brisbane?Click here →

Caching Strategies

9 min read

Caching stores computed results so they don't need to be recalculated on the next request. A database query that takes 100ms might be cached for 1 hour, reducing thousands of queries to one. Caching is essential for scaling applications without scaling databases.

The tradeoff: stale data. A cache might return yesterday's user count when today's is different. For most data, staleness is acceptable if the TTL is short (cache for 5 minutes, not 5 days).

What Caching Is

Caching is trading accuracy for speed. You compute a result once, store it in fast storage (memory), and reuse it. When the underlying data changes, you invalidate the cache.

Caching exists at multiple levels:

  • Database query results: Store results of expensive queries in-memory
  • Rendered content: Store HTML or JSON responses so you don't recompute
  • External API responses: Cache responses from third-party APIs
  • CDN: Cache static assets at edge servers globally

Redis: The Most Common Cache

Redis is an in-memory data store. Reads are sub-millisecond. It's the standard for caching.

You store data as key-value pairs:

SET user:123:profile { name: Alice, email: alice@example.com }
GET user:123:profile
// Returns cached object

Redis supports expiry: SET with EX flag sets a TTL. After the TTL, the key is deleted automatically.

Redis also supports more complex operations: lists, sets, sorted sets, pub/sub. But for most caching, key-value storage is sufficient.

What to Cache

  • Expensive database queries: Results of complex queries that don't change frequently
  • External API responses: Results of calls to third-party APIs (often rate-limited)
  • Rendered content: HTML or JSON that's expensive to generate
  • User sessions: Session data that must be retrieved on every request
  • Rate limit counters: Track how many times a user has hit an endpoint

What Not to Cache

  • Data that must always be current: Account balances, inventory counts
  • User-specific data: Unless you namespace by user (user:123:data)
  • Large objects: Redis stores in memory. Caching a 10MB object uses 10MB of RAM.
  • Frequently changing data: If data changes every request, caching overhead isn't worth it

Cache Invalidation: The Hard Problem

Phil Karlton said: "There are only two hard things in Computer Science: cache invalidation and naming things."

When underlying data changes, the cache becomes stale. Two strategies:

TTL (Time-To-Live)

Cache entries expire after a set duration. Simple but imprecise. You might serve stale data for up to the TTL. But simplicity is powerful.

TTL works well for data that changes slowly or where staleness is acceptable. Cache search results for 5 minutes? TTL. Cache user profile for 10 minutes? TTL.

Event-Based Invalidation

When data changes, explicitly clear the cache. When a user updates their profile, invalidate the profile cache.

More complex but accurate. You invalidate exactly what changed. No staleness. The tradeoff: you must remember to invalidate.

In practice: TTL is simpler and works for most cases. Event-based is for critical data where accuracy matters more than complexity.

CDN Caching: Static Assets at the Edge

CDNs (Content Delivery Networks) cache static assets (JavaScript, CSS, images) on servers around the world. A user in London requests an image, it's served from the London edge server, not from your server in New York.

This is vastly faster. Requests have latency of milliseconds instead of hundreds of milliseconds. Use a CDN for all static assets.

CDNs like Cloudflare, Akamai, or AWS CloudFront are standards. Most hosting includes CDN.

For CDN caching to work, set Cache-Control headers on static assets. `Cache-Control: public, max-age=31536000` caches for a year.

Browser Caching

Browsers cache assets locally. Revisit a page, and the browser uses the cached CSS/JavaScript instead of downloading again.

Set Cache-Control headers to control this. Long TTL for assets that don't change often. Short/no cache for HTML (it might change).

Application-Level Caching vs Database Caching vs CDN

Different levels of caching solve different problems:

  • Application caching (Redis): Speeds up expensive application code, reduces database load
  • Database caching: Database has its own cache (buffer pool) for frequently accessed data
  • CDN caching: Speeds up static asset delivery globally

All three are valuable. Use them together.

Note
Caching is the first optimization to try. Before optimizing code or adding more servers, cache expensive operations. It's often the biggest improvement with minimal effort.

Monitoring Caches

Track cache hit rate: percentage of requests served from cache vs database. Hit rate above 80% is good. Below 50% suggests the cache isn't helping much.

Monitor Redis: memory usage, key evictions, client connections. If memory is full and keys are being evicted, increase memory or reduce what you cache.

The Realistic Approach

Start without caching. Measure what's slow. Cache the bottleneck. Measure again. This data-driven approach prevents cache bloat and unnecessary complexity.

A simple Redis instance can often extend your database 10x before you need to scale the database. Caching is powerful. Use it.