Blog/developer

What Is Caching in Distributed Backend Systems?

By Yurlie TeamAugust 16, 20268 min read 4 views
What Is Caching in Distributed Backend Systems?

What Is Caching in Distributed Backend Systems?

Every backend engineer eventually hits the same wall: the database is fine under normal load, but the moment traffic spikes, response times crawl and connection pools max out. The usual root cause is not a slow query, it is the sheer number of times the same data gets fetched from the same place. Caching solves this by keeping a temporary copy of frequently accessed data somewhere faster and closer to the application, so most requests never have to touch the original data store at all.

In a distributed backend, caching is not a nice-to-have. It is often the difference between a service that survives a traffic spike and one that falls over. This article breaks down how caching works in distributed systems, the patterns you will actually use in production, and what happens to latency and throughput when you get it right.

What Caching Actually Means

Caching is the technique of copying data that is read often into storage that responds faster than the original source. It works best when the underlying data store is relatively static, is slower than the cache, faces heavy contention, or sits far enough from the client that network latency becomes noticeable.

Distributed applications typically use one of two caching strategies:

  • Private cache: data is stored locally within the memory of the process or machine running the application.
  • Shared cache: a separate service acts as a common data source that multiple processes and machines can access at once.

Private Cache vs Shared Cache

A private, in-memory cache lives inside a single process's address space, so access is extremely fast and simple to implement. The catch is that if you run multiple instances of your application, each instance holds its own independent snapshot of the data. If the underlying data changes, different instances can serve different, inconsistent results to the same query, since each one is only working from a snapshot taken at a different point in time.

A shared cache fixes this by centralizing the cached data in its own service, usually backed by a cluster. Every application instance reads from the same source, so there is no risk of instances disagreeing with each other. Shared caches also scale horizontally by adding more nodes to the cluster, with the underlying infrastructure handling data distribution transparently. The trade-off is that access is slower than a local in-memory read, since it now involves a network hop, and running a separate cache service adds operational complexity.

Most production systems end up combining both: a small local cache for the hottest keys, backed by a shared cache like Redis for everything else.

The Cache-Aside Pattern

The most common way to populate a cache is on demand, using a pattern called cache-aside (also called lookup-aside). The flow is simple:

  1. Check the cache first. The application looks up the key in the cache.
  2. On a cache miss, fall back to the database. If the key is not found, the application queries the original data store.
  3. Write the result back to the cache with a TTL. The retrieved value is stored in the cache with an expiration time, so future requests for the same key are served without hitting the database again.
{
  "pattern": "cache-aside",
  "steps": ["GET from cache", "on miss: GET from database", "SET in cache with TTL"],
  "ttl_seconds": 300
}

This approach keeps the read path simple and means the application only needs to hit the database once per key, per TTL window, no matter how many times that key gets requested afterward.

Keeping the Cache Consistent on Writes

Reading from a cache is easy. Writing is where things get complicated, because now you have to decide how the cache stays consistent with the system of record. Two approaches dominate in practice:

  • Invalidate on write: write the change to the database, then delete the corresponding cache entry. The next read repopulates the cache from the database. This keeps the write path simple, but readers can briefly see stale data or a cache miss right after the write.
  • Write-through: update the database and the cache as part of the same write operation, only returning success once both succeed. Readers get the updated value immediately, at the cost of higher write latency and more coordination logic.

Write-through is worth the extra complexity for read-heavy paths that need fresh data immediately after a write. For data that is rarely read right after being written, invalidate-on-write is usually the simpler and cheaper choice.

Managing TTL and Eviction

Every cached item should have an expiration policy. Set the TTL too short, and you lose most of the benefit of caching, since data keeps expiring before it gets reused. Set it too long, and you risk serving stale data. Most caching systems, Redis included, let you set a default expiration policy for the whole cache plus a per-key TTL for individual objects.

When the cache fills up, entries get evicted to make room for new ones. Common eviction policies include:

PolicyBehaviorBest suited for
LRU (Least Recently Used)Removes the item that has not been accessed in the longest timeGeneral purpose caching, default in most Redis deployments
FIFO (First In, First Out)Removes the oldest item regardless of access patternSimple, predictable workloads
Explicit invalidationRemoves items based on a specific event, such as an underlying record being modifiedData with clear write triggers

Distributed Caching in a Microservices Architecture: What the Numbers Look Like

Caching theory is one thing, but the performance gap it produces is easier to appreciate with real numbers. A 2025 study from Politeknik Negeri Banyuwangi and Institut Teknologi Al-Muhajirin tested a Redis Cluster deployed as a distributed cache in front of a three-service microservices system (user, product, and order services), comparing a baseline scenario with no caching against one using a six-node Redis Cluster.

The load test used Apache JMeter with 10,000 requests ramped up over 5 minutes and 200 concurrent threads, repeated three times per scenario. The results:

ParameterWithout CacheWith Redis ClusterChange
Average latency (ms)125.448.7↓ 61.1%
Throughput (req/sec)420.2986.5↑ 134.8%
CPU usage (%)78.564.1↓ 18.3%
Database hit rate (%)100.018.7↓ 81.3%

An independent t-test on the latency and throughput results returned p < 0.01, meaning the improvement was statistically significant rather than random variation between test runs. The drop in database hit rate from 100% to under 19% is the number that matters most operationally: it shows the caching layer absorbed the vast majority of read traffic that would otherwise have hit PostgreSQL directly. The one cost worth noting is memory: the cached scenario used about 41% more memory than the baseline, since data now lives in Redis as well as in the database.

The services chosen for caching in that study, product and order, were picked specifically because they had high read intensity relative to the rest of the system. That is a pattern worth copying: cache what gets read often and changes rarely, not everything indiscriminately.

High Availability and Partitioning in Shared Caches

Once a shared cache becomes critical to your read path, its own availability becomes a concern. Two mechanisms are typically used to handle this at scale, sharding and replication.

Sharding splits the cached dataset across multiple nodes, usually by hashing each key to a slot and mapping slots to nodes. This is how Redis Cluster distributes 16,384 hash slots across master nodes, letting the cache scale horizontally as data volume grows. Replication pairs each node with one or more replicas, so if a node goes down, a replica can take over without interrupting the whole cache. This combination of sharding and replication is also what makes it possible to add or remove nodes without redistributing the entire dataset manually.

It is worth noting that most distributed caches, Redis included, prioritize availability over strict consistency under network partitions. That means a read from a replica can occasionally return slightly stale data right after a write to a different node. If your application cannot tolerate that, use a shorter TTL for that specific data, or skip the cache entirely for that read path and go straight to the source.

When Caching Is the Wrong Tool

Caching is not free, and it is not universally helpful. It works poorly for data that changes constantly, since the cached copy goes stale faster than it can be reused, and the overhead of keeping it in sync can outweigh the benefit. It also should never be treated as the primary store for critical data. Cache entries can disappear at any time, whether from eviction, a restart, or a node failure, so anything that matters needs to be durably written to the actual data store first.

A good rule of thumb: cache data with a high ratio of reads to writes, and keep the source of truth somewhere durable regardless of what the cache says.

Explore More Timing and Scheduling Tools on Yurlie

TTL and cache expiration are fundamentally timestamp problems. Convert, calculate, and format timestamps for your cache configuration and cron-based invalidation jobs with the free Yurlie Timestamp & Cron Tool.

Total Views: 4Category: developer

Try Yurlie Online Developer Tools

Run CIDR calculations, JSON formatting, Base64 encoding, and UUID generation instantly in your browser.

Explore Tools →