Caching is the highest-leverage performance tool most teams have, and the one they most often reach for too late or wire up wrong. The premise is simple: keep a copy of expensive-to-produce data somewhere fast and close, so you don't recompute or re-fetch it every time. Do it well and you cut database load dramatically, drop response times from hundreds of milliseconds to single digits, and absorb traffic spikes that would otherwise take you down. Do it carelessly and you get the failure mode that ruined someone's afternoon: users seeing stale data, a customer looking at another tenant's cached response, or a cache stampede that hammers your database at exactly the wrong moment. The famous quip that the two hard problems in computer science are naming things and cache invalidation exists for a reason. This is a guide to getting the benefit without the pain.
Cache at the layer that matches the problem
Caching isn't one thing — it's a series of layers, and the right one depends on what's slow. A CDN caches static assets and increasingly whole API responses at the edge, close to users, and is the cheapest big win for anything that doesn't change per-user; it pairs naturally with edge computing. An application-level cache — typically Redis or Memcached — holds computed results, session data, and hot database rows in memory, and is where most SaaS caching lives. Below that, the database itself caches query plans and pages, and your ORM may cache within a request. The discipline is to cache as close to the consumer as the data's freshness requirements allow: the closer to the user you can safely cache, the more work you skip — but the harder it is to invalidate. Match the layer to the data.
The patterns: cache-aside vs write-through
- Cache-aside (lazy loading): the application checks the cache first; on a miss it reads the database, populates the cache, and returns. Simple, robust, and the sensible default — the cache only ever holds data that's actually been requested. The tradeoff is a slower first request per key and a window where the cache can be stale after a write.
- Write-through: writes go to the cache and the database together, so the cache is always current. Reads are always fast and fresh, at the cost of slower writes and cache churn for data that may never be read.
- Write-behind and refresh-ahead are further up the curve — writing to cache first and flushing to the database asynchronously, or proactively refreshing hot keys before they expire — powerful but with real complexity and failure modes. Start with cache-aside; reach for the others only when a specific access pattern demands it.
Invalidation: the actual hard part
Adding a cache is easy; keeping it correct is the whole job. There are two ways to keep cached data from going stale, and you'll use both. Time-based expiry (TTL) is the workhorse — every entry expires after a set interval, so staleness is bounded and self-healing; the art is choosing a TTL that matches how fresh the data must be, seconds for a volatile counter, hours for a rarely-changing config. Explicit invalidation actively evicts or updates an entry the moment the underlying data changes — necessary when even brief staleness is unacceptable, and the source of most caching bugs because you have to remember every place a given piece of data can change. A pragmatic rule: lean on TTLs for the common case and reserve explicit invalidation for the data where being wrong for thirty seconds actually matters. And in multi-tenant systems, make tenant identity part of every cache key — a cache that serves one tenant's data to another isn't a performance bug, it's a multi-tenant isolation breach.
The traps that bite in production
- Thundering herd (cache stampede): a popular key expires and a thousand concurrent requests all miss and slam the database at once. Mitigate with locking so one request repopulates while others wait, staggered/jittered TTLs, or refreshing hot keys before they expire.
- Caching errors and empties: cache a failed lookup or an error response and you'll happily serve it for the whole TTL. Decide deliberately whether and how briefly to cache negatives.
- Unbounded growth: a cache without an eviction policy becomes a memory leak. Configure eviction (LRU is a sane default) and size it on purpose.
- Stale-after-deploy: cached data whose shape changed with a new release. Version your cache keys so a deploy doesn't read yesterday's structure.
- Caching the un-cacheable: per-user, always-changing, or security-sensitive data often shouldn't be cached at all. Caching isn't free correctness — some data has to be computed fresh.
Add caching for the right reason
The mistake that undoes all of this is caching by reflex instead of by evidence. Caching adds a source of truth you now have to keep consistent, and that complexity is only worth it against a measured problem — a slow endpoint, a hot query, a database near its ceiling. So measure first: observability tells you which queries and endpoints actually hurt, and caching those is a scalpel, not a shotgun. Frequently, the right move isn't a cache at all but fixing the underlying query or index — see scaling PostgreSQL — and a cache bolted over a pathological query just hides the problem until traffic doubles. Cache deliberately, cache what's measurably hot, and remember that every cache is a small consistency liability you're taking on in exchange for speed.
How Infiniti Tech Partners approaches caching
We treat caching as a targeted intervention, not a default: we measure first to find the endpoints and queries that actually hurt, then apply the right layer — CDN, application cache, or a query fix — with an invalidation strategy chosen for how fresh the data has to be. We build in the guardrails that keep it correct at scale: tenant-scoped keys, bounded eviction, stampede protection, and versioned keys that survive deploys. The result is the speed and database headroom caching promises, without the stale-data and cross-tenant incidents that careless caching causes. If your app is slowing down under load and you're wondering where a cache should go, that's exactly the question we help answer.
Frequently asked questions
What is the difference between cache-aside and write-through caching?
With cache-aside (lazy loading) the application checks the cache first and, on a miss, reads the database, populates the cache, and returns — it's simple, robust, and the sensible default, since the cache only holds data that's actually been requested. Write-through sends writes to the cache and database together so the cache is always current and reads are always fast and fresh, at the cost of slower writes and churn for data that may never be read. Start with cache-aside and reach for write-through or more advanced patterns only when a specific access pattern demands it.
How do you handle cache invalidation?
There are two mechanisms and you'll use both: time-based expiry (TTL), where each entry expires after an interval so staleness is bounded and self-healing, and explicit invalidation, which actively evicts an entry the moment the underlying data changes. TTLs are the workhorse — pick an interval that matches how fresh the data must be — and explicit invalidation is reserved for data where even brief staleness is unacceptable, since it's the source of most caching bugs. In multi-tenant systems, always make tenant identity part of the cache key, because serving one tenant's cached data to another is an isolation breach, not just a bug.
What is a thundering herd in caching?
A thundering herd (or cache stampede) happens when a popular key expires and many concurrent requests all miss at once and slam the database simultaneously, often at the worst possible moment. You mitigate it with locking so one request repopulates the key while others wait, by staggering and jittering TTLs so keys don't all expire together, or by refreshing hot keys before they expire. It's one of the classic production traps, alongside caching error responses, unbounded cache growth without eviction, and stale-after-deploy data when a cached structure changes.
Related reading
Disaster Recovery for SaaS: Setting RTO/RPO You Can Actually Hit
How to build a disaster recovery plan for SaaS around honest RTO and RPO targets — backups you've actually restored, tested failover, and a runbook that works at 3am.
CloudEdge Computing for SaaS: When the Latency Is Worth the Complexity
Edge computing promises lower latency and resilience, but adds real operational cost. A pragmatic 2026 guide for SaaS CTOs on when the edge pays off — and when a good CDN is enough.
CloudKubernetes or Serverless? The 2026 Decision Tree for Growth-Stage CTOs
When to run Kubernetes, when serverless cuts cost, and how most growth-stage companies use both — with a four-factor decision framework and cost benchmarks.