Consider GET /products/42 served through a shared cache. This path can lower database load and latency for repeat reads, but it introduces a second copy whose freshness must be managed.
1. Route the request
An edge proxy or load balancer chooses a service instance. The request carries the product identifier and relevant authorization context. If responses differ by user or permission, the cache key and cache policy must prevent one caller from receiving another caller's data.
2. Build the cache key
The service derives a key from the resource identity and authorization context. A key that ignores tenant or user context is a cross-tenant data leak waiting to happen. A key that is too specific — including volatile fields like timestamps — defeats the cache entirely.
3. Check the cache
A hit returns a stored value subject to its expiry and invalidation rules. The response leaves the service immediately; no database work occurs.
A miss continues to the database. Concurrent misses on the same key can cause a thundering herd unless the system coalesces requests or limits duplicate work with a single-flight pattern.
4. Read from the database
The service queries the authoritative store, maps the row into an API representation, and returns it. This hop is where most of the per-request cost lives — a cache miss can be 10–100× more expensive than a cache hit, depending on the data size and database load.
5. Return and populate
The service returns the result and writes that value into the cache with a time-to-live. The cache write can fail independently of the database read; the service must decide whether the response still succeeds and how that failure is observed.
Caching is a consistency decision as well as a speed decision. Measure hit rate and stale-read impact before adding layers. The cache-backed system flow diagram shows this path visually, and the design-from-workload article puts it in the context of failure handling.