Skip to content

Application Caching

A cache keeps a reusable copy closer to the consumer to reduce latency, load, or cost. It trades freshness and complexity for reuse. Before adding one, measure the underlying operation and determine whether an index, query change, batch, or simpler local computation would solve the cause directly.

Common access strategies

Strategy Read and write behavior Important consequence
Cache-aside Application loads on a miss and invalidates or updates after a write Application owns race handling
Read-through Cache abstraction loads missing data Loader behavior becomes part of the cache contract
Write-through Write completes in cache and backing store synchronously Higher write latency, simpler read freshness
Write-behind Cache acknowledges before asynchronous persistence Data-loss and ordering policy is required

Time-to-live limits age; maximum size limits space. Neither proves freshness. Versioned keys and event-driven invalidation can narrow stale windows but add coordination. Every design should state whether stale data is permitted and what happens when the cache is unavailable.

Correct keys and values

Keys must include all dimensions that affect the response, especially tenant, authorization scope, locale, representation version, and query parameters. Cached mutable values can leak changes between callers; prefer immutable snapshots or defensive copies. Treat negative results separately because a short absence lifetime may be appropriate.

Stampedes and concurrent loads

When a popular entry expires, many callers may reload it simultaneously. This cache stampede can overload the source. Possible controls include request coalescing, bounded single-flight loading, randomized expiry, refresh-ahead, and serving an explicitly bounded stale value. Each needs a timeout and a policy for loader failure; a lock held around an external call can become another outage.

Spring method caching

Spring's @Cacheable provides declarative method caching through an abstraction. In the common proxy mode, only calls that pass through the proxy are intercepted; self-invocation does not activate caching. Key expressions, cache manager, serialization, expiry, and eviction remain application decisions. Never assume that adding the annotation defines consistency.

@Cacheable(cacheNames = "catalog", key = "#sku")
public ProductView findProduct(String sku) {
    return catalogClient.fetch(sku);
}

Test and observe

  • test miss, hit, expiry, eviction, invalidation, and source failure;
  • exercise concurrent misses for the same key;
  • measure hit ratio, load latency, evictions, size, and load failures;
  • avoid user IDs or cache keys as unbounded metric labels;
  • verify that cache failure degrades according to the stated contract.

See the official Spring cache abstraction.