CDN Integration Low-Level Design

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.

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.

Scroll to Top