20992883ff
---ci--- project: atelier phase: 1 milestone: v0.4 status: complete phase_role: execution phase_tag: v0.3.1 requirements: covered: [ATELIER-92, ATELIER-93, ATELIER-94, ATELIER-95, ATELIER-96] partial: [] ---/ci---
12 KiB
12 KiB
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-linksdomains/performance/frontendfor generic caching,domains/security/input-validationfor cache poisoning, anddomains/observability/metricsfor 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/frontendis 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
Varyheaders 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 (seedomains/observability/metrics). - A cache with no explicit key strategy is a TTL-less cache under partition (P6 violation): staleness is silent and unbounded.
# 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-agebounds the browser cache;s-maxagebounds the CDN PoP;stale-while-revalidateallows 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).
# 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/metricsfor 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-revalidaterather 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
Varyagainst an allow-list; neverVary: *on a shared cache (poisonable via header injection). - Treating uncacheable responses (
Set-Cookie,Cache-Control: private) as never-stored.
- See
domains/security/input-validationfor 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/metricsfor the per-CDN hit-ratio and purge-latency signals.
# 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 |