# CDN — Derived Rules > Derives from `domains/edge/first-principles.md`. Applies P1 > (Proximity is the Design Driver) and P6 (Cache Invalidation is > Explicit) primarily, with P5 (idempotent cache fill), P8 > (geographic distribution), and P9 (identity at the edge). For the > edge-cache-vs-origin decision, see the decision matrix below. > Cross-links `domains/performance/frontend` for generic caching, > `domains/security/input-validation` for cache poisoning, and > `domains/observability/metrics` for cache-hit ratio. ## What a CDN Is (P1 Proximity is the Design Driver) - A content delivery network is a fleet of PoPs (points of presence) placed near users. The PoP serves cached content; the origin is the authoritative source. The CDN's whole purpose is P1: compute (the cache) is placed near the user so the round trip to the origin does not bound latency. Latency is a correctness constraint at the edge (C1), not a performance preference. - The CDN is the canonical edge-cache architecture (Akamai, Cloudflare, Fastly): PoPs near users, origin shielding, cache-key normalization, purge APIs. Atelier derives the placement/invalidation principles, not the vendor config. - The boundary with `domains/performance/frontend` is per D-061: performance owns *generic* caching and optimization (cache what is expensive, stable, read often — `performance/P5 Caching with Intent`); edge owns the *geographic, partition-aware* placement and invalidation angle. A CDN is an edge concern because its defining trait is geographic distribution (P8) and partition-aware invalidation (P6), not measurement. ## Cache Key Design (P6 Cache Invalidation is Explicit) - The cache key is the contract between the URL and the cached representation. A key that varies on the wrong dimensions serves the wrong content; a key that varies on too many dimensions collapses the hit ratio. Key design *is* the invalidation surface: a key that includes a content hash or version segment makes invalidation explicit; a key that ignores `Vary` headers serves stale variants. - Normalize the key: lower-case the host, strip default ports, sort query parameters, ignore tracking parameters. A non-normalized key is a cache-poisoning vector (see `domains/security/input-validation`) and a hit-ratio destroyer (see `domains/observability/metrics`). - A cache with no explicit key strategy is a TTL-less cache under partition (P6 violation): staleness is silent and unbounded. ```http # Cache key derivation: vary on what changes content, ignore what # does not. The key is the tuple (host, normalized-path, sorted- # query, Vary-headers); the cache entry is the representation + TTL. Cache-Key: example.com /api/v1/products?sort=price®ion=us Vary:Accept-Encoding Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=300 Vary: Accept-Encoding ``` - `max-age` bounds the browser cache; `s-maxage` bounds the CDN PoP; `stale-while-revalidate` allows serving stale while refetching. Each is an explicit invalidation strategy (P6). ## TTL vs Explicit Invalidation (P6, C3 Simplicity) - **TTL-based invalidation** (`max-age`, `s-maxage`): the cache entry expires after a duration. Simple, no origin contact required to invalidate, but bounded staleness is the contract — the entry may be stale up to TTL. Fits content where eventual consistency is acceptable (asset fingerprints, lists, derived images). - **Explicit invalidation** (purge, surrogate keys): the operator signals the cache to drop entries. Tighter staleness bounds, but requires the origin or operator to know which entries to purge. Fits content where staleness is a correctness defect (price updates, availability, breaking news). - A TTL-less cache with no explicit invalidation is a P6 violation: stale-forever under partition. Every cache must have one or the other (or both), and the choice is documented per content type. ## Cache-Hit / Miss / Origin-Fetch (P5, P6) - **Hit**: the PoP serves from cache. Latency is PoP-local (P1). - **Miss**: the PoP has no entry; it fetches from the origin (or an origin-shield PoP). The fetch must be idempotent (P5) — a retried miss must not corrupt the cache or double-write side effects. - **Revalidate**: the PoP holds a stale entry and asks the origin (`If-None-Match`, `If-Modified-Since`); a 304 refreshes the TTL without re-fetching the body. Revalidation is the bandwidth-economical middle ground (C8). ```http # Conditional revalidation — the PoP asks the origin "is this still # current?" The 304 response refreshes the TTL without a body. GET /api/v1/products HTTP/1.1 Host: example.com If-None-Match: "etag-7a3f" HTTP/1.1 304 Not Modified ETag: "etag-7a3f" Cache-Control: s-maxage=600 ``` - A cache-hit ratio that is not measured is a gate on noise — see `domains/observability/metrics` for the SLI/SLO discipline that makes the hit ratio a meaningful signal. A CDN with no hit-ratio metric is operating blind (P10 analog). ## Origin Shielding (P1, P8, C8 Economy) - Origin shielding routes all origin fetches through a single shield PoP (or shield region). The shield absorbs the thundering-herd: 10 000 PoPs missing the same URL fetch the origin once, not 10 000 times. This is C8 Economy (origin bandwidth is bounded) and P1 (the shield is itself a proximity layer for the origin). - Shielding is a geographic decision (P8): the shield sits in a region close to the origin, not close to the user. The shield is the inner ring of the CDN; the user-facing PoPs are the outer ring. - A CDN without origin shielding under a stampede will overload the origin; a shield that is itself partitioned from the origin must degrade gracefully (P7) — serve stale per `stale-while-revalidate` rather than 500. ## Purge Strategies (P6, C3 Simplicity) | Strategy | Granularity | Latency to Invalidate | Cost | Best for | |----------|-------------|-----------------------|------|----------| | URL purge | One URL | Seconds | Low (one entry) | Surgical fixes, single-page corrections | | Soft purge | One URL (mark stale, serve while refetch) | Seconds | Low | High-traffic URLs where a hard purge causes a stampede | | Surrogate-key purge | A tag set (e.g., `product:123`, `category:shoes`) | Seconds | Medium (key indexing) | Related-content invalidation (a product update purges all its category pages) | | Wildcard purge | A path prefix or pattern | Seconds to minutes | High (scan) | Site-wide template changes | | All-cache purge | Everything | Seconds | Very high (origin stampede) | Disaster recovery only; never the steady-state invalidation path | - Surrogate-key purge (Fastly, Akamai) is the highest-value strategy: tag cache entries with content keys, then purge by tag. This is explicit invalidation at scale (P6) without the origin stampede of an all-cache purge. - An all-cache purge as the steady-state invalidation path is a P6 violation dressed as a feature — it pushes the origin load back to 100% miss, defeating the CDN's purpose (P1). ## Cache Poisoning Prevention (P9, cross-link security/input-validation) - A cache poisoned by a crafted request (a URL with a malicious header that gets cached and served to others) is a correctness defect (C1) and a security breach (P9 — the edge node is exploited). Prevent poisoning by: - Normalizing the cache key (strip untrusted query parameters, ignore unknown headers, lower-case the host). - Validating `Vary` against an allow-list; never `Vary: *` on a shared cache (poisonable via header injection). - Treating uncacheable responses (`Set-Cookie`, `Cache-Control: private`) as never-stored. - See `domains/security/input-validation` for the general input-validation discipline the cache key must follow. The cache key is a validation surface; a non-validated key is an attack surface. ## Multi-CDN Routing (P8 Geographic Distribution) - A multi-CDN strategy routes each request to the best PoP across providers (Akamai + Cloudflare + Fastly). Routing is location-aware (P8): latency, cost, and availability vary by region and provider. The DNS layer (or a client-side router) selects the CDN per request. - Multi-CDN is a P8 decision, not a vendor-management decision: geographic distribution is the first-class constraint. A single-CDN deployment routes everything to one provider's PoPs; a multi-CDN deployment routes by region, latency, and cost. - Invalidation across multiple CDNs is harder (P6): each provider has its own purge API and surrogate-key scheme. A multi-CDN purge must fan out to all providers; a purge that reaches only one CDN leaves the others stale. Track purge completion per provider — see `domains/observability/metrics` for the per-CDN hit-ratio and purge-latency signals. ```http # A CDN config example: cache-control headers + a purge rule. # Origin response: declare the cache contract (P6). HTTP/1.1 200 OK Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=300 Surrogate-Key: product:123 category:shoes ETag: "etag-7a3f" Vary: Accept-Encoding # Purge rule (Fastly-style surrogate-key): when product 123 # updates, purge every cache entry tagged product:123 OR # category:shoes. Explicit, bounded, no origin stampede (P6). POST /service/svc1/purge Surrogate-Key: product:123 category:shoes # Returns: {"status": "ok", "id": "purge-abc"} — poll the purge # status to confirm completion across all PoPs (P8, P10). ``` ## Edge-Cache vs Origin — Decision Matrix (D-069) | Strategy | When | Latency | Origin Load | Correctness Risk | |----------|------|---------|-------------|------------------| | Serve from PoP (cache hit) | The PoP holds a fresh entry (within TTL or revalidated) | Lowest (PoP-local, P1) | None | Low — bounded by TTL staleness (P6) | | Serve stale while revalidate | The PoP holds a stale entry and `stale-while-revalidate` is set | Low (stale served immediately, refetch in background) | Background refetch (1 per entry) | Medium — stale served up to the revalidate window; acceptable for eventually-consistent content | | Fetch fresh from origin (miss) | The PoP has no entry, or the content is non-cacheable | High (origin round trip) | Full fetch per miss | Low — fresh by construction; the miss is the correctness floor | | Origin-shield fetch | Multiple PoPs miss the same URL; the shield collapses the herd | Medium (PoP → shield → origin) | Bounded to one origin fetch per shield (C8) | Low — shield is the inner ring; staleness bounded by shield TTL | | Purge and serve fresh | Explicit invalidation received (surrogate-key or URL purge) | Medium (purge propagates, then fresh fetch) | Full fetch post-purge | Lowest — explicit invalidation is the tightest staleness bound (P6) | | Serve from origin directly (bypass cache) | Content is non-cacheable (personalized, real-time) | Highest (every request hits origin) | Full fetch per request | Lowest for correctness, highest for origin load — use sparingly | - The default is **serve from PoP** when fresh, **fetch fresh from origin** on miss with **origin-shield** to bound origin load, and **purge and serve fresh** when explicit invalidation is required. Bypass-the-cache is for non-cacheable content only — bypassing for cacheable content is a P1 violation (you have defeated the CDN). - The correctness risk column is bounded by the invalidation strategy (P6): every row except "bypass" carries staleness risk that is bounded by TTL or explicit purge. A row with no invalidation strategy is a P6 violation. ## What Violates CDN Discipline | Violation | Principle | |-----------|-----------| | TTL-less edge cache under partition (stale-forever, no explicit invalidation) | P6 Cache Invalidation is Explicit | | Cache key that varies on untrusted query parameters (poisonable) | P6, P9 (`domains/security/input-validation`) | | All-cache purge as the steady-state invalidation path (origin stampede) | P6, C8 Economy | | Non-normalized cache key (case-sensitive host, unsorted query) | P6, `domains/security/input-validation` | | Bypass-the-cache for cacheable content | P1 Proximity is the Design Driver (defeats the CDN) | | Multi-CDN with no per-CDN purge completion tracking | P8, P10 (stale cache invisible to the operator) | | Origin fetch that is not idempotent under retry | P5 Edge Operations are Idempotent | | Cache-hit ratio not measured | P10, `domains/observability/metrics` | | Single-CDN deployed where geographic distribution requires multi-CDN | P8 Geographic Distribution | | Shield PoP that 500s instead of serving stale under partition | P7 Partial Degradation is Engineered |