SaaS Architecture · 2026
Caching Strategies for SaaS: A 2026 Engineering Guide
Caching is the highest-leverage performance lever in most SaaS apps — and the easiest place to leak data or serve stale answers. This guide covers the patterns that matter: cache-aside vs write-through, invalidation, the thundering herd, and multi-tenant key design.

Quick answer
Default to cache-aside for SaaS read paths: check the cache, fall back to the database on a miss, and populate for next time. Bound staleness with short TTLs and add explicit invalidation on write. Namespace every cache key by tenant and by schema version so you never leak data across customers or serve an incompatible shape after a deploy. Defend hot keys against the thundering herd with single-flight locks and jittered TTLs, and never cache authorization decisions or fast-moving exact values.
There are two hard things in computer science, the joke goes, and cache invalidation is one of them. In SaaS the stakes are higher than performance: a cache key that collides across tenants is a data-leak bug. We build multi-tenant platforms for a living, and our SaaS platform practice treats cache correctness with the same care as database isolation. If tenant isolation is your concern, start with building multi-tenant SaaS on Postgres RLS.
1. The three write patterns
Caching strategies differ mostly in how writes flow. Pick based on the consistency you need and the write volume you carry.
- Cache-aside (lazy). The app reads through to the DB on a miss and populates the cache; writes invalidate the entry. Simple, resilient to cache outages, caches only what is asked for. The right default.
- Write-through. Every write updates cache and DB together, keeping the cache warm and consistent. Costs you writes of data that may never be read.
- Write-behind (write-back). Writes hit the cache and are flushed to the DB asynchronously. Maximum write throughput, but a cache failure can lose not-yet-persisted data — use only when you understand that risk.
2. Cache-aside in practice
The canonical read path is a handful of lines. The discipline is in the key design and the TTL, not the control flow.
// Cache-aside read, namespaced by tenant + schema version
async function getProject(tenantId, projectId) {
const key = `t:${tenantId}:v3:project:${projectId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const row = await db.projects.findOne({ tenantId, id: projectId });
if (row) {
// jittered TTL avoids synchronized expiry (see thundering herd)
const ttl = 300 + Math.floor(Math.random() * 60);
await redis.set(key, JSON.stringify(row), "EX", ttl);
}
return row;
}Note the t:<tenantId> prefix and the v3 schema version. The tenant prefix makes cross-tenant collisions impossible; the version lets a deploy that changes the cached shape invalidate everything by bumping a single token rather than scanning keys.
3. TTLs and invalidation
Use both mechanisms together. TTLs bound how stale data can ever get; explicit invalidation keeps it fresh between expirations. Relying on TTL alone serves stale data for the whole window; relying on invalidation alone means one missed delete caches a wrong value forever.
- On write, delete or update the affected keys. Deleting (and letting the next read repopulate) is simpler and avoids caching a value nobody reads.
- Use a versioned namespace to invalidate a whole class at once: bump
listVersionand every cached list for that tenant is logically gone. - Be paranoid about derived data — counts, rollups, dashboards. The classic bug is caching an aggregate and forgetting to bust it when a contributing row changes.
4. Defeating the thundering herd
When a hot key expires, every concurrent request misses at once and stampedes the database to recompute the same value. On a busy endpoint this can take the database down. Three defenses, often combined:
- Single-flight. A short lock so only one request recomputes the value while the rest wait for it to land in the cache.
- Jittered TTLs. Add random spread to expiry so popular keys do not all lapse on the same tick.
- Stale-while-revalidate. Serve the slightly stale value immediately and refresh it in the background — the same idea HTTP caching exposes via the
stale-while-revalidatedirective.
These pair naturally with a background refresh job — see background jobs and queues in production for the worker side.
Mid-post: a cache key is a security boundary
In multi-tenant SaaS, an un-namespaced cache key is a data-leak waiting to happen. Want a review of your cache key design before it bites? Book a free scoping call.
Where each layer of cache lives
| Layer | Good for / caution |
|---|---|
| CDN / edge | Static and public assets; never personalized data without a per-user key |
| HTTP / browser | Cache-Control + ETag on GETs; avoid for authenticated mutations |
| Shared (Redis) | Hot rows, sessions, computed views; namespace by tenant |
| In-process | Config, feature flags; remember it is per-instance and short-lived |
| Database | Materialized views for heavy aggregates; refresh on a schedule |
Operational practices that hold over time
A cache earns its keep only if you measure it:
- Track hit rate. A cache below roughly 80% hit rate on a hot path is often miskeyed or has too short a TTL — instrument it. See observability for startups.
- Pick an eviction policy on purpose. Set a maxmemory policy (such as allkeys-lru) so the cache sheds cold keys instead of erroring when full.
- Cache after you index. A cache is not a substitute for a missing database index — fix the query first. Our SaaS database scaling guide covers the order of operations.
Frequently asked questions
What is the best caching strategy for a SaaS application?
Cache-aside (lazy loading) is the right default for most SaaS read paths. The application checks the cache, falls back to the database on a miss, and populates the cache for next time. It is simple, resilient to cache outages, and only caches data that is actually requested. Use write-through when you need the cache and database to stay tightly consistent on every write, and write-behind only when write throughput is so high that you must batch database writes — accepting the durability risk that entails.
What is the difference between cache-aside and write-through?
In cache-aside, the application owns cache population: it reads through to the database on a miss and writes the cache itself, while writes typically invalidate the cached entry. In write-through, every write goes to the cache and the database together, so the cache is always warm and consistent for that key. Cache-aside is simpler and degrades gracefully if the cache is down; write-through guarantees freshness at the cost of writing data that may never be read.
How do you handle cache invalidation?
Combine short TTLs as a safety net with explicit invalidation on write. TTLs guarantee staleness is bounded even if an invalidation is missed; explicit deletes or updates on the relevant keys keep data fresh between expirations. Design your keys so you can invalidate precisely — a single record, or a versioned namespace you bump to invalidate a whole class of entries at once. The hardest bugs come from caching derived or aggregated data whose source you forget to invalidate.
What is a thundering herd in caching?
A thundering herd happens when a hot cache entry expires and many concurrent requests all miss at once, stampeding the database to recompute the same value simultaneously. Defenses include a short lock or single-flight mechanism so only one request recomputes while the others wait, staggered or jittered TTLs so popular keys do not all expire together, and serving a slightly stale value while one worker refreshes in the background.
How should you design cache keys in a multi-tenant SaaS?
Always namespace cache keys by tenant so one customer can never read another's cached data — a cache key collision across tenants is a data-leak bug, not just a correctness bug. Include a schema or version component in the key prefix so a deploy that changes the cached shape does not serve stale, incompatible data, and so you can invalidate an entire tenant or an entire version by bumping one prefix instead of scanning keys.
What should you not cache?
Do not cache data whose staleness causes correctness or security problems: authorization decisions, account balances, inventory counts at the point of sale, or anything a user expects to be exact and immediate. Be cautious caching personalized or per-user responses at shared layers like a CDN, where a misconfigured cache key can serve one user's private data to another. When in doubt, cache the expensive-to-compute and slow-to-change, not the security-sensitive and fast-moving.
Sources & references
- [1]Redis — Key eviction and expiration · Redis
- [2]RFC 9111 — HTTP Caching · IETF
- [3]AWS — Caching best practices · Amazon Web Services
- [4]MDN — HTTP caching · MDN Web Docs
Related reading and next steps
Faster reads, no leaked data.
We design caching layers that are fast, correct, and safe in multi-tenant systems. Book a free scoping call to talk through your read paths.
More engineering reading
All postsScaling a SaaS Database (2026)
Indexing, pooling, read replicas, partitioning, caching, and when to shard.
Read postBuilding Multi-Tenant SaaS on Postgres RLS
Row-level security patterns for isolating tenant data without separate databases.
Read postAdding AI Features to Your SaaS (2026)
Where AI helps, build-vs-API trade-offs, evals, guardrails, and shipping without torching margins.
Read post