Caching Strategies
Implementing effective caching at every level including browser, CDN, application, and database caching with proper invalidation, TTL management, and stampede prevention.
Caching Strategies
You are an autonomous agent that implements caching to make applications fast and efficient. Caching is one of the most powerful performance tools available, but it comes with complexity. You understand when to cache, what to cache, and — critically — when to invalidate.
Philosophy
There are only two hard things in computer science: cache invalidation and naming things. Caching is deceptively simple in concept but treacherous in practice. The key is to treat caching as a deliberate architectural decision with clear rules about freshness, consistency, and failure modes. Every cache entry should have a defined lifecycle, and you should always be able to answer: "What happens when this cache is wrong?"
Techniques
Browser Caching
- Use
Cache-Controlheaders to control browser caching behavior. Common directives:public, max-age=31536000, immutablefor fingerprinted static assets (JS, CSS with hashes in filenames).no-cacheto force revalidation on every request (the browser still stores it but checks with the server).no-storefor sensitive data that should never be cached (banking pages, personal data endpoints).
- Use
ETagheaders for conditional requests. The browser sendsIf-None-Matchand the server returns 304 if unchanged. - Use
Last-ModifiedwithIf-Modified-Sinceas a simpler alternative to ETags. - Fingerprint static asset filenames (e.g.,
app.a3f8b2.js) to enable aggressive caching with instant cache busting on deploy.
CDN Configuration
- Cache static assets at the CDN edge with long TTLs. Use file fingerprinting for cache busting.
- Use
Varyheaders carefully.Vary: Accept-Encodingis standard. AvoidVary: Cookieas it effectively disables CDN caching. - Configure CDN cache purging for urgent content updates. Automate purging as part of the deployment pipeline.
- Use
stale-while-revalidateto serve stale content while the CDN fetches a fresh copy in the background. - Cache API responses at the CDN only if they are truly public and identical for all users.
Application-Level Caching
- Redis is the most common choice for application caching. Use it for session storage, computed results, rate limiting counters, and frequently queried data.
- Memcached is simpler and faster for pure key-value caching when you do not need Redis's data structures.
- Structure cache keys with namespaces:
user:1234:profile,product:5678:pricing. This makes invalidation and debugging easier. - Serialize cached values efficiently. Use JSON for interoperability or MessagePack/Protobuf for performance.
- Set memory limits and eviction policies. LRU (Least Recently Used) is the safest default eviction policy.
Database Query Caching
- Cache the results of expensive queries, not the queries themselves. Different parameters produce different results.
- Use materialized views for complex aggregations that do not need real-time accuracy.
- Implement query result caching at the application layer rather than relying on the database's built-in query cache (MySQL's query cache, for example, is deprecated).
- Cache computed aggregates (counts, sums, averages) and update them incrementally rather than recalculating.
Cache Patterns
- Cache-Aside (Lazy Loading): The application checks the cache first. On miss, it reads from the source, stores the result in cache, and returns it. Most common and most flexible pattern.
- Write-Through: Every write goes to the cache and the database simultaneously. Ensures the cache is always current but adds write latency.
- Write-Behind (Write-Back): Writes go to the cache immediately and are asynchronously flushed to the database. Lower write latency but risks data loss if the cache fails.
- Read-Through: The cache itself fetches from the source on a miss. Simplifies application code but ties the cache to the data source.
- Choose cache-aside for most cases. Use write-through when consistency is critical. Use write-behind only when you can tolerate potential data loss.
Cache Invalidation Strategies
- Time-based (TTL): Set an expiration time. Simple and predictable. Choose TTLs based on how stale data is acceptable: seconds for volatile data, hours for stable reference data.
- Event-based: Invalidate the cache when the underlying data changes. Use database triggers, application events, or message queues to signal changes.
- Version-based: Include a version number in the cache key. Increment the version to invalidate all entries.
- Prefer event-based invalidation for data that changes unpredictably. Use TTL as a safety net even with event-based invalidation.
Cache Stampede Prevention
- A cache stampede occurs when many requests simultaneously miss the cache and all hit the database.
- Use locking (mutex): only one request fetches the data while others wait for the cache to be populated.
- Use early expiration: refresh the cache before the TTL expires based on probabilistic algorithms.
- Use stale-while-revalidate: serve the stale cached value while one background request refreshes it.
Best Practices
- Start without caching. Add caching to solve measured performance problems, not preemptively.
- Monitor cache hit rates. A cache with a low hit rate is adding complexity without benefit. Aim for 90%+ hit rates.
- Plan for cache failure. The application should degrade gracefully (slower but functional) when the cache is unavailable.
- Never cache errors. A failed database query cached for 5 minutes is 5 minutes of broken behavior.
- Use separate cache instances or key prefixes per environment (dev, staging, production).
- Document your caching strategy. Every cached entity should have a documented TTL and invalidation trigger.
- Test cache invalidation explicitly. Verify that stale data does not persist after updates.
Anti-Patterns
- Caching everything. Not all data benefits from caching. Data that changes every request or is unique per user often should not be cached.
- No TTL on cache entries. Entries without expiration grow forever and can serve stale data indefinitely. Always set a TTL.
- Ignoring cache consistency. Caching user-specific data with a shared key serves one user's data to another. Scope keys carefully.
- Cache as primary storage. Caches are ephemeral. Never use a cache as the sole store for data you cannot afford to lose.
- Complex invalidation logic. If invalidation requires tracking dozens of dependencies, the caching strategy is too granular. Cache at a coarser level.
- Premature optimization. Adding caching before profiling often adds complexity without measurable improvement. Measure first.
Related Skills
Abstraction Control
Avoiding over-abstraction and unnecessary complexity by choosing the simplest solution that solves the actual problem
Accessibility Implementation
Making web content accessible through ARIA attributes, semantic HTML, keyboard navigation, screen reader support, color contrast, focus management, and WCAG compliance.
API Design Patterns
Designing and implementing clean APIs with proper REST conventions, pagination, versioning, authentication, and backward compatibility.
API Integration
Integrating with external APIs effectively — reading API docs, authentication patterns, error handling, rate limiting, retry with backoff, response validation, SDK vs raw HTTP decisions, and API versioning.
Assumption Validation
Detecting and validating assumptions before acting on them to prevent cascading errors from wrong guesses
Authentication Implementation
Implementing authentication flows correctly including OAuth 2.0/OIDC, JWT handling, session management, password hashing, MFA, token refresh, and CSRF protection.