Caching and Content Delivery - Complete Notes

A language-neutral guide to placing reusable copies of data safely, reasoning about freshness and invalidation, protecting origins from bursts, operating multi-layer caches, and explaining the trade-offs clearly in production and system-design interviews.

00. The cache mental model

A cache is a faster, limited, and disposable copy of data whose authority still belongs somewhere else.

Think of a busy restaurant. The pantry near the cook holds ingredients used often, while the warehouse holds the full stock. The pantry shortens each trip, but it has limited space and its labels can become wrong when the warehouse changes. A useful pantry needs rules for what enters, how long it remains trusted, what leaves first, and what happens when it is empty.

Caching exchanges one kind of work for another. It avoids repeated computation, storage reads, or network transfers, but introduces copies, freshness rules, memory pressure, coordination, and new failure modes. A cache is valuable only when the avoided cost is larger than the cost of managing those copies.

Term Precise meaning Design question
Source of truth The authoritative system allowed to decide the current value Where must writes commit before success is acknowledged?
Cache entry A value plus identity, freshness, metadata, and sometimes validation state Can two users or versions ever share this identity safely?
Hit A request satisfied from a reusable cached entry Was it fresh, validated, or deliberately stale?
Miss No reusable entry exists, so the underlying path must run Can the source absorb simultaneous misses?
Freshness The period or condition under which a copy may be reused What staleness can this business operation tolerate?
Invalidation Making a cached representation unusable after authoritative state changes What happens if the invalidation message is delayed or lost?
Eviction Removing an entry to reclaim limited capacity Which entry has the lowest expected future value?
Warm cache A cache containing much of the active working set How is capacity protected during restart or failover?
Start with correctness

Define ownership, key identity, allowed staleness, and failure behavior before selecting a cache product. "Put Redis in front" or "add a CDN" is not a complete design.

01. Why caches work

Caches work when future access is predictable enough that a small, fast layer can serve a large share of requests.

Locality and working sets

Temporal locality
Something used recently is likely to be used again soon, such as a trending product.
Spatial locality
Items near a requested item are likely to be requested, such as adjacent bytes in a file.
Frequency skew
A small set receives a large fraction of traffic, such as popular profile pages.
Working set
The data actively needed during a relevant period, not the total stored dataset.

If a 10 TB catalog has a 4 GB hourly working set, a 6 GB cache may perform well. If every request chooses a random object uniformly from 10 TB, the same cache will churn and provide little value. Measure reuse-distance distributions and key popularity instead of assuming a universal percentage of the database belongs in memory.

Hit ratio, byte hit ratio, and miss cost

Basic cache economics
request_hit_ratio = cache_hits / cache_lookups
byte_hit_ratio    = bytes_served_from_cache / total_bytes_served

average_latency =
  hit_ratio * hit_latency
  + (1 - hit_ratio) * miss_latency

origin_requests_per_second =
  total_requests_per_second * (1 - hit_ratio)

A request hit ratio treats a 1 KB response and a 1 GB video equally. A CDN often cares about byte hit ratio because bandwidth and origin egress dominate cost. An application cache may care more about avoided database CPU or tail latency. Count hits only when they satisfy correctness. Serving the wrong tenant's value is not a successful hit.

Example Without cache With 95% hit ratio
20,000 reads/s, source takes 40 ms 20,000 source reads/s About 1,000 source reads/s in steady state
Cache hit takes 2 ms Average near 40 ms Average near 3.9 ms before queueing effects
Cache becomes empty No behavior change Source load can jump 20 times immediately
Averages hide the dangerous part

Misses are usually slower than hits and may queue behind other misses. Track hit and miss latency separately at p50, p95, p99, and p99.9. A high overall hit ratio can still hide a severe hot-key or cold-start tail.

02. Cache layers and placement

Placement decides latency, sharing, failure scope, consistency, and who controls the copy.

Layer Useful for Main limitation
CPU and operating-system cache Instructions, memory pages, filesystem blocks Mostly implicit and local to one machine
Browser or mobile client Static assets, HTTP responses, offline data Hard to purge and physically outside server control
Process-local application cache Small computed values, metadata, configuration Duplicated across instances and lost on restart
Distributed application cache Shared objects, sessions when appropriate, expensive query results Network hop, serialization cost, shared failure domain
Database cache Pages, plans, materialized results, indexes May still consume database CPU and connections
Reverse proxy Whole HTTP responses near the application Cache key and authentication mistakes affect many users
Edge cache or CDN Static and explicitly cacheable dynamic content near users Globally distributed invalidation and privacy risk

Multi-level caching is common: browser, edge, regional shield, application cache, then database buffer cache. Each layer should have a named owner, observable behavior, and a freshness policy. More layers can reduce latency and origin traffic, but make incident diagnosis and invalidation harder. An entry might be absent in one layer, stale in another, and fresh in a third.

Private and shared caches

A private cache serves one user or process. A shared cache serves many users. The distinction is a security boundary. A response personalized by cookies, authorization, locale, experiment, or tenant must not enter a shared cache unless every relevant dimension is represented safely in the cache key and the response is explicitly allowed to be shared.

Content delivery networks

A CDN stores reusable HTTP objects at geographically distributed points of presence. A nearby edge reduces round-trip time and origin bandwidth. A tiered topology can let an edge miss consult a regional or upper-tier cache before the origin, concentrating origin connections and reducing duplicate fills. It does not remove the need for origin capacity, because a purge, expiry wave, routing shift, or new popular object can still create misses.

03. Read caching patterns

Cache-aside, or lazy loading

The application checks the cache, reads the source on a miss, and places the result in the cache. It is simple and keeps the cache focused on requested data.

Cache-aside read path
READ(key):
  cached = cache.get(key)
  if cached exists and cached.version is acceptable:
    return cached.value

  value = source.read(key)
  cache.put(key, value, ttl = policy_for(value))
  return value

Client -> Service -> Cache
                    | hit  -> value
                    | miss
                    v
                  Source -> value -> Cache -> Client

The race is important. A reader can miss, read old source state, then populate the cache after a concurrent writer invalidates it. Version checks, delete-after-write ordering, short TTLs, or a second invalidation can bound the problem. For strict correctness, read directly from the authority or use a cache protocol that validates versions.

Read-through

The cache abstraction loads from the source when a key is absent. Callers see one interface. This centralizes loading, TTL, and coalescing, but the loader must preserve source authorization, cancellation, timeouts, and error semantics. A generic cache library cannot guess business freshness.

Memoization

Memoization caches a function result by its inputs. It is safe when the function is deterministic for the complete key. Hidden inputs such as current time, feature flags, permissions, model versions, locale, and database state must either enter the key or make the result non-cacheable. Bound memoization by size and lifetime. An unbounded map is a memory leak with a convenient name.

Negative caching

Negative caching stores "not found," empty, or known failure results briefly. It protects the source from repeated requests for absent keys and automated enumeration. Use a shorter TTL than for existing data, because a missing object can be created. Distinguish authoritative absence from temporary source failure. Never cache an authorization failure under a key shared by other users.

04. Write caching and consistency patterns

Pattern Write flow Benefit Primary risk
Write-through Write cache layer, which synchronously writes authority Cache is warm after acknowledged writes Added write latency and cache dependency
Write-around Write authority, then invalidate or bypass cache One-time writes do not pollute cache Next read misses
Write-behind Accept into cache or queue, persist later Low latency and batchable writes Data loss, reordering, and complex recovery
Refresh-ahead Refresh popular entries before expiry Fewer user-visible misses Refreshes unused data and can overload source

Write-behind makes the cache or durable queue part of the authoritative write path. It is no longer "just a cache." Acknowledged writes need replication, durable logging, idempotent replay, ordering rules, backpressure, and reconciliation. If losing an accepted payment or inventory update is unacceptable, a volatile cache is not sufficient.

A safe write and invalidation flow

Database first, then invalidate with durable repair
UPDATE_PRODUCT(command):
  begin source transaction
    update product, increment version
    append cache-invalidation event to transactional outbox
  commit

  best_effort cache.delete(product_key)
  return committed version

RELAY:
  repeatedly publish unprocessed outbox events

INVALIDATION_CONSUMER:
  delete cache key or reject versions older than event.version
  acknowledge event only after the cache action succeeds

The direct deletion gives low propagation latency. The transactional outbox repairs a lost deletion after a crash. TTL is the final bound if both paths fail. A version in the cached value prevents an old fill from overwriting a newer one when the cache supports compare-and-set or the application checks versions.

Failure point State Recovery
Source transaction fails No committed change and no valid outbox event Return failure; do not invalidate as if write succeeded
Process crashes after commit Source is new, cache may be old Outbox relay invalidates; TTL bounds delay
Invalidation delivered twice Same key deleted repeatedly Deletion is idempotent
Old miss fill races new write Old value may be reinserted Versioned put, second delete, or short TTL
Cache unavailable Source remains authoritative Choose bounded source fallback or fail closed by domain

05. Freshness, TTL, validation, and invalidation

A TTL is a business staleness decision expressed as time, not a correctness mechanism by itself.

TTL and time to idle

Time to live, or TTL
Expire a fixed duration after creation or refresh, whether or not the entry is read.
Time to idle, or TTI
Expire after the entry has not been accessed for a duration.
Absolute expiry
Expire at a specified instant, such as the end of a pricing promotion.

Choose TTL from the harm caused by stale data, update frequency, source load, and recovery needs. Product descriptions might tolerate minutes. Authorization revocation may tolerate seconds or no shared caching at all. A longer TTL raises hit ratio and outage tolerance but lengthens stale exposure. Add random jitter so millions of entries do not expire simultaneously.

Jittered expiry
effective_ttl = base_ttl * random_between(0.8, 1.2)

Version validation

Validation asks whether a stored representation is still current. HTTP uses validators such as ETag with If-None-Match, or Last-Modified with If-Modified-Since. A 304 Not Modified response avoids retransmitting the body while confirming reuse. Application caches can store entity versions, revision numbers, or content hashes. Validation still costs a round trip, but may save computation and bandwidth.

Versioned keys and tagging

A versioned key such as asset:sha256:... or catalog:v42:item:123 turns invalidation into publishing a new name. Immutable assets can receive long lifetimes because old URLs still identify old bytes. The application must switch references atomically enough for its consistency needs, and old versions still require eventual cleanup.

Tags group entries by dependency, such as product:123 or tenant:acme:catalog. Tag invalidation is operationally convenient, but the reverse index consumes memory and a broad purge can cause a miss storm. Prefer precise tags and stage large invalidations.

Serving stale intentionally

Stale-while-revalidate serves a stale value immediately while one controlled refresh happens in the background. Stale-if-error serves stale data when the source fails. Both trade freshness for availability and lower tail latency. State explicit maximum stale windows, exclude safety-critical values, preserve authorization, and expose stale age to operators.

Stale is a policy outcome

"The cache was stale" is not a full incident explanation. Ask whether the age was within policy, whether the client was told, whether the source was healthy, and whether the represented data was eligible for stale service.

06. HTTP caching and cache keys

HTTP already defines a distributed cache protocol. Use its semantics before inventing custom headers.

Directive or field Meaning Common misunderstanding
max-age Freshness lifetime in seconds for receiving caches It is relative to response generation, not a purge guarantee
s-maxage Shared-cache freshness overriding max-age It does not set browser freshness
public Allows storage by shared caches where otherwise restricted It does not make private data safe to share
private Response is intended for a private cache, not a shared cache It does not encrypt the response
no-cache Stored response requires validation before reuse It does not mean "never store"
no-store Caches must not store the response under normal HTTP rules It cannot erase copies already leaked elsewhere
must-revalidate Stale response cannot normally be reused without successful validation It does not force validation while the entry is fresh
Vary Names request fields that affect representation selection Unbounded variation can destroy hit ratio
Age Approximate seconds since generation or successful validation It is not application object age
Separating browser and shared-cache freshness
Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=30
ETag: "product-123-v42"
Vary: Accept-Encoding

The cache key commonly begins with method, target URI, and selected request fields. Query normalization, host, scheme, headers, cookies, compression, locale, device variants, experiments, and authorization can change the representation. If a representation varies but the key does not, users receive wrong content. If the key varies on noisy data that does not affect the response, the cache fragments and hit ratio falls.

What should be cacheable?

  • Cache immutable, content-addressed assets for a long time.
  • Cache public catalog responses under an explicit freshness and purge policy.
  • Use private caching or no-store for personal financial, medical, or secret-bearing responses.
  • Do not assume only status 200 is cacheable. Define negative caching deliberately.
  • Do not cache unsafe method responses as if they were reusable reads.
  • Do not share responses merely because an authentication header was stripped at the edge.

07. Capacity and eviction algorithms

Eviction decides which copy to sacrifice when limited memory cannot hold the working set.

Policy Idea Works well when Weakness
LRU Evict least recently used Recent use predicts near-future use A large scan can evict a valuable working set
LFU Evict least frequently used Stable popularity matters Old popularity needs decay to avoid permanent winners
FIFO Evict oldest insertion Low overhead matters more than precision Ignores reuse
Random Evict a random candidate Access is uniform or metadata cost must be tiny May discard a very hot entry
TTL-first Prefer entries closest to expiry Application expiry predicts value Popularity is ignored
Size or cost aware Balance bytes, recomputation cost, and reuse Objects vary greatly in size or miss cost Needs better metadata and tuning

Production systems often approximate policies by sampling because exact global LRU or LFU metadata is expensive. Measure evictions, rejected writes, memory fragmentation, object-size distribution, and hit ratio by key family. Reserve headroom for metadata, replication buffers, network buffers, and allocator fragmentation. A configured 10 GB data limit does not mean the process needs only 10 GB of memory.

Admission is different from eviction

Eviction chooses what leaves. Admission chooses whether a new object deserves space at all. A single-use scan should often bypass a small cache, preserving frequently reused items. Size limits prevent a single large object from evicting thousands of valuable small objects. Separate caches or quotas can isolate workloads with different value and access patterns.

08. Stampedes, penetration, and hot keys

Cache stampede

A stampede occurs when many callers miss or observe expiry together and all perform the expensive fill. The cache normally hides source capacity limits, so a cold cache can turn a healthy source into an overloaded source.

Single-flight request coalescing
GET_OR_LOAD(key):
  value = cache.get(key)
  if fresh(value): return value

  if this process owns refresh(key):
    loaded = source.read(key, deadline)
    cache.put(key, loaded, jittered_ttl)
    publish result to waiters
    return loaded

  wait briefly for owner
  if fresh value appears: return it
  if eligible stale value exists: return stale
  otherwise fail within caller deadline

Single-flight or request coalescing allows one fill per key while other requests wait or receive stale data. It must be bounded by deadlines. A failed leader cannot hold every waiter forever. Distributed locks can coordinate across processes, but they add their own failure and fencing problems. Often per-node coalescing plus source concurrency limits is simpler and sufficient.

Probabilistic early refresh

Refreshing every entry at a fixed threshold still synchronizes clients. A probabilistic policy raises the chance of refresh as expiry approaches. Popular keys receive more opportunities to refresh early, while cold keys usually expire naturally. Only one winner should perform the refresh.

Conceptual early-refresh rule
remaining = expires_at - now
refresh_probability = clamp(refresh_window / max(remaining, 1), 0, 1)
if random() < refresh_probability and acquire_refresh_ownership(key):
  refresh(key)

Cache penetration

Penetration is repeated access to keys that never become cache hits, often due to invalid input, enumeration, or random identifiers. Validate syntax early, rate-limit abusive principals, use short negative caching for authoritative absence, and consider probabilistic membership filters where false positives are acceptable. Do not let a filter become the source of truth, because a stale false negative could hide a real object.

Hot keys

One extremely popular key can saturate a cache shard, network path, or serialization thread even when overall capacity looks healthy. Replicate read-only hot values, use near-caches, split large aggregates, cache pre-serialized forms, and apply per-key request coalescing. Detect hot keys by sampled access counts and shard imbalance without logging sensitive raw keys.

09. Large objects and content delivery

Large objects change the unit of value. Cache chunks or byte ranges when clients consume only parts, support resumable transfer, and avoid buffering a complete object in application memory. Compression saves bandwidth but costs CPU and can fragment variants. Precompress immutable assets when practical and include content encoding in representation selection.

Decision Trade-off
Whole-object caching Simple, but one large object can evict many smaller high-value entries
Chunk caching Improves partial reads and retries, but increases key count and metadata
Edge delivery Reduces distance and origin egress, but distributes invalidation globally
Signed URLs Offloads delivery, but expiry, cache key, and authorization scope must align
Origin shield Collapses regional misses, but becomes another dependency and concentration point

10. Security, privacy, and tenant isolation

A shared cache amplifies both performance and mistakes.

Cache poisoning and deception

Cache poisoning stores an attacker-influenced response under a key later used by victims. Unkeyed request headers, host manipulation, query normalization differences, response splitting, and inconsistent proxy parsing are common ingredients. Cache deception tricks a cache into storing a private dynamic response by making its path look static.

  • Canonicalize and validate scheme, host, path, query, and forwarding headers consistently.
  • Include every representation-changing input in the key, or reject it before caching.
  • Ignore attacker-controlled headers that should not influence origin output.
  • Do not cache authenticated responses by file extension or path shape alone.
  • Test duplicate headers, ambiguous paths, encoded separators, and parameter reordering.
  • Restrict purge permissions, log them, and protect them from replay.

Tenant and authorization boundaries

Prefix application keys with stable tenant identity and object version, not a display name. Authorize before returning a cached object unless the object is independently public. Do not put raw bearer tokens in keys or logs. If role or policy affects the representation, encode a stable authorization-policy version or keep the result private. Prevent one tenant from consuming all memory through quotas, admission controls, and size limits.

Sensitive data and retention

Encryption in transit and at rest protects some threats but does not fix cross-user reuse. Minimize personal data, align cache lifetime with deletion and residency obligations, and ensure backups or persistent tiers do not silently extend retention. A purge API should cover every relevant layer. For secrets, payment details, health records, and similarly sensitive views, bypass shared caches unless a reviewed design proves safe.

11. Failure policy: fail open, fail closed, or bypass

Domain Cache failure response Reason
Public product description Bounded stale response or source fallback Availability usually outweighs brief staleness
Authorization revocation Fail closed or validate against authority Stale allow decisions can expose protected data
Rate-limit counter Domain-specific conservative limit Fail open invites abuse; fail closed can cause outage
Recommendation feed Fallback to popular or older safe content Degraded personalization is acceptable
Inventory availability Use cache as hint, verify before commitment Overselling has a business correctness cost

Source fallback must be capacity-limited. If every cache client immediately bypasses a failed cache, the source can fail next. Use concurrency limits, deadlines, load shedding, and a degraded response. Cache errors should not be retried recursively through every layer.

12. End-to-end cached catalog design

Assume 50,000 product reads/s at peak, 100 product updates/s, a 300 ms acceptable public staleness target for prices, and a requirement to verify inventory during checkout.

Read architecture and trust boundaries
Browser private cache
        |
        v
CDN edge cache --> regional shield
        | miss
        v
Catalog API --> process near-cache --> distributed cache
                                         | miss
                                         v
                                  authoritative database

Checkout API always validates current price version and inventory before commitment.

Read path

  1. The public response key includes product ID, locale, currency, and representation version.
  2. The edge serves a fresh public response or revalidates through the shield.
  3. The API checks a tiny process-local cache, then the distributed cache.
  4. A miss is coalesced per product and loaded with a deadline from the database.
  5. The value stores product version, source commit time, and cache creation time.
  6. The API returns an ETag derived from the representation version.

Write and invalidation path

  1. An authorized command updates the product and increments its version transactionally.
  2. The same transaction appends an invalidation event.
  3. The writer performs a best-effort distributed-cache delete after commit.
  4. The relay publishes the event, and consumers delete near-cache and edge entries by tag.
  5. Consumers ignore older event versions and retry idempotently.
  6. A short price TTL bounds any missed invalidation; descriptions may have a longer TTL.

Capacity estimate

Suppose the active set is 2 million objects averaging 4 KB serialized. Raw values need about 8 GB. Add key, metadata, allocator, replication, and growth overhead rather than sizing at exactly 8 GB. With 95% application-cache hits, the database sees about 2,500 reads/s before edge hits. If the cache empties, the potential source demand is 50,000 reads/s, so warm-up must be rate-limited and the system must serve stale or shed load instead of assuming the database absorbs it.

13. Observability and troubleshooting

Signal Why it matters Useful breakdown
Hit, miss, stale, bypass, and error rate Separates successful reuse from all other outcomes Layer, key family, region, tenant class
Hit and miss latency distributions Shows whether the cache improves user experience p50, p95, p99, p99.9
Source request and error rate Measures actual offload and source protection Cache outcome and caller
Evictions, expirations, and rejected writes Reveals capacity or policy pressure Shard and policy
Memory, fragmentation, CPU, network, connections Finds cache-server saturation Node and shard
Coalesced waiters and load duration Detects stampedes and stuck leaders Key hash or safe key family
Entry age and invalidation lag Measures real freshness, not configured TTL Data class and region
Byte hit ratio and origin egress Measures content-delivery cost reduction Object size, media type, point of presence

Use correlation IDs across cache, source, and invalidation paths. Sample keys by a one-way digest or family to avoid exposing personal data. HTTP's standardized Cache-Status field can describe forwarding and hit behavior, but exposing detailed keys publicly can aid attackers, so restrict sensitive diagnostics.

Troubleshooting playbook

  1. Confirm whether users see wrong data, high latency, errors, or excess cost.
  2. Identify the affected layer, key family, tenant, region, and deployment version.
  3. Compare hit ratio with source load. A miss spike plus source saturation suggests cold cache.
  4. Check evictions, memory, skew, hot keys, and expiry waves.
  5. Inspect invalidation lag and compare cached version with source version.
  6. Mitigate with bounded stale service, fill limits, selective bypass, or precise purge.
  7. Avoid a global purge unless origin capacity has been modeled and protected.
  8. After recovery, replay invalidations and reconcile sampled keys against the source.

14. Production and deployment practices

  • Assign separate TTL and failure policies by data class, not one global default.
  • Use deterministic key schemas with explicit tenant, schema, and representation versions.
  • Deploy key-schema changes with dual read or dual fill only for a bounded migration window.
  • Canary cache-policy changes and watch source load, not only cache hit ratio.
  • Warm only the proven hot set at a controlled rate. Avoid replaying the entire database.
  • Distribute replicas and shards across failure domains without assuming replicas add capacity.
  • Set cache timeouts below caller deadlines and bound connection pools and waiting queues.
  • Keep an emergency bypass and purge mechanism protected by authorization and audit logging.
  • Practice cache loss, partial shard failure, stale invalidations, and origin slowdown.
  • Track cost per million requests and per cached GB, including network and operational cost.

15. Common mistakes and safer corrections

Mistake Why it fails Safer correction
Using only object ID as key Tenant, locale, version, or authorization variants collide Define and test complete representation identity
Very long TTL with no invalidation repair Lost invalidation causes long stale exposure Durable invalidation plus TTL as a bound
Deleting cache before committing source A reader can refill old state before the write commits Commit source first, then invalidate, with race protection
No stampede protection Expiry converts one expensive read into thousands Jitter, coalescing, early refresh, and source limits
Unlimited source fallback Cache outage becomes database outage Bound concurrency and serve safe degraded results
Global purge during peak Every layer misses together Version keys or staged targeted invalidation
Unbounded local map Memory grows until process failure Bound weight, lifetime, and admission
Caching errors as absence Temporary failure becomes a believable not-found result Classify errors and negative-cache only authoritative absence
One shared cache for unrelated workloads A scan or tenant can evict critical data Quotas, admission, or separate capacity pools
Reporting one global hit ratio Hot endpoints hide broken or unsafe key families Measure outcome by layer, family, size, and region

16. Testing strategy

Unit and property tests

  • Prove key construction distinguishes tenants, locales, versions, permissions, and variants.
  • Use a controllable clock to test fresh, stale, expired, and jitter boundaries.
  • Verify negative results use different types and lifetimes from source errors.
  • Property-test that canonical query inputs produce the intended key and no collisions.
  • Test serialization compatibility across rolling deployments.

Integration and concurrency tests

  • Run real cache and source services for TTL, eviction, reconnection, and atomic-operation tests.
  • Race a cache miss against an update and verify old data cannot persist beyond policy.
  • Send 1,000 simultaneous misses for one key and verify source calls remain bounded.
  • Test duplicated, reordered, delayed, and lost invalidation events.
  • Verify HTTP validators, Vary, Age, and private/shared behavior.
  • Test cache poisoning inputs across application, proxy, and CDN parsing.

Load, fault, and recovery tests

  • Measure warm and cold throughput, tail latency, byte hit ratio, and source offload.
  • Restart the cache at peak test load and confirm controlled warm-up.
  • Fail one shard, add latency, exhaust connections, and corrupt selected serialized entries.
  • Expire a large key cohort together to expose synchronized refresh.
  • Disable invalidation delivery, then restore it and verify replay and reconciliation.
  • Run restore or regional failover while measuring stale age and origin capacity.

17. Hands-on exercises

Exercise 1: Key design review

Design keys for a product page varying by tenant, product, currency, locale, tax region, login state, experiment, and schema version. Decide which dimensions truly change the response. Expected reasoning: unnecessary dimensions reduce hit ratio, but omitting a real dimension causes cross-context data leakage. Prefer public base content plus separately private personalization when possible.

Exercise 2: Stampede simulator

Build a small approved-language service with a 100 ms source loader. Fire 500 concurrent requests after expiry. Compare no protection, one process-local single-flight, jitter, and bounded stale service. Expected reasoning: compare source-call count and tail latency, not only client success.

Exercise 3: Invalidation failure timeline

Write timelines for a writer crash before commit, after commit but before delete, and after delete but before event acknowledgement. Expected reasoning: source commit defines truth, direct deletion reduces normal latency, durable outbox retries repair crashes, and TTL bounds residual staleness.

Exercise 4: CDN policy

Define policies for fingerprinted JavaScript, a public article, a personalized dashboard, and a signed file download. Expected reasoning: use immutable versioned assets for long caching, validators and bounded freshness for articles, private or no-store for sensitive dashboards, and align signed authorization lifetime with edge behavior.

Exercise 5: Capacity during cache loss

A service handles 80,000 reads/s at 98% hits while the database safely handles 4,000 reads/s. Determine cache-loss behavior. Expected reasoning: normal source load is about 1,600 reads/s, but complete loss exposes 80,000 reads/s. Reserve source headroom, limit fills, prioritize critical reads, serve bounded stale data, and shed excess demand.

18. Interview questions and model answers

1. Why is cache invalidation considered difficult?

The authoritative update and removal of every distributed copy are usually separate operations. Processes can crash between them, messages can be lost or reordered, readers can repopulate old state, and different layers have different lifetimes. A strong answer names an allowed staleness bound and combines ordering, durable invalidation, versions, and TTL rather than claiming one delete is atomic with the database.

Follow-up: What if a price must never be stale at checkout?

Treat cached price as a browsing hint and validate current price and version inside the authoritative checkout transaction before commitment.

2. Compare cache-aside and read-through.

In cache-aside, application code performs get, source load, and put. It is explicit and flexible but easily duplicated. In read-through, a cache abstraction loads automatically, centralizing policy but hiding source behavior unless carefully designed. Both still need key correctness, coalescing, deadlines, error classification, and invalidation.

3. How do you choose a TTL?

Start from the maximum harmful staleness for the data class. Then consider update frequency, miss cost, invalidation reliability, source capacity, and outage policy. Validate with measured hit ratio and stale age. Add jitter. Different fields or endpoints may need different TTLs.

4. How do you prevent a cache stampede?

Combine staggered expiry, single-flight request coalescing, refresh-ahead or probabilistic early refresh for hot entries, safe stale serving, and hard source concurrency limits. Bound waiters by their deadlines and handle a failed refresh owner.

Follow-up: Why is a distributed lock not automatically the best answer?

It adds coordination latency, availability dependence, expiry races, and ownership problems. Per-node coalescing plus source limits may provide enough protection with fewer failure modes.

5. What is the difference between eviction and expiry?

Expiry removes or marks an entry unusable because its policy lifetime ended. Eviction removes an entry to reclaim capacity, potentially while it is fresh. A high eviction rate can reduce hit ratio even when TTLs are long.

6. Explain LRU versus LFU.

LRU favors recently used items and adapts quickly, but scans can displace the working set. LFU favors frequently used items and protects stable hot keys, but needs frequency decay when popularity changes. Real systems often approximate both for lower metadata cost.

7. What makes a cache key safe?

It uniquely describes the representation and its security boundary. Include stable tenant, object, schema, locale, content-negotiation, and policy dimensions that truly alter output. Canonicalize inputs consistently. Exclude secrets and noisy dimensions that do not affect output. Test collisions and cross-user behavior.

8. When should a service fail open or fail closed?

Base the decision on harm. Public content can often use bounded stale data. Authorization commonly fails closed because a stale allow is dangerous. Rate limiting needs a deliberate conservative degraded mode because pure fail-open permits abuse and pure fail-closed causes an outage.

9. Why can a cache outage cause a database outage?

The database is usually sized for misses, not total traffic. Removing a 98% hit cache can increase database demand 50 times. Unbounded fallback, synchronized retries, and cold fills then amplify queueing. Protect the source with limits, stale service, staged warm-up, and load shedding.

10. How does HTTP revalidation differ from fetching again?

A client sends a validator such as an ETag. If the representation is unchanged, the origin returns 304 without the full body. It still requires a request and origin decision, but saves transfer and possibly rendering work.

11. How would you cache multi-tenant data?

Use stable tenant identity in keys, authenticate and authorize at the correct boundary, separate public from private representations, apply tenant quotas, encrypt transport, redact diagnostics, and test that one tenant cannot infer existence, values, timing, or key structure of another.

12. What metrics prove a cache helps?

Hit and byte-hit ratios are only the start. Show reduced source requests, CPU, connections, bandwidth, and cost, plus better user latency. Also show staleness, invalidation lag, cache errors, evictions, hot-key skew, and behavior during cold start. A high hit ratio with wrong data is a failure.

13. When is write-behind appropriate?

When delayed persistence and reordering are acceptable or when the buffering layer is engineered as a durable write system. It needs durable replication, idempotent replay, backpressure, reconciliation, and explicit acknowledgement semantics. It is not appropriate merely because synchronous writes seem slow.

14. How do versioned keys help?

A new value receives a new identity, so old copies cannot masquerade as new. This allows long lifetimes for immutable content and reduces purge dependence. The system still needs a way to publish current references and garbage-collect old versions.

15. Design a cached content feed.

Clarify personalization, freshness, fan-out, privacy, and update rate. Cache public ranking candidates and per-user page IDs separately, fetch mutable item details by version, use short negative caching for removed items, coalesce misses, and keep authorization outside unsafe shared layers. Measure feed age and source offload. Use a safe older feed during a ranking outage.

19. Revision cheat sheet

  • A cache is a limited, faster, disposable copy. The authority must be explicit.
  • Value comes from locality, reuse, high miss cost, and a manageable working set.
  • Measure request hits, byte hits, source offload, latency, cost, and correctness.
  • Cache layers include client, process, distributed, database, proxy, edge, and CDN.
  • Cache-aside is explicit; read-through centralizes loading; memoization keys function inputs.
  • Write-through persists synchronously; write-behind changes the durability boundary.
  • Commit authority first, invalidate after, repair durably, and bound with TTL.
  • TTL is allowed staleness. Add jitter and use different policies by data class.
  • Versioned keys avoid reuse across versions; tags enable grouped invalidation.
  • Coalesce concurrent misses and limit source load to stop stampedes.
  • LRU uses recency; LFU uses frequency; admission prevents low-value pollution.
  • Negative-cache only authoritative absence and usually use a short TTL.
  • Hot keys require replication, near-caches, coalescing, or data decomposition.
  • HTTP no-cache means validate; no-store means do not store.
  • A shared cache key is a security boundary. Test poisoning and tenant isolation.
  • Choose fail-open, fail-closed, stale, or bypass behavior from business harm.
  • Cold-start and purge capacity matter more than steady-state capacity.

20. Primary official references

Last reviewed · July 2026 · part of knowledge-base