FR

Data · 2 min read

Verified Experience Pattern

Caching Strategies with Redis

Cache-aside versus write-through, and how to choose based on how your data is actually read and written.

RedisPostgreSQL
Application
Strategy
Cache-Aside
Write-Through
Redis
PostgreSQL

Redis shows up in almost every high-read production system, but "add a cache" isn't a strategy by itself. The strategy is in how the cache is kept consistent with the source of truth.

Cache-aside

The application checks Redis first; on a miss, it reads from PostgreSQL and writes the result into Redis before returning it. Writes go to PostgreSQL and either invalidate or update the corresponding cache key.

Good fit when: reads vastly outnumber writes, and the data can tolerate being briefly stale between a write and the next cache population.

Watch for: cache stampedes, where a popular key expires and many concurrent requests all miss at once and hit the database simultaneously. A short-lived lock or request-coalescing pattern around cache population prevents this.

Write-through

Writes go to Redis and PostgreSQL together (or Redis is updated immediately after a successful database write), so the cache is never stale relative to the last write.

Good fit when: staleness is unacceptable for the data in question — for example, account balances or permission state — but read volume still justifies a cache in front of the database.

Watch for: write latency increases slightly since every write touches two systems, and you need a clear answer for what happens if the cache write fails after the database write succeeds.

Picking between them

The honest answer is that most systems need both, applied to different data. Session state, permission checks, and anything correctness-sensitive lean toward write-through or short TTLs. Expensive-to-compute, read-heavy, staleness-tolerant data — dashboards, aggregates, public listings — is a good fit for cache-aside with a reasonable TTL.

Operational notes that matter more than the pattern choice

  • TTLs are a reliability mechanism, not just a memory-management one. A sane TTL bounds how wrong the cache can be if invalidation logic has a bug.
  • Key design determines invalidation difficulty. Structuring keys around the natural invalidation boundary (e.g. per-user, per-resource) makes targeted invalidation possible instead of resorting to broad cache flushes.
  • Monitor hit rate, not just latency. A cache with a low hit rate is adding operational complexity without delivering the latency benefit it's there for.

Caching decisions like these were a direct contributor to the latency improvements described in Scaling PostgreSQL for High-Read Workloads and the Distributed SaaS Platform system.