CDN Integration — Low-Level Design
A CDN (Content Delivery Network) caches static assets and API responses at edge nodes close to users, reducing latency and origin server load. Integrating a CDN correctly requires designing cache keys, cache headers, invalidation, and edge behavior. This design is asked at Netflix, Cloudflare, and any globally distributed platform.
What to Put Behind a CDN
Excellent CDN candidates (high cache hit ratio):
- Static assets: JS, CSS, fonts, images — cache for 1 year (immutable)
- Video/audio segments (.ts, .m4s, .mp3) — cache for 1 year
- API responses that are the same for all users (public catalog, homepage data)
- Resized image variants (once generated, never change)
Poor CDN candidates:
- User-specific responses (cart, profile, notifications) — must not be shared
- Responses with Set-Cookie or Authorization headers — CDN caches per-user key
- Highly dynamic data (real-time prices, stock levels) — TTL must be very short
- Write operations (POST, PUT, DELETE) — should bypass CDN to origin
Cache-Control Headers
from flask import make_response
# Immutable assets (content-hashed filenames):
# /static/app.abc123.js — hash changes when content changes
@app.route('/static/')
def static_file(filename):
response = make_response(send_file(filename))
response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
# immutable: browser never revalidates (no conditional request)
return response
# API response: cache at CDN for 60 seconds, client for 10 seconds
@app.route('/api/public/products')
def public_products():
products = get_public_products()
response = make_response(jsonify(products))
response.headers['Cache-Control'] = 's-maxage=60, max-age=10, stale-while-revalidate=30'
# s-maxage: CDN TTL; max-age: browser TTL; stale-while-revalidate: serve stale while refreshing
return response
# Never cache:
@app.route('/api/user/cart')
def user_cart():
response = make_response(jsonify(get_cart()))
response.headers['Cache-Control'] = 'private, no-store'
return response
Cache Keys and Vary Headers
-- Default CDN cache key: URL only (scheme + host + path + query)
-- Problems arise when the same URL returns different content based on headers
-- Accept-Encoding: client supports gzip → CDN must cache compressed and uncompressed separately
Vary: Accept-Encoding ← tells CDN to include Accept-Encoding in cache key
-- Accept: image/webp → CDN must cache WebP and JPEG variants separately
Vary: Accept ← for content negotiation
-- Cookie or Authorization → user-specific, must not be shared
-- WRONG: setting Vary: Cookie on public endpoints — every user gets their own cache entry
-- RIGHT: strip auth cookies before caching, or set Cache-Control: private
-- CloudFront example: custom cache policy
Cache key:
URL path + query string (selected params only)
NOT: cookies, Authorization header (these cause per-user cache fragmentation)
Cache Invalidation
-- Option 1: Versioned URLs (best for static assets)
-- Bundle hashes filenames: app.abc123.js → app.def456.js on next deploy
-- No invalidation needed; old URL serves old file, new URL serves new file
-- Option 2: CDN API invalidation (for content changes)
def invalidate_cdn_cache(paths):
cloudfront.create_invalidation(
DistributionId='E1234567890ABC',
InvalidationBatch={
'Paths': {'Quantity': len(paths), 'Items': paths},
'CallerReference': str(uuid.uuid4()),
}
)
# Examples:
invalidate_cdn_cache(['/api/public/products']) # Specific endpoint
invalidate_cdn_cache(['/images/product-123/*']) # Image variants for a product
# CloudFront: $0.005 per 1,000 invalidation paths
# Use wildcards to reduce cost: /images/product-123/* counts as 1 path
-- Option 3: Short TTL + stale-while-revalidate (for frequently changing data)
Cache-Control: s-maxage=30, stale-while-revalidate=60
-- CDN serves stale (up to 60s old) while fetching fresh in background
-- No explicit invalidation needed for data that changes frequently
Edge Functions (Compute at CDN)
// CloudFront Functions / Cloudflare Workers: run JS at every edge node
// Example: A/B test routing at the edge (no origin hit)
export default {
async fetch(request) {
const url = new URL(request.url);
const userId = getCookie(request, 'user_id');
const bucket = parseInt(userId) % 2; // 50/50 split
if (bucket === 0) {
url.pathname = url.pathname.replace('/checkout', '/checkout-v2');
}
return fetch(url.toString(), request);
}
};
// Example: Add security headers at edge
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('Content-Security-Policy', "default-src 'self'");
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
Key Interview Points
- Content-addressed filenames eliminate cache invalidation for static assets: app.abc123.js will never change. Set max-age=31536000, immutable and never think about invalidation for that file. The build process handles rotation by generating new hashes.
- Vary: Cookie destroys CDN efficiency: If you set Vary: Cookie on a public endpoint, every user gets their own cache entry. The cache hit rate drops to near zero. Strip or ignore auth cookies before caching public responses.
- stale-while-revalidate enables smooth cache refresh: Without it, when TTL expires, the first request waits for origin. With stale-while-revalidate, the stale response is served immediately while the CDN refreshes in background. Users see no latency spike at TTL expiry.
- CDN logs enable origin request reduction metrics: The key CDN metric is cache hit ratio (target: >95% for static, >70% for cacheable API responses). Monitor hit ratio per path; a low-hit path signals a misconfigured Cache-Control header.
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”What is the difference between CDN push and CDN pull models?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Pull model: CDN fetches content from your origin server on the first request. User requests /image/foo.jpg, CDN misses, fetches from origin, caches, serves. All subsequent requests are served from cache until TTL expires. Zero setup per asset — the CDN caches whatever is requested. Best for unpredictable access patterns (user-uploaded content, long-tail assets). Push model: you proactively upload content to CDN nodes before it is requested. Use for predictable, high-traffic content (JS bundles, CSS, app icons released at deploy time). Ensures zero-miss latency for known assets. Hybrid: push critical static assets at deploy time; pull everything else on demand. Most production systems use pull for dynamic/user content and push for static build artifacts.”}},{“@type”:”Question”,”name”:”How does Cache-Control header design determine CDN behavior?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Cache-Control: public, max-age=31536000, immutable — for versioned static assets (hashed filenames like app.a3f9bc.js). CDN caches for 1 year; browser caches indefinitely. The immutable directive tells the browser not to revalidate even after max-age expires. Use this for content-addressed assets where the URL changes when the content changes. Cache-Control: public, max-age=3600, stale-while-revalidate=86400 — for semi-static content (product pages, blog posts). CDN serves stale content for up to 24 hours while revalidating asynchronously. Cache-Control: private, no-store — for user-specific responses (dashboard, account page). CDN will not cache; only the user’s browser handles caching. Getting these wrong causes either stale content (too long TTL) or no CDN benefit (too short TTL or private).”}},{“@type”:”Question”,”name”:”How do you invalidate CDN cache after a deployment without waiting for TTL expiry?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Three strategies: (1) Versioned URLs — rename the asset on every deploy (e.g., app.abc123.js). Old URLs expire naturally; new URLs are fresh from origin. Never need to purge. Best practice for static assets. (2) CDN purge API — call the CDN’s purge endpoint (e.g., Cloudflare PATCH /zones/{id}/purge_cache) immediately after deploy. Purges propagate to all edge nodes within 30 seconds. Use for content where the URL cannot be versioned (e.g., /og-image.png). (3) Surrogate keys (cache tags) — tag CDN responses with logical keys (e.g., product:123). Purge all cached responses for that tag in one API call. Fastly and Cloudflare support this. Use for content with complex invalidation relationships.”}},{“@type”:”Question”,”name”:”How do you handle CDN caching for authenticated or personalized content?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Never cache personalized content at the CDN. The Vary: Cookie header tells the CDN to cache separate copies per cookie value — with millions of users, this is millions of CDN cache entries that each see one request. Instead, architect for CDN-cacheable public content + client-side personalization: serve the same public HTML/JSON from CDN; fetch personalized data (user name, cart count, recommendations) via a separate authenticated API call from the client. The page shell is CDN-cached; the personalization layer is fetched fresh. For edge personalization (A/B tests, geolocation-based content), use CDN edge functions (Cloudflare Workers, Lambda@Edge) that modify the cached response at the edge without hitting origin.”}},{“@type”:”Question”,”name”:”How do you measure CDN effectiveness and what metrics matter?”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Key metrics: (1) Cache hit ratio = CDN_hits / (CDN_hits + origin_requests). Target >95% for static assets, >80% for semi-static content. Low hit ratio means TTLs are too short, cache keys are too granular (Vary: headers causing too many variants), or content is too dynamic. (2) Origin offload = 1 – (origin_requests / total_requests). Tracks how much traffic the CDN is absorbing. (3) P95/P99 latency by region — CDN should deliver assets in <50ms from edge nodes, vs 200-500ms from origin. (4) Bandwidth cost — CDN bandwidth is much cheaper than origin egress. Monitor these in the CDN dashboard (Cloudflare Analytics, Fastly Real-Time Analytics). Alert if cache hit ratio drops by >5% after a deployment — indicates caching misconfiguration.”}}]}
CDN integration and global content delivery design is discussed in Netflix system design interview questions.
CDN integration and CloudFront delivery design is covered in Amazon system design interview preparation.
CDN integration and media delivery optimization is discussed in Snap system design interview guide.