Part of our Performance & Scalability series
Read the complete guideCaching Strategies: Redis, CDN & HTTP Caching for Web Applications
Caching is the single most effective technique for improving application performance. A well-designed cache can reduce database load by 90%, cut response times from 200ms to 2ms, and save thousands of dollars in infrastructure costs monthly. Yet caching is also one of the most misunderstood areas of software engineering -- invalidation bugs create stale data, cache stampedes bring down servers, and poorly chosen TTLs either waste memory or serve outdated content.
Key Takeaways
- Design caching in layers: browser to CDN to application (Redis) to database query cache -- each layer handles different data characteristics
- Redis cache-aside pattern is the safest default: read from cache, fall back to database, populate cache on miss
- HTTP cache headers (Cache-Control, ETag, Vary) eliminate unnecessary requests entirely -- the fastest request is one that never reaches your server
- Cache invalidation is harder than caching -- use TTL-based expiration as the primary strategy and event-driven invalidation for critical data freshness
The Cache Hierarchy
Effective caching works in layers, with each layer optimized for different latency, capacity, and freshness requirements.
| Layer | Latency | Capacity | Best For | Invalidation | |---|---|---|---|---| | Browser cache | 0ms (local) | Limited by device | Static assets, user-specific data | Cache-Control headers | | CDN edge cache | 5-50ms | Terabytes across edge nodes | Static assets, public API responses | Purge API, TTL | | Application cache (Redis) | 1-5ms | Gigabytes (RAM-limited) | Session data, computed results, rate limits | TTL + event-driven | | Database query cache | 10-50ms | Configurable | Repeated identical queries | Automatic on table writes |
Layer 1: Browser Cache
The browser cache is the fastest and cheapest cache because it eliminates the network request entirely. HTTP Cache-Control headers control browser caching behavior.
For static assets with content-hashed filenames (like Next.js build output), set Cache-Control: public, max-age=31536000, immutable. The content hash in the filename guarantees that changed content gets a new URL, so you can cache aggressively without worrying about stale content.
For HTML pages and API responses, use shorter TTLs with revalidation: Cache-Control: public, max-age=60, stale-while-revalidate=300. This serves cached content for 60 seconds, then revalidates in the background while continuing to serve stale content for up to 5 minutes.
Layer 2: CDN Edge Cache
A CDN caches content at edge servers distributed globally, reducing latency by serving content from the location closest to each user. For a global user base, CDN caching reduces average latency from 200-500ms (origin server round trip) to 10-50ms (nearest edge).
What to cache on CDN:
- All static assets (JavaScript, CSS, images, fonts)
- Public marketing pages (with short TTL for content freshness)
- Product catalog pages (5-15 minute TTL for eCommerce)
- API responses for public data (product listings, blog content)
What NOT to cache on CDN:
- Authenticated API responses (user-specific data)
- Shopping cart and checkout pages
- Admin panel pages
- Webhook endpoints
- Any response with Set-Cookie headers
Layer 3: Application Cache (Redis)
Redis provides microsecond-latency access to cached data, making it ideal for data that is expensive to compute or frequently accessed. Unlike CDN caching, Redis can cache authenticated and user-specific data because your application controls access.
Layer 4: Database Query Cache
PostgreSQL maintains a buffer cache (shared_buffers) that caches frequently accessed table and index pages in memory. While not directly controllable per query, proper configuration ensures hot data stays in memory. For reporting queries, consider materialized views that precompute expensive aggregations.
Redis Caching Patterns
Redis is the most popular application-level cache for web applications, supporting strings, hashes, lists, sets, sorted sets, and streams. The pattern you choose determines how your cache behaves under edge cases.
Cache-Aside (Lazy Loading)
Cache-aside is the safest default pattern. The application checks Redis first. On a cache hit, it returns the cached data. On a cache miss, it queries the database, stores the result in Redis with a TTL, and returns the data.
Advantages:
- Only requested data is cached (no wasted memory on unused data)
- Database failure only affects cache misses, not cache hits
- Simple to implement and reason about
Disadvantages:
- First request for each key always hits the database (cold start)
- Stale data possible between database update and cache expiration
Write-Through
In write-through caching, every database write also updates the cache immediately. The application writes to both the database and Redis in the same operation.
Advantages:
- Cache is always fresh -- no stale data window
- Read performance is always fast (no cache misses after initial write)
Disadvantages:
- Write latency increases (two writes per operation)
- Cache may contain data that is never read (wasted memory)
- Requires careful error handling when one write succeeds and the other fails
Write-Behind (Write-Back)
Write-behind caching writes to Redis immediately but defers the database write to a background process. This reduces write latency but introduces the risk of data loss if Redis fails before the database write completes.
Use sparingly -- this pattern is appropriate for analytics counters, rate limiting, and session data where occasional data loss is acceptable, not for financial transactions or inventory counts.
Cache Stampede Prevention
A cache stampede occurs when a popular cache key expires and hundreds of concurrent requests all query the database simultaneously to rebuild it. This can overload the database.
Prevention strategies:
- Stale-while-revalidate -- serve slightly stale data while one request rebuilds the cache in the background
- Mutex locking -- use Redis SETNX to ensure only one request rebuilds the cache while others wait or serve stale data
- Probabilistic early expiration -- randomly recompute before TTL expiration, spreading rebuilds over time instead of concentrating them at expiration
TTL Strategy Design
Time-to-live (TTL) values determine how long data stays in cache. Too short and you get excessive cache misses. Too long and you serve stale data.
| Data Type | Recommended TTL | Rationale | |---|---|---| | User session | 30 minutes (sliding) | Balance security with UX | | Product catalog | 5-15 minutes | Products change infrequently, freshness matters for pricing | | Search results | 1-5 minutes | Results change as inventory updates | | Static content (about, FAQ) | 1-24 hours | Content changes rarely | | Rate limiting counters | Match window size | Must be precise for rate limiting to work | | Computed dashboards | 5-30 minutes | Balance freshness with computation cost | | Feature flags | 30-60 seconds | Quick propagation of flag changes |
Sliding TTL vs Fixed TTL
Fixed TTL expires the key at a set time after creation. Sliding TTL resets the expiration every time the key is accessed. Use sliding TTL for session data (keep active sessions alive) and fixed TTL for content caches (ensure regular refresh from source).
HTTP Cache Headers Deep Dive
HTTP caching is powerful because it works at every layer -- browser, CDN, proxies, and load balancers all understand the same headers.
Cache-Control Directives
- public -- any cache (browser, CDN, proxy) may store the response
- private -- only the browser may cache (not CDN or proxies) -- use for authenticated responses
- no-cache -- cache the response but revalidate with the server before using it (misleading name -- it does cache)
- no-store -- do not cache at all (sensitive data like banking pages)
- max-age=N -- cache is valid for N seconds
- s-maxage=N -- CDN/proxy cache duration (overrides max-age for shared caches)
- stale-while-revalidate=N -- serve stale content for N seconds while revalidating in the background
- immutable -- content will never change (use with content-hashed URLs)
ETag and Conditional Requests
ETags provide content-based cache validation. The server generates a hash of the response content and sends it as the ETag header. On subsequent requests, the browser sends the ETag in an If-None-Match header. If the content has not changed, the server responds with 304 Not Modified (no body), saving bandwidth.
Use ETags for API responses where content changes unpredictably and TTL-based caching would either be too aggressive or too conservative.
Vary Header
The Vary header tells caches that the response depends on specific request headers. For example, Vary: Accept-Encoding means gzip and Brotli versions are cached separately. Vary: Accept-Language caches different language versions separately.
Be careful with Vary -- each unique combination of varied headers creates a separate cache entry. Vary: Cookie effectively disables caching because every user has different cookies.
Cache Invalidation Strategies
Cache invalidation is famously one of the two hard problems in computer science. The goal is to remove or update cached data when the source data changes, without introducing stale reads or unnecessary cache misses.
TTL-Based Expiration
The simplest and most reliable invalidation strategy. Set a TTL on every cached key and accept that data may be slightly stale within the TTL window. For most use cases, a 5-minute TTL provides an excellent balance between freshness and performance.
Event-Driven Invalidation
When a database record changes, publish an event that triggers cache deletion. This provides near-instant freshness but adds complexity and failure modes. If the event is lost (network issue, queue failure), the cache serves stale data until TTL expires.
Use event-driven invalidation for critical data like inventory counts and pricing, and rely on TTL for everything else.
Tag-Based Invalidation
Some caching systems support tagging cache entries with labels. When you invalidate a tag, all entries with that tag are purged. This is useful for invalidating all cached data related to a specific entity (for example, all product-related caches when a product catalog is updated).
Frequently Asked Questions
How do I decide what to cache?
Cache data that is read frequently, expensive to compute, and tolerant of brief staleness. Product catalog pages, user permissions, computed dashboard metrics, and configuration data are excellent candidates. Shopping carts, real-time inventory counts, and financial transactions should generally not be cached or should use very short TTLs with event-driven invalidation.
What happens when Redis goes down?
With cache-aside pattern, your application falls back to querying the database directly. Response times increase but the application remains functional. Design your application to handle cache misses gracefully -- Redis should be a performance optimization, not a single point of failure.
How much memory should I allocate to Redis?
Monitor your cache hit ratio and memory usage. A hit ratio above 95% with memory utilization below 80% indicates good sizing. If the hit ratio drops below 90%, you either need more memory or your TTLs are too short. Start with 1-2GB for most applications and scale based on monitoring data.
Should I use Redis or Memcached?
Redis is the better default choice for most applications. It supports more data types, persistence options, pub/sub for event-driven invalidation, and Lua scripting for atomic operations. Memcached is simpler and slightly faster for pure key-value caching at extreme scale, but Redis covers more use cases.
How do I prevent serving stale data after a deployment?
Include a version identifier in your cache keys (for example, prefix keys with the deployment version or a schema version). When you deploy, the new version uses new cache keys, and old keys expire naturally via TTL. Alternatively, flush the entire cache on deployment if your application handles cold caches gracefully.
What Is Next
Start by implementing HTTP cache headers for your static assets and public pages -- this provides immediate performance improvement with zero application changes. Then add Redis caching for your most expensive database queries and API endpoints.
For the full performance optimization picture, see our pillar guide on scaling your business platform. To optimize the data source that feeds your cache, read our guide on database query optimization.
ECOSIRE implements caching architectures for platforms running on Odoo ERP and Shopify. Contact our team for a caching strategy review.
Published by ECOSIRE — helping businesses scale with AI-powered solutions across Odoo ERP, Shopify eCommerce, and OpenClaw AI.
Written by
ECOSIRE Research and Development Team
Building enterprise-grade digital products at ECOSIRE. Sharing insights on Odoo integrations, e-commerce automation, and AI-powered business solutions.
Related Articles
API Performance: Rate Limiting, Pagination & Async Processing
Build high-performance APIs with rate limiting algorithms, cursor-based pagination, async job queues, and response compression best practices.
Core Web Vitals Optimization: LCP, FID & CLS for eCommerce Sites
Optimize Core Web Vitals for eCommerce. Improve LCP, INP, and CLS scores to boost SEO rankings and reduce cart abandonment by 24%.
Database Query Optimization: Indexes, Execution Plans & Partitioning
Optimize PostgreSQL performance with proper indexing, EXPLAIN ANALYZE reading, N+1 detection, and partitioning strategies for growing datasets.
More from Performance & Scalability
API Performance: Rate Limiting, Pagination & Async Processing
Build high-performance APIs with rate limiting algorithms, cursor-based pagination, async job queues, and response compression best practices.
Core Web Vitals Optimization: LCP, FID & CLS for eCommerce Sites
Optimize Core Web Vitals for eCommerce. Improve LCP, INP, and CLS scores to boost SEO rankings and reduce cart abandonment by 24%.
Database Query Optimization: Indexes, Execution Plans & Partitioning
Optimize PostgreSQL performance with proper indexing, EXPLAIN ANALYZE reading, N+1 detection, and partitioning strategies for growing datasets.
Integration Monitoring: Detecting Sync Failures Before They Cost Revenue
Build integration monitoring with health checks, error categorization, retry strategies, dead letter queues, and alerting for multi-channel eCommerce sync.
Load Testing Your eCommerce Platform: Preparing for Black Friday Traffic
Prepare your eCommerce site for Black Friday with load testing strategies using k6, Artillery, and Locust. Learn traffic modeling and bottleneck identification.
Monitoring & Observability: APM, Logging & Alerting Best Practices
Build production observability with the three pillars: metrics, logs, and traces. Compare APM tools and design alerts that reduce noise and catch real issues.