Skip to content

VOL 04 / CH 07 / LESSON 02

7.2 CDN Caching, Origin Pulling, and Traffic Routing ​

When many remote users download the same static asset, origin load rises quickly. The system must decide which responses are cacheable, which copies should sit near users, and when an edge should revalidate with the origin.

A CDN is a distributed edge platform, and caching is one of its most common capabilities. Cache hits can shorten the path to content and reduce origin load. The edge can also handle TLS, routing, and access policies. These benefits depend on cacheability, traffic mapping, and origin design. Merely enabling a CDN does not make every request faster.

1. The Basic Path of a Single Edge Request ​

text
client
  |
  | DNS answer / anycast route
  v
edge point of presence
  |-- fresh cache hit -----------------> response
  |-- stale entry -> revalidate origin -> 304 / new response
  `-- miss ----------------------------> origin or origin shield

A CDN doesn't necessarily have to push all files to global locations first. Many content items are pulled from the origin on the first request that reaches the edge, and only subsequent requests hit the cache; pre-positioning is also viable for large-scale launches or video workflows.

2. The cache key determines which requests share the same response ​

The basic HTTP cache key includes the request method and target URI, including its query component. GET-only caches often leave the method implicit. Vary additionally distinguishes representations by selected request fields. A CDN can configure query dimensions, but dropping or normalizing a parameter requires proving that it does not affect the response; omission is not the HTTP default.

A cache key that's too narrow might serve user A's personalized response to user B; one that's too broad could split each tracking query, cookie, or header variant into separate objects, lowering hit ratio and amplifying storage.

Suppose a public product listing varies by page and a preselected language variant. Its contract confirms that utm_source affects neither content, response headers, nor access rights. This Python 3 model preserves meaningful query items and their order, dropping only that tracking parameter. Inputs are already parsed; it implements neither URL nor Accept-Language parsing:

python
def cache_key(path: str, query: tuple[tuple[str, str], ...], language: str):
    if language not in {"en", "zh"}:
        raise ValueError("unsupported language variant")
    effective_query = tuple((k, v) for k, v in query if k != "utm_source")
    return ("GET", "https", "shop.example.test", path, effective_query, language)

first = cache_key("/products", (("page", "1"), ("utm_source", "mail")), "en")
second = cache_key("/products", (("page", "1"), ("utm_source", "search")), "en")
assert first == second
assert first != cache_key("/products", (("page", "2"),), "en")
assert first != cache_key("/products", (("page", "1"),), "zh")
print("tracking shares; page and language stay separate")

If prices also vary by account, this key is insufficient. Prefer excluding such private responses from shared caching or designing a reviewed isolated cache; appending every cookie is not proof of isolation.

The cache policy should clearly specify:

  • Which methods and status codes can be stored;
  • Whether authenticated or personalized responses may enter the shared cache;
  • Which query, header, and cookie dimensions form the key;
  • How to handle Cache-Control, Expires, ETag, and Last-Modified;
  • Whether stale responses may be reused during origin errors or revalidation;
  • Maximum object size and negative/error caching policy.

3. Freshness, validation, and purge are three separate things ​

A fresh response can be reused when request constraints, Vary, and cache directives permit it. A stale response can be validated using ETag or Last-Modified. A 304 updates matching stored-response metadata while retaining its body; it does not authorize arbitrary cached responses or imply that Age is always reset to zero.

Purge/invalidation is a CDN vendor control plane capability, not an HTTP caching semantic guarantee of global instant transactions. Purge takes propagation time and may fail to hit the expected object due to key mismatches.

Static assets are typically well-suited for content-addressed or versioned URLs:

text
/assets/app.4f3a91c2.js

When content changes, a new URL is generated; old URLs can be set with a long freshness lifetime. The HTML entry point uses a shorter freshness or validation period to reference the new asset URL. This is easier to reason about than having to precisely purge old objects globally on every release.

For example, an immutable public JS resource can use Cache-Control: public, max-age=31536000. Public HTML can use Cache-Control: no-cache with an ETag to validate each reuse. no-cache permits storage; no-store prohibits it. Retain old assets while old HTML, offline pages, or rollback versions may still reference them.

For another public response with Cache-Control: max-age=60, stale-while-revalidate=30, assume no other directive forbids stale reuse. This model uses an already calculated current_age and conservatively requires validation at 90 seconds. It implements neither a full cache nor background requests:

python
def reuse_policy(age: int) -> str:
    if age < 0:
        raise ValueError("negative age")
    if age < 60:
        return "fresh"
    if age < 90:
        return "stale: may serve while revalidating"
    return "revalidate before reuse"

for age in [45, 60, 75, 90, 95]:
    print(age, reuse_policy(age))
# 45 fresh
# 60 stale: may serve while revalidating
# 75 stale: may serve while revalidating
# 90 revalidate before reuse
# 95 revalidate before reuse

4. Miss Storm and Origin Shield ​

When a popular object expires, a large number of edges/requests can simultaneously origin-pull, causing a cache stampede. Possible mitigations include:

  • request collapsing / single flight: Only one origin fetch for a key within the collapsing scope;
  • freshness jitter: avoid a large number of objects expiring at the same time;
  • stale-while-revalidate / stale-if-error strategy;
  • origin shield: multiple edge requests aggregate misses toward a single shield cache;
  • origin capacity limit and circuit breaker.

A shield adds a hop and protects the origin by aggregating misses. It does not lower latency for every request.

Collapsing has a scope. Thirty edge nodes collapsing locally to one fetch each can still produce thirty origin requests; a shared shield can collapse them further. To stagger refreshes, refreshing early or shortening local freshness is easier to keep correct than extending freshness beyond what the origin allows.

5. Geo-aware DNS and anycast do not guarantee "geographic proximity" ​

A CDN can map a recursive resolver's or client's subnet to a specific edge cluster via DNS responses, or enable multiple sites to advertise the same anycast prefix, with the path selected by BGP route selection. In practice, deployments often combine multiple mechanisms.

GeoDNS often sees recursive resolver addresses, not necessarily end-user addresses; EDNS Client Subnet can pass along part of the client prefix, but introduces a trade-off between privacy and cache fragmentation. DNS answers are still cached for the duration of their TTL and cannot make per-request instantaneous load decisions.

Anycast chooses the best path based on routing policy, not the shortest Euclidean distance or the lowest latency site. BGP itself does not consider CDN application queue depth. Operators can influence the mapping through route advertisement or withdrawal, communities, and traffic engineering, but additional health signals and failover design are required.

6. Observe the cache, don't guess the brand from vendor headers ​

bash
curl -sS -D - -o /dev/null https://www.example.com/assets/app.js

Inspect:

  • Cache-Control, Expires, ETag, Last-Modified;
  • Age is the current age information of the response in the cache system, not evidence of a hit on a specific CDN brand;
  • Via, Server-Timing, or vendor-specific cache-status header; their semantics should be checked in the provider's documentation;
  • Whether the response is a hit, miss, bypass, stale reuse, or revalidated;
  • Can the Request ID/trace be correlated with the origin log?

Searching for strings like cf- or x-cache solely in the response header is not a reliable way to determine whether CDN is being used: headers can be deleted, modified, or generated by other proxies.

The URL illustrates the command, not a guaranteed existing CDN asset. Use a resource from your own service and compare repeated requests for the same key with origin logs. Age estimates time since origin generation or successful validation and can include upstream cache residence; it is not simply time spent at this edge.

7. Cache Correctness and Security ​

A CDN is a shared intermediary; a cache policy error can amplify data leakage. Focus your inspection on:

  • Is the authenticated/private response stored in the shared cache?
  • Does the cache key omit request headers, cookies, or query parameters that change the response?
  • Can an untrusted host/header pollute a generated redirect or absolute URL?
  • Is Origin accessible only to trusted CDN endpoints, and how is edge-to-origin authentication performed?
  • Signed URL/cookie expiration, scope, and key rotation;
  • Can compression/range requests and object size limits be abused?

When TLS is terminated at the client-to-edge level, the edge can see the plaintext data in order to perform HTTP caching and routing. Edge-to-origin represents a separate security boundary that requires its own TLS authentication, authorization, and certificate lifecycle management.

Set-Cookie alone does not prohibit HTTP caching. Personal responses need appropriate Cache-Control directives and CDN rules that preserve them; Vary: Cookie is not an authorization check. Shared reuse of Authorization requests has additional requirements and cannot be treated like an ordinary public GET.

8. Acceptance Questions ​

  1. Design separate cache policies for versioned JS assets and HTML entry points.
  2. Why can Age alone not prove that the response comes from a specific CDN?
  3. Explain the failure modes of cache keys that are too narrow versus too wide.
  4. Design request collapse, stale policy, and origin shield for a hot object's expiry.
  5. Why does anycast not equal application-aware load balancing?

Next, move on to 7.3 Load Balancing Algorithms, Health Checks, and Retry Boundaries.

References ​

Built with VitePress | Software Systems Atlas