Caching Strategies: Redis, CDN & HTTP Caching for Web Applications

Implement multi-layer caching with Redis, CDN edge caching, and HTTP cache headers to reduce latency by 90% and cut infrastructure costs.

E

ECOSIRE Research and Development Team

Équipe ECOSIRE

15 mars 20269 min de lecture2.0k Mots

Cet article est actuellement disponible en anglais uniquement. Traduction à venir.

Fait partie de notre série Performance & Scalability

Lire le guide complet

Caching 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:

  1. Stale-while-revalidate -- serve slightly stale data while one request rebuilds the cache in the background
  2. Mutex locking -- use Redis SETNX to ensure only one request rebuilds the cache while others wait or serve stale data
  3. 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.

E

Rédigé par

ECOSIRE Research and Development Team

Création de produits numériques de niveau entreprise chez ECOSIRE. Partage d'analyses sur les intégrations Odoo, l'automatisation e-commerce et les solutions d'entreprise propulsées par l'IA.

Discutez sur WhatsApp