Skip to content

    System Design

    CDN and Edge Networking: Cache Keys, Shielding, Purge, Edge Compute

    How a CDN works as a tiered shared cache: edge, regional and shield layers, cache keys, freshness headers, purge models and the documented limits of edge compute.

    By Published 13 min read

    Share

    What you'll learn: By the end of this lesson you will be able to describe a CDN as a tiered shared cache in front of your origin, explain how edge locations, regional caches and a shield cut origin requests, say what a cache key is and why vendors default to different keys, steer a CDN with s-maxage, CDN-Cache-Control and stale directives, compare purge by URL, tag, prefix and everything with versioned URLs, name the documented limits of edge functions on CloudFront and Cloudflare, and judge what must not go through a CDN.

    A newspaper does not drive every copy from the printing plant to every reader. It sends bundles to regional depots, depots supply local kiosks, and the plant is only asked when a kiosk and its depot both sold out. If the paper prints a correction, copies already in kiosks stay until someone pulls them.

    A CDN is that arrangement for HTTP. CDN and Object Storage explains the basics (edge, origin, TTL, versioned URLs) and Cache Layers places the CDN among the other caches. This lesson goes deeper on tiers, cache keys, purge and edge compute. The proxy rules underneath are in Forward and Reverse Proxies. For the browser side see HTTP Caching for the Front End and Edge Rendering; a front-end lesson on CDN and edge caching is planned, not published.

    Verified against RFC 9111, 9213 and 5861, the Amazon CloudFront developer guide, the Cloudflare cache and Workers documentation and the Fastly guides on shielding and surrogate keys, all read on 2026-11-08. Vendor limits and defaults change, so read the current page before you rely on a number.

    The core idea

    A CDN is a shared cache with a network around it. It answers from a location near the viewer, and when it cannot, it asks the next layer inward and finally your origin. Its job is to turn many viewer requests into few origin requests. Three things decide whether it does:

    1. The cache key: what counts as "the same request".
    2. Freshness: how long a copy may be served.
    3. Control: how you remove a copy early.

    Vendors default differently on all three, so a design that works on one CDN can misbehave on another. The illustrations are a minimal example (origin requests with and without tiers), a realistic example (ShopStream's weekly offers) and a failure example (a purge that empties the cache).

    The path of a request: edge, regional tier, shield

    CloudFront's guide describes the first step: "DNS routes the request to the CloudFront POP (edge location) that can best serve the request, typically the nearest CloudFront POP in terms of latency." On a miss the POP asks a regional edge cache, a larger cache between POPs and the origin, because "individual POPs might remove those objects to make room for more popular content". Only when that misses does the request go to the origin, and then the object is stored at both levels.

    Other vendors name the same idea differently:

    Idea CloudFront Cloudflare Fastly
    Closest cache to the viewer POP, edge location Lower-tier data center Edge POP
    Middle layer Regional edge cache Upper tier (Tiered Cache) not described on the page read
    Single designated layer in front of the origin Origin Shield (extra cost) Smart Tiered Cache picks "the single closest upper tier" to the origin Shielding: a POP "physically close to your origin"

    Each layer raises the hit chance and adds a hop on a miss. Fastly says a request that reaches the origin through a shield "will be counted as two misses, one at the edge, and one at the shield". CloudFront says Origin Shield "may not be a good fit" for dynamic content, content with low cacheability or infrequently requested content, and bills for requests that reach it.

    Viewer requests funnel through tiers before reaching the originViewers in Region A to Edge POPs; Viewers in Region B to Edge POPs; Edge POPs to Shield tier (miss); Edge POPs to Shield tier (miss); Shield tier to Origin (miss)CDN NETWORKViewers in Region AViewers inRegion AViewers in Region BViewers inRegion BEdge POPs: many, small cachesEdge POPsmany, smallcachesEdge POPs: many, small cachesEdge POPsmany, smallcachesShield tier: merges missesShield tiermerges missesOrigin: your servers or storageOriginyour servers orstoragemissmissmiss

    Figure 1. A tiered CDN. How to read this: each arrow labelled "miss" is a layer that did not have a fresh copy. The middle tier merges misses from many edges, so the origin sees one request where many edges missed.

    sequenceDiagram accTitle: A cold miss and a warm hit through a shield accDescr: The first viewer misses at the edge and at the shield, so the origin serves the object and both layers store it. A viewer at another edge misses locally but hits at the shield, so the origin is not asked again. participant V1 as Viewer 1 participant E1 as Edge A participant V2 as Viewer 2 participant E2 as Edge B participant S as Shield participant O as Origin V1->>E1: GET object E1->>S: miss S->>O: miss O-->>S: object with headers S-->>E1: object, shield stores it E1-->>V1: object, edge A stores it V2->>E2: GET object E2->>S: miss S-->>E2: hit at the shield E2-->>V2: object, the origin was not asked

    Figure 2. Two edges, one origin request. How to read this: edge B missed but the shield held the object, so only the first miss reached the origin.

    Minimal example. One new product image is requested at 50 edge locations in the first minute (an illustrative number). With no middle tier, each edge misses once, so the origin can see 50 requests. With one shield tier that holds the object after the first fetch, the origin can see as few as one. CloudFront's wording is that requests for content not in the shield "are consolidated with other requests for the same object, resulting in as few as one request going to your origin."

    CloudFront adds exceptions: PUT, POST, PATCH, OPTIONS and DELETE go straight from the POP to the origin, and "dynamic requests, as determined at request time, do not flow through regional edge caches". It also counts GET and HEAD with a TTL under 3,600 seconds as dynamic for Origin Shield billing, so a short TTL reduces the benefit of the middle layers.

    The cache key

    A cache key is the information a cache uses to choose a response (RFC 9111). Two requests with the same key share one stored object; different keys store separate copies. The defaults differ:

    CloudFront default key Cloudflare default key
    Includes Distribution domain and URL path Full URL (scheme, host, URI with query string), the Origin header, and some override and forwarded-host headers
    Query string, cookies, other headers Not included unless a cache policy adds them Query string is in the key; cookies and other headers are not, unless customised

    On CloudFront, ?ref=a and ?ref=b hit the same object by default, which is wrong if the query selects content. On Cloudflare they are two objects, which wastes cache if the parameter is only tracking. The CloudFront guide gives the rule: "When a value in the viewer request determines the response that your origin returns, you should include that value in the cache key. But if you include a value in the cache key that doesn't affect the response that your origin returns, you might end up caching duplicate objects."

    Duplicates multiply fast. Accept-Language values such as en-US,en, en,en-US and en-US all mean English but would be separate keys, so the guide suggests putting language in the URL path. User-Agent "can have thousands of unique variations", and user-specific cookies are poor key candidates. When the origin needs a value for logging only, CloudFront's answer is an origin request policy: forward it without keying on it. Cloudflare offers key controls for query string, headers, cookies, host and device type; per its page, ignoring or sorting the query string is on all plans and the rest are Enterprise.

    Vary tells a cache which request headers change the response (RFC 9111 section 4.1); a CDN may honour it or replace it with its own key rules, so check the vendor page. Never let a personalised response into a shared key, as Cache Layers shows.

    Freshness: your headers and the vendor's rules

    Start from RFC 9111: s-maxage overrides max-age for shared caches, private and no-store keep a response out of shared caches, and a shared cache must not reuse a response to a request with Authorization unless allowed. Vendors add their own layer:

    • Targeted fields. RFC 9213 defines CDN-Cache-Control so an origin can "control the behavior of CDN caches" separately. A cache that supports it "MUST select the first (in target-list order) field with a valid, non-empty value" and then ignore Cache-Control and Expires for that response. A cache that does not support it ignores it.
    • Default behaviour. Cloudflare's page says it "does not cache HTML or JSON by default" and decides "based on file extension and not by MIME type". It skips responses with private, no-store, no-cache, max-age=0 or Set-Cookie. With no headers its default edge TTL is 120 minutes for 200, 206 and 301.
    • Serving stale. RFC 5861 (Informational) defines stale-while-revalidate and stale-if-error: for example max-age=600, stale-while-revalidate=30 lets a cache serve the response for 30 seconds after it turns stale while it refreshes. RFC 9111 does not define them, so check whether your CDN supports them.

    A POP may also drop a rarely requested object before its TTL: expiry is a limit, not a promise of residency.

    Purge: removing what you cached

    Method Example Cost or limit as documented Best for
    Versioned URLs /app.3f9c2e.js None; CloudFront recommends it first Assets you build
    By URL or path /products/42 CloudFront: first 1,000 paths a month free, a wildcard path counts as one A few known objects
    By tag or surrogate key #product:42 or Surrogate-Key: product-42 CloudFront tag invalidation shares the 1,000-path allowance; Fastly: tokens up to 1 KB, header up to 16 KB Groups of pages for one product
    By prefix or host /offers/* Cloudflare lists both A section of a site
    Everything all objects Cloudflare lists it on every plan Emergencies only

    CloudFront recommends versioning for frequent updates because "versioning enables you to control which file a request returns even when the user has a version cached either locally or behind a corporate caching proxy", while after an invalidation "the user might continue to see the old version until it expires from those caches". Purge reaches the CDN, not the browser. Cloudflare's page adds that an HTTP 200 from the purge API means the request was received, and that you verify by requesting the asset again and checking that CF-Cache-Status is no longer HIT. Publish, purge, then check, and think about what the origin sees when a purge empties many objects at once.

    flowchart TD accTitle: Choosing between versioned URLs and purge accDescr: If you control the URL and the content is immutable, use a versioned URL with a long TTL. If the URL is fixed but groups of pages change together, tag the responses and purge by tag. If a single fixed URL changes rarely, purge by URL. Avoid purge-everything except in emergencies. S["Content changed"] --> Q1{"Does the URL change with the content?"} Q1 -->|"yes"| V["Versioned URL, long TTL, nothing to purge"] Q1 -->|"no"| Q2{"Do many pages change together?"} Q2 -->|"yes"| T["Tag responses, purge by tag"] Q2 -->|"no"| U["Purge the single URL, keep a short TTL as a safety net"]

    Figure 3. Prefer not to purge. How to read this: the first question avoids purge altogether. Purge-everything is not on the chart because it is the failure example, not a design.

    Compute at the edge: what is documented

    Edge functions run your code near the viewer, usually to rewrite a URL, normalise a cache key, add a header or check a token. They are not a general server. The limits below are quoted from each vendor's page at the time of writing and change, so confirm before designing around them.

    CloudFront Functions Lambda@Edge Cloudflare Workers
    Duration or CPU Submillisecond Up to 30 seconds per event CPU time 10 ms per request on Free; Paid default 30 seconds, up to 5 minutes
    Memory 2 MB 128 MB (viewer events), up to 10 GB (origin events) 128 MB per isolate
    Code size 10 KB 50 MB 64 MiB uncompressed
    Network access, request body No Yes Subrequests: 50 (Free), 10,000 (Paid)

    CloudFront positions Functions for cache key normalisation, headers, redirects and token validation, and Lambda@Edge for longer work or work that needs network or the request body. Keep the edge for small, stateless decisions.

    Trade-offs

    Choice Gain Cost Watch for
    A CDN in front of the origin Latency, origin offload, spike absorption Another vendor, purge delay, staleness Personalised responses in a shared key
    A minimal cache key High hit ratio Risk of serving the wrong variant Query strings that change content

    Three illustrations

    A minimal example is the 50-edge arithmetic above. A realistic example is ShopStream's weekly offers page, in the scenario below. A failure or edge case is the purge that empties the cache: if the hit ratio is 90% (illustrative) at ShopStream's shared browsing peak of about 2,300 requests per second, the origin serves about 230 requests per second. After a purge-everything the hit ratio drops towards zero and the origin can see up to 2,300 per second, ten times its usual load, until the cache refills: the stampede from Cache Stampede and Hot Keys, at CDN scale.

    The other side of this topic. The browser sees only a Cache-Control it must obey and a version it cannot purge; see HTTP Caching for the Front End.

    In practice: ShopStream's weekly offers page behind a CDN

    NOTE

    This is a realistic scenario, not a case history. It is a composite on the course's running example, ShopStream. It describes no real company, and every number other than the shared ShopStream figures is an illustrative assumption.

    The situation

    Every Monday ShopStream publishes weekly offers: a page and a JSON list, the same for every shopper in a city, linked from emails with tracking parameters such as ?campaign=.... The CDN's hit ratio on this page is poor, and the origin has felt each email blast.

    Starting architecture

    Offers traffic: tracking parameters split the cache keyShoppers to CDN edge (?campaign=x123); CDN edge to Edge proxy (miss per variant); Edge proxy to Offers serviceShoppers: from emails and the appShoppersfrom emails andthe appCDN edge: key includes full queryCDN edgekey includes fullqueryEdge proxyEdge proxyOffers serviceOffers service?campaign=x123miss per variant

    Figure 4. One page, many cache entries. How to read this: each distinct tracking value is a separate key, so the page is stored over and over and each new campaign starts cold.

    Options considered

    Option Behaviour Risk
    A. Leave it, raise the TTL More objects live longer Duplicates remain; a longer TTL delays corrections
    B. Drop tracking parameters from the cache key One object per city A parameter that does change content must not be dropped
    C. B plus a shield tier and s-maxage with tag purge Fewer origin requests; a price correction is one purge by tag Extra cost, header discipline

    The decision

    Option C. The key becomes host, path and the city parameter only; campaign and similar parameters are ignored by the cache key but still forwarded to the origin for analytics (on CloudFront that is an origin request policy, on Cloudflare a custom cache key). Responses carry Cache-Control: public, max-age=0, s-maxage=300 and a tag per offer set. Record: context tracking parameters duplicate the page; decision key on city only, tag by offer set, shield on; consequences a purge-by-tag step in the publishing pipeline; revisit when a new parameter starts changing content.

    Rollout and verification

    1. Measure hit ratio and origin requests per second on the offers path for a week.
    2. Change the key for one path and compare; check that two cities still get different bodies.
    3. Test a correction: change an offer, purge its tag, and request the page until it shows the change.

    The 2 a.m. failure

    The page reads: origin CPU at ten times normal after an engineer ran "purge everything" to fix one wrong price. Checks: the purge log, the origin request rate, the hit ratio. Mitigation: rate-limit cold paths, enable the shield, warm the top pages from a list. Follow-up: purge by tag, and alert when the origin rate jumps.

    Architect's notes

    • Write the cache key down. For each path, list the request values that change the response, and key on exactly those.
    • Measure origin requests, not just the hit ratio.
    • Keep the edge small; business rules belong at the origin.

    Common mistakes

    • Purging everything, then meeting the stampede.
    • Forgetting that purge does not reach browsers.

    Key takeaways

    • A CDN is a tiered shared cache: edge, regional or shield tier, then origin.
    • The cache key decides what is shared; defaults differ by vendor, so state it explicitly.
    • Freshness comes from your headers plus the vendor's rules; CDN-Cache-Control targets CDN caches only.
    • Prefer versioned URLs; use tag or surrogate-key purge for groups; avoid purge-everything.
    • A cold CDN sends its full load to the origin, so plan for it.

    Practice exercises

    Exercise 1 (20 min). For a catalogue API with a city query parameter that changes content and a utm_source that does not, write the cache key for CloudFront (default plus additions) and for Cloudflare (default minus exclusions).

    Exercise 2 (30 min). At a 95% hit ratio and 2,300 requests per second, what does the origin see, and what after a purge drops the ratio to 20%?

    Exercise 3 (2 hours). Design a publishing step that updates a product and purges by tag, then lists three ways the step could fail and how you would notice each.

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments