When you deploy content to a CDN with 300+ Points of Presence, you are not pushing to a single coherent store. You are replicating state across an eventually consistent system where each PoP operates as an autonomous cache. An update at your origin in Virginia does not instantly appear in Singapore, São Paulo, or Frankfurt. For a window of time — seconds to minutes depending on your strategy — different users will see different versions of the same URL.
This is not a bug. It is the fundamental trade-off that makes CDNs fast. Strong consistency would require synchronous coordination between PoPs on every read, destroying the latency benefits you adopted a CDN for in the first place. The engineering challenge is not to eliminate eventual consistency, but to manage it deliberately. If you are new to how edge caching works, start with our what is a CDN primer, then dive into the patterns below.
Why Strong Consistency Fails at the Edge
In a traditional database, strong consistency means that after a write completes, all subsequent reads return the updated value. Achieving this across a global CDN would require a consensus protocol like Paxos or Raft between PoPs before acknowledging a write. With round-trip times of 100-250ms between continents and hundreds of nodes, that coordination would add seconds of latency to every purge and origin fetch.
CDNs therefore choose availability and partition tolerance over strong consistency, as described by the CAP theorem. Each PoP serves from its local cache independently, even if it is disconnected from the origin or from other PoPs. The system guarantees that, in the absence of new writes, all PoPs will eventually converge to the same state — hence eventual consistency.
Three factors determine your consistency window:
- TTL (Time-To-Live): The maximum time a PoP will serve a cached object without revalidating. Longer TTLs increase cache hit ratio but widen the inconsistency window.
- Purge propagation delay: The time for an invalidation message to traverse the CDN's control plane to all edges. Modern CDNs achieve 150ms-2s for global purge.
- Origin shielding and tiered caching: Intermediate parent caches can absorb purges before they reach leaf PoPs, adding a hop but reducing origin load.
Treat every cached object as having a coherence SLA. Define how stale it is allowed to be, then select a pattern that enforces that SLA with minimal operational cost. Not all content needs the same consistency.
Consistency Models You Can Choose
Not all eventual consistency is equal. You can tune the model per content type using HTTP cache directives and CDN configuration. Understanding these models helps you match business requirements to technical implementation, as detailed in our CDN fundamentals guide.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
1. TTL-Based Expiration (Passive Coherence)
The simplest model. You set Cache-Control: max-age=60, s-maxage=300 and each PoP expires the object independently. No control plane messages are needed. Consistency is bounded by the TTL value. This is ideal for content where a few minutes of staleness is harmless, such as blog posts or product descriptions.
2. Active Invalidation (Purge)
You explicitly tell the CDN to drop an object via API. The CDN broadcasts the invalidation to all PoPs. This provides much tighter coherence but introduces control plane dependency and cost. Most providers charge or rate-limit purges, and excessive purging negates caching benefits.
3. Versioned / Fingerprinted URLs (Immutable Coherence)
Instead of mutating /app.css, you deploy /app.a3f9c1.css. The URL itself becomes the version. Old and new versions coexist without conflict, and coherence is perfect because there is no mutation. This is the gold standard for static assets and should be your default for build artifacts.
4. Stale-While-Revalidate (SWR)
Defined in RFC 5861, SWR allows a PoP to serve stale content while revalidating in the background: Cache-Control: max-age=60, stale-while-revalidate=30. Users never wait for origin, but may see content up to 90 seconds old. It smooths latency spikes during revalidation.
Five Patterns for Managing Coherence
The right pattern depends on content mutability, traffic volume, and tolerance for staleness. Most production architectures combine several.
| Pattern | Consistency | Best For | Trade-off |
|---|---|---|---|
| Short TTL max-age 30-60s |
Bounded ~60s | News, pricing, inventory | Lower hit ratio, higher origin load |
| Surrogate Keys Tag-based purge |
Strong ~1s | CMS pages, product catalogs | Requires origin tagging logic |
| Versioned URLs /asset.[hash].js |
Perfect | JS, CSS, images, fonts | Build pipeline complexity |
| Stale-While-Revalidate SWR 30-300s |
Weak ~90s | APIs, SSR pages | Clients may see stale data |
| Global Purge All /* invalidation |
Eventual 2-15s | Emergency rollback only | Thundering herd, cache cold start |
For high-cardinality content like e-commerce catalogs, surrogate keys are the most scalable active pattern. Instead of purging by URL, you tag responses with keys like product-123 or category-shoes and purge by tag. A single API call invalidates thousands of URLs across all PoPs without enumerating them. This is far more efficient than URL-based purging and integrates cleanly with your CDN SSL/TLS configuration and edge logic.
Invalidation vs. Expiration: Implementation
Expiration is declarative and origin-driven; invalidation is imperative and control-plane-driven. Prefer expiration wherever possible. It is cheaper, more reliable, and does not depend on the CDN's purge queue.
Expiration Headers (Preferred)
Set tiered TTLs to balance hit ratio and freshness. Use s-maxage for shared caches and max-age for browsers.
Cache-Control: public, s-maxage=60, max-age=30, stale-while-revalidate=30, stale-if-error=86400
Surrogate-Key: product-4821 category-footwear brand-nike
CDN-Cache-Control: max-age=60
Vary: Accept-Encoding, Accept-Language
This tells PoPs to cache for 60 seconds, serve stale for 30 seconds while revalidating, and keep stale for 24 hours if origin is unreachable. Browsers cache for 30 seconds. The surrogate keys enable targeted purging later.
Targeted Invalidation (When You Must)
When price or inventory changes, purge by surrogate key rather than waiting for TTL expiry:
curl -X POST \
-H "Fastly-Key: $FASTLY_API_TOKEN" \
-H "Accept: application/json" \
https://api.fastly.com/service/SERVICE_ID/purge/product-4821
# Cloudflare equivalent with cache-tags
curl -X POST \
-H "Authorization: Bearer $CF_TOKEN" \
-d '{"tags": ["product-4821"]}' \
https://api.cloudflare.com/client/v4/zones/ZONE_ID/purge_cache
For atomic deployments, combine versioned URLs with a short-TTL HTML shell. The HTML at /index.html has s-maxage=30 and references /main.a3f9c1.js immutably cached for a year. Purging only the HTML is enough to roll forward, and rollback is instant by re-purging the previous HTML.
A wildcard purge (/*) evicts the entire cache and forces every PoP to refetch from origin simultaneously. On high-traffic sites this causes origin overload and latency spikes. Reserve it for security incidents. For load distribution during revalidation, use CDN load balancing algorithms with origin shielding.
Monitoring Coherence and Measuring Drift
You cannot manage what you do not measure. Instrument your edge to detect drift before users do.
- Age and X-Cache headers: Log
Age,X-Cache: HIT/MISS, andX-Cache-Hitsfrom multiple PoPs via synthetic checks. A sudden increase in Age variance indicates a stuck purge. - Version header: Emit
X-Content-Version: v2-20260908-1422from origin and assert it at the edge. Alert if more than one version is served for longer than your SLA. - Purge audit log: Track purge ID, tag, start time, and completion time per PoP. Most CDNs provide a purge status API — poll it and alert on incomplete propagation.
- Real User Monitoring (RUM): Compare
Datevs.Last-Modifiedin the wild. If users in different regions report different ETags for the same URL, your inconsistency window is wider than expected.
Set SLOs per content class: 5 seconds for pricing, 60 seconds for CMS, infinite for fingerprinted assets. Use tiered caching and CDN DDoS protection shielding to absorb revalidation storms — a shield PoP coalesces concurrent misses into a single origin request, protecting your origin during convergence.
Choosing Your Strategy
Start with this decision tree: Is the asset immutable? Use versioned URLs with immutable and a one-year TTL. Is it mutable but taggable? Use short TTL (30-60s) plus surrogate-key purge. Is it highly dynamic and personalized? Bypass cache or use private, no-store and cache at the application layer instead.
Eventual consistency is not a compromise to work around — it is the mechanism that lets your site serve 95%+ of requests from the edge at <50ms. By making coherence explicit through TTLs, surrogate keys, and versioning, you turn an unpredictable distributed system into a predictable one with bounded staleness and clear operational playbooks.
Frequently Asked Questions
With active purge, most tier-1 CDNs achieve global coherence in 150ms to 2 seconds. With TTL-only expiration, consistency depends on your max-age: a 60s TTL means up to 60s drift, while a 1-hour TTL means up to 1-hour drift across PoPs.
Use versioned URLs (fingerprinting) for static assets like JS, CSS, and images where immutability is possible. Use purge or surrogate-key invalidation for dynamic content like HTML, API responses, and personalized pages where versioning is impractical.
Stale-while-revalidate intentionally trades strict coherence for availability and latency. Clients may receive stale content for the SWR window (e.g., 30–60s) while the edge revalidates asynchronously. It's ideal for content where brief staleness is acceptable.
Combine soft purge with stale-while-revalidate, use request coalescing via origin shield, and implement tiered caching. This allows one PoP to revalidate while others serve stale, preventing thousands of edge nodes from hitting origin simultaneously.