How to Design a Content Delivery Network (CDN)
Building a Cloudflare/Akamai-Scale System — Edge Caching, DNS Routing, DDoS Protection, Edge Computing
1. Introduction & Why CDNs Matter
A Content Delivery Network (CDN) is a geographically distributed network of proxy servers and data centers that deliver web content to users based on their geographic proximity. CDNs have become the backbone of the modern internet — by some estimates, over 75% of all internet traffic traverses a CDN at some point. Companies like Cloudflare, Akamai, Fastly, and Amazon CloudFront operate networks spanning hundreds of data centers across every inhabited continent, collectively handling hundreds of petabytes of traffic per day.
The fundamental problem a CDN solves is simple: the speed of light is finite, and network hops introduce latency. A user in Tokyo requesting content hosted on a server in Virginia will experience significantly higher latency than if the same content were served from a data center in Tokyo. Moreover, the origin server has finite bandwidth and compute capacity. Without a CDN, every user request hits the origin, creating a bottleneck that limits scalability and increases the risk of cascading failures.
CDNs address this through three core mechanisms: caching (storing copies of content at edge locations close to users), routing optimization (using DNS and anycast to direct users to the nearest or optimal edge), and security hardening (absorbing DDoS attacks and filtering malicious traffic before it reaches the origin). Modern CDNs have evolved far beyond simple caching — they now offer edge computing platforms, real-time analytics, image optimization, bot management, zero-trust networking, and more.
The Scale of the Problem
Consider the numbers involved in operating a major CDN:
- Cloudflare operates in 310+ cities across 120+ countries, processing over 60 million HTTP requests per second at peak.
- Akamai's network carries 15-30% of all web traffic, with over 365,000 servers in 135 countries.
- A single flash event — like a major product launch or a viral social media post — can generate millions of requests per second for a single piece of content within minutes.
- DDoS attacks have grown to over 3.47 Tbps (as recorded in 2024), requiring CDNs to absorb traffic volumes that would overwhelm any single data center.
Designing a CDN is not merely a caching problem. It is a distributed systems challenge that spans networking (BGP, anycast, DNS), storage (hierarchical caching with strict consistency requirements), security (TLS termination, WAF, DDoS mitigation), compute (edge functions, real-time transformations), and operations (global fleet management, incident response across time zones). This guide walks through each of these dimensions in depth.
Historical Context
The first CDN, Akamai, was founded in 1998 at MIT. The original insight was that by distributing copies of popular content across a network of servers, you could dramatically reduce latency and improve reliability. Early CDNs focused exclusively on static content — images, stylesheets, and downloadable files. Over time, CDNs evolved to handle dynamic content (through techniques like edge-side includes and partial caching), video streaming (through adaptive bitrate protocols like HLS and DASH), and eventually general-purpose compute (through edge computing platforms like Cloudflare Workers and Fastly Compute).
The modern CDN has become an application delivery platform. It is the first line of defense against attacks, the closest compute surface to end users, and the critical infrastructure that makes the global web feel fast and responsive. Understanding how to design one from first principles is an essential skill for senior engineers working on distributed systems at scale.
2. Functional & Non-Functional Requirements
Functional Requirements
- Content Caching & Delivery: Cache and serve static assets (HTML, CSS, JS, images, video segments, fonts, API responses) from edge locations closest to end users.
- Origin Offload: Reduce origin server load by serving cached content from edge, with configurable TTLs and cache policies per content type.
- Cache Invalidation: Support both time-based expiration (TTL) and on-demand purging (instant purge across all edge nodes within seconds).
- TLS Termination: Terminate TLS at edge servers, support TLS 1.2/1.3, SNI-based certificate selection, and automatic certificate provisioning via ACME.
- DDoS Protection: Absorb volumetric, protocol, and application-layer DDoS attacks at the network edge without impacting legitimate traffic.
- WAF (Web Application Firewall): Inspect HTTP traffic for OWASP Top 10 vulnerabilities (SQLi, XSS, etc.) and apply custom rulesets.
- DNS Resolution: Provide authoritative DNS with low-latency resolution, supporting CNAME, A/AAAA records, and CNAME flattening.
- Edge Computing: Execute customer-defined logic (serverless functions) at edge locations for request/response transformation.
- Load Balancing: Distribute origin traffic across multiple origin servers with health checks, failover, and weighted routing.
- Real-Time Analytics: Provide customers with real-time dashboards for traffic, cache hit rates, latency, errors, and security events.
- Image Optimization: On-the-fly image resizing, format conversion (WebP, AVIF), and compression.
- Rate Limiting & Bot Management: Identify and throttle abusive traffic patterns based on configurable rules.
Non-Functional Requirements
| Requirement | Target | Rationale |
|---|---|---|
| Availability | 99.99%+ (five nines) | CDN downtime affects millions of websites; contractual SLAs demand near-perfect uptime |
| Latency (p99) | < 50ms for cached content | Edge servers must be within one network hop of most users |
| Cache Hit Ratio | > 95% for static assets | High hit ratio is the core value proposition of a CDN |
| Throughput | 100+ Tbps aggregate network capacity | Must handle flash crowds and absorb large DDoS attacks |
| Purge Latency | < 5 seconds globally | Customers need fast invalidation for security patches and content updates |
| TLS Handshake | < 5ms (with TLS 1.3 0-RTT) | TLS must not add perceptible latency to requests |
| Edge Locations | 300+ PoPs globally | Must be within 50ms of 95% of the internet-connected population |
| Security | PCI DSS, SOC 2 Type II, ISO 27001 | Enterprise customers require compliance certifications |
| Scalability | Linear horizontal scaling | Adding capacity should be proportional to traffic growth |
| Observability | Sub-second metric granularity | Customers and operations teams need real-time visibility |
Scope Boundaries
For this design, we focus on the HTTP/HTTPS content delivery use case. We do not design for DNS-only services (though we discuss DNS as it relates to CDN routing), raw TCP/UDP proxying, or dedicated DDoS-only scrubbing services. Our CDN is the full-stack product: edge caching, security, edge compute, and observability.
3. Capacity Estimation & Back-of-Envelope Math
Traffic Assumptions
Let us assume our CDN serves approximately 10 million websites with a combined peak traffic of 50 million requests per second. The average response size for cached content is 50 KB (considering a mix of HTML, CSS, JS, images, and small API responses).
50M req/s × 50 KB = 2.5 GB/s = 20 Gbps of sustained throughput per edge PoP.
With 300 PoPs: 300 × 20 Gbps = 6 Tbps aggregate egress.
Peak-to-average ratio of 3x: 6 Tbps × 3 = 18 Tbps peak capacity needed.
Storage Estimation
Assuming an average cacheable object size of 50 KB and we want to cache 100 million unique objects across the network (a reasonable estimate for popular web content):
| Component | Calculation | Result |
|---|---|---|
| Object count (global) | 100M unique objects | 100,000,000 |
| Average object size | 50 KB | 50 KB |
| Total cache (global) | 100M × 50 KB | 5 TB |
| Per PoP cache (300 PoPs) | 5 TB / 300 | ~17 GB per PoP (hot content) |
| Per server SSD | Typical edge server | 2-4 TB NVMe |
| Servers per PoP | Varies by location | 20-200 servers |
Cache Hit Ratio Impact
If our cache hit ratio is 95%, only 5% of requests reach the origin:
- Requests to origin: 50M × 0.05 = 2.5M requests/second
- Origin bandwidth: 2.5M × 50 KB = 125 GB/s = 1 Tbps
- If hit ratio drops to 90%, origin load doubles to 2 Tbps — potentially overwhelming origin infrastructure.
Connection Estimation
At 50M requests/second with an average request duration of 200ms:
- Concurrent connections per PoP: 50M / 300 PoPs × 0.2s = ~33,000 concurrent connections per PoP
- Per server (100 servers/PoP): 33,000 / 100 = 330 concurrent connections per server
- Each server handles approximately 500 requests/second — well within the capacity of a modern event-driven server using epoll/io_uring.
DDoS Absorption Capacity
A major DDoS attack might generate 3+ Tbps of traffic. Our CDN must absorb this without impacting legitimate traffic:
- Each PoP must absorb ~10 Gbps of attack traffic (distributed across 300 PoPs)
- With 300 PoPs each having 20 Gbps of capacity, aggregate absorption capacity is 6 Tbps
- Over-provisioning factor of 3x gives us 18 Tbps — sufficient for current generation attacks
4. Data Model & Metadata Schema
A CDN requires several data stores to manage configuration, caching metadata, analytics, and security policies. The data model is not a traditional relational schema — it is a distributed metadata layer that must be globally consistent and extremely fast to query at the edge.
Core Entities
C#
// Represents a customer's zone (domain) configuration
public class Zone
{
public string ZoneId { get; set; } // Unique identifier
public string DomainName { get; set; } // e.g., "example.com"
public string CustomerId { get; set; } // Owning customer
public ZoneStatus Status { get; set; } // Active, Suspended, Purging
public List<OriginServer> Origins { get; set; } // Origin server definitions
public CachePolicy CachePolicy { get; set; } // Default caching rules
public List<CacheRule> CacheRules { get; set; } // Path-specific rules
public SecurityPolicy SecurityPolicy { get; set; } // WAF & DDoS config
public TLSConfig TLSConfig { get; set; } // Certificate & TLS settings
public RateLimitConfig RateLimits { get; set; } // Rate limiting rules
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Dictionary<string, string> CustomMetadata { get; set; }
}
public class OriginServer
{
public string OriginId { get; set; }
public string Address { get; set; } // IP or hostname
public int Port { get; set; } // Default 443
public OriginProtocol Protocol { get; set; } // HTTP, HTTPS, HTTP2
public int Weight { get; set; } // For weighted load balancing
public HealthCheckConfig HealthCheck { get; set; }
public OriginShield OriginShield { get; set; } // Shield assignment
}
public class CacheRule
{
public string RuleId { get; set; }
public string Pattern { get; set; } // Glob pattern: "/assets/*"
public CacheAction Action { get; set; } // Cache, Bypass, ForceCache
public int TTL { get; set; } // Seconds
public int EdgeTTL { get; set; } // Edge cache TTL
public int BrowserTTL { get; set; } // Browser cache TTL
public bool StaleWhileRevalidate { get; set; }
public bool StaleIfError { get; set; }
public CacheKeyLayout CacheKey { get; set; } // How to construct cache key
}
public enum CacheAction
{
Cache, // Normal caching with TTL
Bypass, // Never cache
ForceCache, // Cache even if origin sends no-cache
Managed, // CDN-managed caching heuristics
IgnoreQueryOrder // Normalize query string
}
// Cache metadata stored per edge server
public class CacheEntry
{
public string CacheKey { get; set; } // Hash of URL + headers
public string ContentHash { get; set; } // SHA-256 of content
public long ContentLength { get; set; }
public string ContentType { get; set; }
public string ContentEncoding { get; set; } // gzip, br, identity
public DateTime CachedAt { get; set; }
public DateTime ExpiresAt { get; set; }
public CacheValidationTags ValidationTags { get; set; }
public List<string> VaryHeaders { get; set; }
public long HitCount { get; set; }
public long ByteSize { get; set; }
public CacheOrigin ShieldOrigin { get; set; }
public PurgeStatus PurgeStatus { get; set; }
}
public class PurgeRecord
{
public string PurgeId { get; set; }
public string ZoneId { get; set; }
public PurgeType Type { get; set; } // URL, Tag, Hostname, Prefix
public List<string> Targets { get; set; } // URLs or tags to purge
public PurgeStatus Status { get; set; } // Pending, InProgress, Complete
public DateTime IssuedAt { get; set; }
public DateTime CompletedAt { get; set; }
public int AffectedEdges { get; set; }
}
public enum PurgeType { URL, Tag, Hostname, Wildcard, Prefix }
public enum PurgeStatus { Pending, InProgress, Complete, Failed }
Analytics Data Model
C#
// Real-time request log entry (sampled at edge, aggregated centrally)
public class RequestLogEntry
{
public string RequestId { get; set; } // Unique ID
public string ZoneId { get; set; }
public string ClientIP { get; set; }
public string ClientCountry { get; set; }
public string ClientASN { get; set; }
public string EdgeLocation { get; set; } // Which PoP handled it
public string OriginIP { get; set; } // Which origin was hit
public string Method { get; set; } // GET, POST, etc.
public string URI { get; set; }
public int ResponseCode { get; set; }
public long ResponseBytes { get; set; }
public long CacheBytes { get; set; } // Bytes served from cache
public bool CacheHit { get; set; }
public bool CacheStatus { get; set; } // HIT, MISS, EXPIRED, REVALIDATED
public double ResponseTimeMs { get; set; }
public double OriginTimeMs { get; set; }
public string RayId { get; set; } // Distributed trace ID
public WAFAction WAFAction { get; set; }
public string ThreatScore { get; set; } // Bot/threat classification
public DateTime Timestamp { get; set; }
}
// Aggregated metrics rollup (1-minute windows)
public class MetricsRollup
{
public string ZoneId { get; set; }
public string EdgeLocation { get; set; }
public DateTime WindowStart { get; set; }
public long TotalRequests { get; set; }
public long CacheHits { get; set; }
public long CacheMisses { get; set; }
public long TotalBytesServed { get; set; }
public long TotalBytesFromOrigin { get; set; }
public double AvgResponseTimeMs { get; set; }
public double P95ResponseTimeMs { get; set; }
public double P99ResponseTimeMs { get; set; }
public long Error4xxCount { get; set; }
public long Error5xxCount { get; set; }
public long BlockedRequests { get; set; }
public Dictionary<int, long> StatusCodeDistribution { get; set; }
public Dictionary<string, long> TopURIs { get; set; }
}
Storage Layer
| Data Type | Storage System | Reasoning |
|---|---|---|
| Zone configuration | Distributed KV store (etcd/ZooKeeper) | Strong consistency needed for config propagation to all edges |
| Cache metadata | Local SSD + distributed invalidation bus | Must be extremely fast (local reads); invalidation via pub/sub |
| Purge commands | Global queue (Kafka) + distributed broadcast | Ordered, durable queue; broadcast to all edges via pub/sub |
| Request logs | Stream processing (Kafka -> ClickHouse/TimescaleDB) | High write throughput; analytical queries on time-series data |
| Security rules | Proprietary rule engine + distributed KV | Rules must be evaluated in microseconds at edge; version-controlled |
| Certificates | Encrypted KV store + ACME automation | TLS certificates must be stored securely; auto-renewal via ACME |
5. High-Level Architecture Overview
The CDN architecture follows a hierarchical model with three primary tiers: the edge layer (closest to end users), the mid-tier / shield layer (regional aggregation), and the origin layer (where customer servers reside). Control plane services run centrally and propagate configuration to all edge nodes via a high-speed distribution network.
Request Flow
When a user requests https://example.com/image.jpg:
- DNS Resolution: The user's recursive resolver queries our authoritative DNS. Anycast routing directs the query to the nearest edge PoP. GeoDNS returns the IP of the optimal edge server.
- Edge Processing: The request arrives at the edge PoP. The edge server performs TLS termination, evaluates WAF rules, checks rate limits, and looks up the cache.
- Cache HIT: If the content is cached and valid, it is served directly from the edge server's local SSD. The response includes appropriate
AgeandX-Cacheheaders. - Cache MISS: If not cached, the edge server forwards the request to the mid-tier shield for that region. The shield checks its larger cache.
- Shield MISS: If the shield also misses, it forwards to the origin server, caching the response on the way back through both shield and edge layers.
- Response Path: The response flows back: Origin → Shield (cache) → Edge (cache) → User. Each layer independently caches the response with appropriate TTLs.
Control Plane vs. Data Plane
The CDN has a clean separation between the data plane (request handling at edge) and the control plane (configuration, monitoring, certificate management):
Geographic Distribution
A modern CDN deploys edge servers in three tiers of location:
- Major metros (Tier 1): 50+ locations — New York, London, Tokyo, Singapore, Sydney, São Paulo, Mumbai, Frankfurt, etc. These PoPs have 50-200 servers each and handle the highest traffic volumes.
- Regional hubs (Tier 2): 100+ locations — Secondary cities like Dallas, Amsterdam, Seoul, Johannesburg, etc. These PoPs have 10-50 servers each.
- Emerging markets (Tier 3): 150+ locations — Smaller markets to reduce latency for underserved regions. These PoPs have 5-20 servers each and may be deployed on bare metal from local ISPs.
The network interconnecting these PoPs is a combination of dedicated fiber (for high-traffic paths), public internet (for lower-traffic paths), and partner networks. The key metric is that any edge PoP should be able to reach any origin or shield within 50ms of additional latency.
6. Edge Server Architecture
The edge server is the workhorse of the CDN. Every HTTP request passes through it, and it must handle TLS termination, caching, security inspection, and response delivery — all within microseconds. The architecture of the edge server is designed for maximum throughput and minimal latency.
Software Stack
Event-Driven Architecture
Edge servers use a single-threaded (or少量 threads) event-driven architecture built on io_uring or epoll. This eliminates thread contention, context switching overhead, and allows efficient handling of hundreds of thousands of concurrent connections with minimal memory overhead.
C#
// Simplified edge server request pipeline
public class EdgeRequestPipeline
{
private readonly TlsTerminator _tls;
private readonly WafEngine _waf;
private readonly RateLimiter _rateLimiter;
private readonly CacheEngine _cache;
private readonly OriginConnector _origin;
private readonly ResponseCompressor _compressor;
private readonly PurgeBus _purgeBus;
public async ValueTask<EdgeResponse> ProcessRequest(
EdgeRequest request, CancellationToken ct)
{
var startTime = ValueStopwatch.StartNew();
// Step 1: TLS Termination
var plainRequest = await _tls.DecryptAsync(request, ct);
// Step 2: WAF Inspection
var wafResult = _waf.Evaluate(plainRequest);
if (wafResult.Action == WafAction.Block)
{
return EdgeResponse BlockedResponse(wafResult);
}
// Step 3: Rate Limiting
if (!_rateLimiter.AllowRequest(plainRequest.ClientIP, plainRequest.ZoneId))
{
return EdgeResponse.RateLimited();
}
// Step 4: Cache Lookup
var cacheKey = BuildCacheKey(plainRequest);
var cacheEntry = _cache.Get(cacheKey);
if (cacheEntry != null && !cacheEntry.IsExpired)
{
// Cache HIT path
return await ServeFromCache(plainRequest, cacheEntry, ct);
}
// Step 5: Origin Fetch (Cache MISS)
var originResponse = await _origin.FetchAsync(plainRequest, ct);
// Step 6: Cache Store
if (IsCacheable(originResponse))
{
_cache.Store(cacheKey, originResponse);
}
// Step 7: Compress & Return
return await _compressor.CompressAsync(originResponse, ct);
}
private string BuildCacheKey(EdgeRequest request)
{
var builder = new ValueStringBuilder(stackalloc byte[512]);
builder.Append(request.Method);
builder.Append(':');
builder.Append(request.Uri);
if (request.HasRelevantVaryHeaders)
{
builder.Append('|');
foreach (var header in request.GetVaryHeaders())
{
builder.Append(header.Name);
builder.Append('=');
builder.Append(header.Value);
builder.Append(';');
}
}
return SHA256.HashToString(builder.AsSpan());
}
}
Hardware Specifications
| Component | Tier 1 PoP Server | Tier 3 PoP Server |
|---|---|---|
| CPU | Dual AMD EPYC 9654 (192 cores) | Single AMD EPYC 7763 (64 cores) |
| RAM | 512 GB DDR5 | 128 GB DDR4 |
| NVMe SSD | 4 × 3.84 TB Samsung PM1733 | 2 × 1.92 TB Samsung PM1733 |
| Network | Dual 100 GbE (bonded) | Dual 25 GbE (bonded) |
| Estimated req/s | 500,000+ HTTP req/s | 100,000 HTTP req/s |
| Estimated throughput | 40+ Gbps | 10+ Gbps |
Connection Pooling and Origin Reuse
The edge server maintains persistent HTTP/2 or HTTP/3 connection pools to origin servers and shield nodes. Connection establishment (TCP handshake + TLS handshake) is expensive — typically 1-3 round trips. By reusing connections, the edge server amortizes this cost across many requests. The connection pool is managed per-origin with configurable limits, health monitoring, and automatic failover to backup origins.
7. DNS Resolution & Anycast Routing
DNS is the critical first mile of CDN routing. When a user requests example.com, the CDN must direct them to the optimal edge server — typically the one topologically closest. This is achieved through a combination of Anycast BGP and GeoDNS.
Anycast
Anycast is a routing technique where multiple geographically distributed servers advertise the same IP address prefix via BGP. The Border Gateway Protocol (BGP) naturally routes packets to the topologically nearest node advertising that prefix. This means every edge PoP in a given region might advertise the same /24 (or /48 for IPv6) prefix, and routers automatically direct traffic to the nearest PoP.
Anycast has several advantages for CDNs:
- Natural DDoS absorption: Attack traffic is distributed across all PoPs rather than overwhelming a single data center.
- Zero-configuration routing: BGP handles failover automatically — if a PoP goes down, BGP withdraws the route and traffic shifts to the next-nearest PoP.
- Low latency: Users are automatically routed to the closest PoP without any client-side logic.
GeoDNS
While anycast handles coarse-grained routing (directing users to the nearest PoP), GeoDNS provides fine-grained control. When the CDN's authoritative DNS receives a query, it can examine the client's IP address (specifically, the EDNS Client Subnet option) and return a specific IP address for the most appropriate edge server.
C#
// DNS request handler with GeoDNS logic
public class CdnDnsResolver
{
private readonly IpGeolocationDatabase _geoDb;
private readonly LoadMonitor _loadMonitor;
private readonly EdgeNodeRegistry _edgeRegistry;
public DnsResponse ResolveQuery(DnsQuery query)
{
var clientIp = query.EDNSSubnet ?? query.SourceIP;
var clientGeo = _geoDb.Lookup(clientIp);
// Find nearest edge PoPs
var candidatePops = _edgeRegistry
.GetPoPsForRegion(clientGeo.Continent, clientGeo.Country)
.Where(pop => pop.Status == PoPStatus.Healthy)
.OrderBy(pop => GeoDistance(clientGeo, pop.Location))
.ThenBy(pop => pop.CurrentLoad)
.Take(3)
.ToList();
if (candidatePops.Count == 0)
{
// Fallback: return any healthy PoP
candidatePops = _edgeRegistry
.GetHealthyPoPs()
.OrderBy(_ => Random.Shared.Next())
.Take(3)
.ToList();
}
// Weighted selection based on current load
var selectedPop = WeightedSelect(candidatePops);
return new DnsResponse
{
Name = query.Name,
Type = DnsRecordType.A,
Address = selectedPop.AnycastIPv4,
TTL = 30, // Short TTL for dynamic routing
AdditionalRecords = candidatePops
.Select(p => new DnsRecord(p.AnycastIPv4, 30))
.ToList()
};
}
private double GeoDistance(GeoLocation a, GeoLocation b)
{
// Haversine formula for great-circle distance
const double R = 6371; // Earth's radius in km
var dLat = ToRadians(b.Lat - a.Lat);
var dLon = ToRadians(b.Lon - a.Lon);
var sinLat = Math.Sin(dLat / 2);
var sinLon = Math.Sin(dLon / 2);
var h = sinLat * sinLat +
Math.Cos(ToRadians(a.Lat)) *
Math.Cos(ToRadians(b.Lat)) *
sinLon * sinLon;
return R * 2 * Math.Atan2(Math.Sqrt(h), Math.Sqrt(1 - h));
}
}
CNAME Flattening
Many CDN customers already have DNS configurations that use CNAME records (e.g., www.example.com CNAME cdn.example.com). CNAME at the apex domain (the bare domain like example.com) is forbidden by DNS RFCs because it conflicts with other record types (SOA, NS). CDNs solve this with CNAME flattening — the CDN's DNS server resolves the CNAME chain internally and returns only A/AAAA records, effectively flattening the CNAME into direct IP addresses.
8. Cache Hierarchy — Edge, Mid-Tier, Origin
CDN caching is not a flat architecture — it is a carefully designed hierarchy that balances hit ratio, latency, bandwidth cost, and origin load. The three tiers (edge, mid-tier/shield, origin) each serve a specific purpose in this hierarchy.
Tier 1: Edge Cache
Each edge server maintains its own local cache on NVMe SSD, with a hot cache in DRAM. Edge caches are intentionally small (2-4 TB per server) to ensure fast lookups. They store the most recently and most frequently accessed content for their geographic region. The edge cache is the first check — if content is found here, it is served with minimal latency (sub-millisecond lookup, no upstream network hops).
- DRAM cache: ~10,000 objects (hot content). Lookup: ~100ns. Used for extremely popular content.
- SSD cache: ~500,000 objects (warm content). Lookup: ~10-50μs. The primary cache tier.
- Eviction policy: LRU with frequency-based promotion. Objects accessed more than 3 times are promoted to DRAM cache.
Tier 2: Mid-Tier (Shield) Cache
The mid-tier shield is a regional aggregation point with much larger cache capacity (100+ TB per shield). It sits between edge PoPs and origin servers. Its primary purpose is to reduce origin load — when content misses at one edge PoP, it might still be in the shield cache (having been fetched by a different edge PoP in the same region). This prevents N edge PoPs from sending N identical requests to origin for the same content.
Tier 3: Origin
The origin is the authoritative source of truth — the customer's actual server. Ideally, the origin should rarely see traffic, as the shield and edge layers should absorb the vast majority of requests. When origin is hit, the response is cached at both the shield and edge layers on the return path.
Cache Key Design
C#
public class CacheKeyBuilder
{
public string BuildKey(CacheKeyLayout layout, HttpRequest request)
{
var builder = new ValueStringBuilder(stackalloc byte[1024]);
// Include scheme, host, and path
builder.Append(request.Scheme);
builder.Append("://");
builder.Append(request.Host);
builder.Append(request.Path);
if (layout.IncludeQueryString)
{
if (layout.NormalizeQueryString)
{
// Sort query parameters for normalization
var sortedParams = request.Query
.OrderBy(p => p.Key, StringComparer.Ordinal)
.ThenBy(p => p.Value)
.ToList();
builder.Append('?');
for (int i = 0; i < sortedParams.Count; i++)
{
if (i > 0) builder.Append('&');
builder.Append(sortedParams[i].Key);
builder.Append('=');
builder.Append(sortedParams[i].Value);
}
}
else
{
builder.Append('?');
builder.Append(request.RawQueryString);
}
}
if (layout.IncludeHeaders?.Count > 0)
{
builder.Append('|');
foreach (var header in layout.IncludeHeaders)
{
if (request.Headers.TryGetValue(header, out var value))
{
builder.Append(header);
builder.Append('=');
builder.Append(value);
builder.Append(';');
}
}
}
if (layout.IncludeCookies?.Count > 0)
{
builder.Append('|');
foreach (var cookie in layout.IncludeCookies)
{
if (request.Cookies.TryGetValue(cookie, out var value))
{
builder.Append(cookie);
builder.Append('=');
builder.Append(value);
builder.Append(';');
}
}
}
// Return hex-encoded SHA-256 hash
return Convert.ToHexString(
SHA256.HashData(builder.AsSpan()));
}
}
Cache Fill Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Pull (Lazy) | Content is fetched from origin on first miss | Default for most content; simplest approach |
| Push (Preload) | Content is proactively pushed to edge before first request | Large video files, software releases, predicted viral content |
| Revalidation | Conditional requests (If-None-Match, If-Modified-Since) check freshness | Content that changes rarely but must be fresh |
| Stale-While-Revalidate | Serve stale content while asynchronously revalidating | Content where slightly stale data is acceptable |
| Stale-If-Error | Serve stale content if origin is unreachable | Origin outage resilience |
9. Cache Invalidation Strategies
Cache invalidation is one of the two hard problems in computer science (alongside naming). A CDN with stale content is worse than no CDN at all — security vulnerabilities, incorrect pricing, and outdated information can all result from serving stale cached content. The CDN must support multiple invalidation mechanisms with guaranteed delivery within seconds.
Invalidation Methods
- TTL-Based Expiration: The simplest and most common method. Each cached object has a Time-To-Live (TTL) set by origin headers (
Cache-Control: max-age=3600) or CDN configuration rules. When TTL expires, the object is considered stale and revalidated on next access. - Instant URL Purge: Customer requests invalidation of specific URLs. The CDN broadcasts a purge command to all edge PoPs, which remove the corresponding cache entries. Must complete globally within 5 seconds.
- Tag-Based Purge: Content is tagged at cache time with logical tags (e.g.,
product:12345). Purging a tag invalidates all content associated with that tag, regardless of URL. More flexible than URL purging. - Wildcard/Prefix Purge: Invalidates all content matching a URL pattern (e.g.,
/images/products/*). Useful for bulk invalidation. - Immediate (Zero-TTL): Content is never cached or revalidated on every request. Used for truly dynamic content that must always reflect the latest state.
Purge Propagation Architecture
C#
// Purge propagation system
public class PurgeOrchestrator
{
private readonly KafkaProducer<PurgeCommand> _kafkaProducer;
private readonly PurgeIndex _localIndex;
public async Task<PurgeResult> ExecutePurge(PurgeRequest request)
{
var purgeRecord = new PurgeRecord
{
PurgeId = Guid.NewGuid().ToString(),
ZoneId = request.ZoneId,
Type = request.PurgeType,
Targets = request.Targets,
IssuedAt = DateTime.UtcNow,
Status = PurgeStatus.Pending
};
// Step 1: Publish purge command to global Kafka topic
var command = new PurgeCommand
{
PurgeId = purgeRecord.PurgeId,
ZoneId = request.ZoneId,
Type = request.PurgeType,
Targets = request.Targets,
IssuedAt = purgeRecord.IssuedAt
};
await _kafkaProducer.ProduceAsync(
"cdn-purge-commands", command);
// Step 2: Wait for acknowledgment from all edges
var ackTimeout = TimeSpan.FromSeconds(5);
var ackCount = await WaitForEdgeAcks(
purgeRecord.PurgeId,
expectedEdges: GetEdgeCount(),
timeout: ackTimeout);
purgeRecord.Status = PurgeStatus.Complete;
purgeRecord.CompletedAt = DateTime.UtcNow;
purgeRecord.AffectedEdges = ackCount;
return new PurgeResult
{
PurgeId = purgeRecord.PurgeId,
Status = PurgeStatus.Complete,
EdgesAffected = ackCount,
DurationMs = (purgeRecord.CompletedAt - purgeRecord.IssuedAt).TotalMilliseconds
};
}
// Called on each edge node when a purge command arrives
public async Task OnPurgeCommandReceived(PurgeCommand command)
{
switch (command.Type)
{
case PurgeType.URL:
foreach (var url in command.Targets)
{
var cacheKey = ComputeCacheKey(url);
_localIndex.Remove(cacheKey);
}
break;
case PurgeType.Tag:
foreach (var tag in command.Targets)
{
var keys = _localIndex.GetKeysByTag(tag);
foreach (var key in keys)
{
_localIndex.Remove(key);
}
}
break;
case PurgeType.Wildcard:
var pattern = command.Targets[0];
var matchingKeys = _localIndex.GetKeysByPattern(pattern);
foreach (var key in matchingKeys)
{
_localIndex.Remove(key);
}
break;
case PurgeType.Prefix:
var prefix = command.Targets[0];
var prefixedKeys = _localIndex.GetKeysByPrefix(prefix);
foreach (var key in prefixedKeys)
{
_localIndex.Remove(key);
}
break;
}
// Send ACK back
await SendPurgeAck(command.PurgeId);
}
}
10. Origin Shield & Shielding
Origin shielding is one of the most impactful optimizations in a CDN. Without shielding, if content is not cached at an edge PoP, the edge server contacts the origin directly. When multiple edge PoPs simultaneously miss on the same content (e.g., during a cache stampede or when new content is first requested), they all bombard the origin with duplicate requests, potentially overwhelming it. This is known as the thundering herd problem.
How Shielding Works
A shield is a dedicated caching layer placed between edge PoPs and origin servers. There are typically 3-5 shields per continent. All cache misses from edge PoPs in a region are routed to the regional shield first. The shield checks its own larger cache — if the content is there, it serves it. If not, only the shield contacts the origin. This means that for a given piece of content, only one request per shield region ever reaches origin, regardless of how many edge PoPs are missing it.
Shield Selection Algorithm
C#
public class ShieldSelector
{
private readonly Dictionary<string, List<ShieldNode>> _shieldsByRegion;
private readonly HealthMonitor _healthMonitor;
private readonly LoadBalancer _loadBalancer;
public ShieldNode SelectShield(string edgePoPId, string originId)
{
// Find which region this edge PoP belongs to
var edgeRegion = GetEdgeRegion(edgePoPId);
var candidateShields = _shieldsByRegion[edgeRegion];
// Filter to healthy shields
var healthyShields = candidateShields
.Where(s => _healthMonitor.IsHealthy(s.ShieldId))
.ToList();
if (healthyShields.Count == 0)
{
// Fallback: use any healthy shield globally
healthyShields = _shieldsByRegion
.SelectMany(kvp => kvp.Value)
.Where(s => _healthMonitor.IsHealthy(s.ShieldId))
.OrderBy(s => GetLatency(edgePoPId, s.ShieldId))
.Take(2)
.ToList();
}
// Select based on load and latency
return _loadBalancer.SelectLeastLoaded(
healthyShields,
weights: s => new LoadWeight
{
LatencyWeight = 1.0 / (GetLatency(edgePoPId, s.ShieldId) + 1),
LoadWeight = 1.0 - s.CurrentLoadRatio,
CacheWeight = s.CacheHitRatio
});
}
private int GetEdgeRegion(string edgePoPId)
{
// Edge PoP to region mapping
return edgePoPId switch
{
"nyc" or "dc" or "bos" or "chi" or "atl" or "mia" => 0, // US-East
"lax" or "sea" or "dfw" or "den" or "sjc" => 1, // US-West
"lhr" or "fra" or "ams" or "par" or "mad" => 2, // EU
"nrt" or "hnd" or "sin" or "hkg" or "icn" => 3, // APAC
"syd" or "mel" or "akl" => 4, // Oceania
_ => 0
};
}
}
Cache Stampede Protection
Even with shielding, cache stampedes can occur at the shield layer when popular new content is first requested. The CDN uses several techniques to mitigate this:
- Request coalescing: When multiple requests for the same cache key arrive simultaneously, only one actually fetches from origin. The others wait and receive the same response. This is implemented using a per-cache-key mutex/lock at the shield level.
- Early expiration (Jitter): TTLs are reduced by a random jitter (e.g., 10%) so that all copies of the same content don't expire simultaneously, preventing synchronized cache misses.
- Background refresh: Content that is frequently accessed is proactively revalidated in the background before its TTL expires, ensuring it is never stale.
C#
// Request coalescing to prevent cache stampedes
public class RequestCoalescer
{
private readonly ConcurrentDictionary<string, Lazy<Task<OriginResponse>>>
_inFlightRequests = new();
public async Task<OriginResponse> CoalesceRequest(
string cacheKey,
Func<Task<OriginResponse>> originFetch)
{
var lazy = _inFlightRequests.GetOrAdd(
cacheKey,
_ => new Lazy<Task<OriginResponse>>(
() => originFetch(),
LazyThreadSafetyMode.ExecutionAndPublication));
try
{
return await lazy.Value;
}
finally
{
// Remove from dictionary once complete
_inFlightRequests.TryRemove(cacheKey, out _);
}
}
}
11. TLS Termination & Certificate Management
Every HTTPS request passing through a CDN requires TLS termination at the edge. This involves performing the TLS handshake, decrypting the request, processing it, and encrypting the response — all without introducing significant latency. Modern CDNs handle millions of TLS handshakes per second across their global network.
TLS 1.3 and 0-RTT
TLS 1.3 (RFC 8446) significantly improves performance over TLS 1.2 by reducing the handshake from two round trips to one (1-RTT). For returning clients, TLS 1.3 supports 0-RTT resumption using Pre-Shared Keys (PSK), allowing the client to send encrypted data in the very first message without waiting for the handshake to complete. This is critical for CDN performance — the TLS handshake is often the largest single contributor to connection latency.
Certificate Management
Managing TLS certificates at CDN scale is a massive operational challenge. Cloudflare alone manages millions of certificates. The system must support:
- Universal SSL: Automatic certificate provisioning for all customers using Let's Encrypt or internal CAs via the ACME protocol.
- Custom certificates: Customers upload their own certificates, which are encrypted, distributed to all edge PoPs, and auto-renewed.
- Advanced certificates: Wildcards, multi-domain (SAN), and dedicated IP certificates for enterprise customers.
- Certificate transparency: All issued certificates are logged to public CT logs for auditability and to prevent misissuance.
C#
// ACME-based automatic certificate provisioning
public class CertificateProvisioner
{
private readonly AcmeClient _acmeClient;
private readonly CertificateStore _certStore;
private readonly EdgeDistributor _distributor;
public async Task ProvisionCertificate(string domain)
{
// Step 1: Create ACME order
var order = await _acmeClient.CreateOrderAsync(new[]
{
new DnsIdentifier { Type = "dns", Value = domain }
});
// Step 2: Complete DNS-01 challenge
foreach (var authz in order.Authorizations)
{
var challenge = authz.Challenges
.First(c => c.Type == "dns-01");
// Create _acme-challenge TXT record
var txtValue = _acmeClient.ComputeKeyAuthorization(
challenge.Token);
await CreateDnsTxtRecord(
$"_acme-challenge.{domain}",
txtValue);
// Wait for DNS propagation
await Task.Delay(TimeSpan.FromSeconds(30));
// Notify ACME server to validate
await _acmeClient.CompleteChallengeAsync(challenge);
}
// Step 3: Generate CSR and finalize order
var keyPair = GenerateEcdsaKeyPair(P256);
var csr = GenerateCsr(domain, keyPair);
var cert = await _acmeClient.FinalizeOrderAsync(
order, csr);
// Step 4: Store certificate encrypted
var certId = await _certStore.StoreAsync(
domain, cert, keyPair,
encryptAtRest: true);
// Step 5: Distribute to all edge PoPs
await _distributor.DistributeCertificateAsync(
certId,
parallelism: 100,
timeoutSeconds: 60);
// Step 6: Schedule renewal (30 days before expiry)
ScheduleRenewal(certId, cert.NotAfter.AddDays(-30));
}
}
OCSP Stapling
To avoid the latency of clients contacting Certificate Authorities for OCSP (Online Certificate Status Protocol) validation, the CDN performs OCSP stapling. The edge server periodically fetches the OCSP response from the CA, caches it, and "staples" it to the TLS handshake. This eliminates an additional network round trip for the client and improves both privacy (the CA doesn't learn which clients are connecting) and performance.
Hardware Acceleration
At CDN scale, TLS operations consume significant CPU resources. Modern deployments use hardware acceleration to offload cryptographic operations:
- AES-NI instructions: Hardware-accelerated AES encryption/decryption built into modern CPUs.
- Intel QAT (QuickAssist Technology): Dedicated hardware for TLS acceleration, allowing a single server to handle 100,000+ TLS handshakes per second.
- NVIDIA BlueField DPU: Smart NICs that offload TLS termination from the CPU entirely, freeing CPU cores for application logic.
12. DDoS Protection Architecture
DDoS protection is a core competency of any modern CDN. Attack volumes have grown exponentially — from gigabit-scale attacks in the 2010s to multi-terabit attacks today. The CDN's distributed architecture is its greatest defense asset: because the CDN operates in 300+ PoPs with aggregate bandwidth of 100+ Tbps, it can absorb attack traffic that would instantly overwhelm any single data center.
Attack Classification
| Layer | Attack Type | Examples | Mitigation |
|---|---|---|---|
| L3/L4 (Network) | Volumetric | UDP flood, DNS amplification, NTP amplification, Memcached amplification | Anycast absorption, upstream filtering, BGP Flowspec |
| L4 (Transport) | Protocol | SYN flood, ACK flood, RST flood, fragmentation attacks | SYN cookies, connection rate limiting, protocol validation |
| L7 (Application) | Application-layer | HTTP flood, Slowloris, RUDY, WordPress pingback, LDAP injection | Behavioral analysis, JS challenges, CAPTCHA, rate limiting |
Multi-Layer Defense Architecture
SYN Cookie Implementation
C#
// SYN cookie implementation for SYN flood protection
public class SynCookieValidator
{
private readonly byte[] _secret;
private readonly int _cookieBits = 24;
private readonly TimeSpan _cookieWindow = TimeSpan.FromSeconds(60);
public SynCookieValidator()
{
_secret = new byte[32];
RandomNumberGenerator.Fill(_secret);
// Rotate secret every 5 minutes
_ = RotateSecretPeriodically();
}
public uint GenerateCookie(IPEndPoint client)
{
var timestamp = (uint)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 16);
var data = CombineBytes(
client.Address.GetAddressBytes(),
BitConverter.GetBytes(timestamp));
var hash = HMACSHA256.HashData(_secret, data);
return BitConverter.ToUInt32(hash, 0) & ((1u << _cookieBits) - 1);
}
public bool ValidateCookie(IPEndPoint client, uint cookie)
{
// Check current and previous time window (clock skew tolerance)
for (int offset = 0; offset <= 1; offset++)
{
var timestamp = (uint)(DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 16) - offset;
var data = CombineBytes(
client.Address.GetAddressBytes(),
BitConverter.GetBytes(timestamp));
var hash = HMACSHA256.HashData(_secret, data);
var expectedCookie = BitConverter.ToUInt32(hash, 0)
& ((1u << _cookieBits) - 1);
if (cookie == expectedCookie)
return true;
}
return false;
}
}
Bot Detection with Machine Learning
Modern DDoS attacks increasingly use sophisticated bots that mimic human behavior — they execute JavaScript, maintain session cookies, and follow realistic browsing patterns. Simple fingerprinting (checking for missing headers, known bot user agents) is no longer sufficient. CDN providers deploy ML models that analyze:
- Behavioral signals: Mouse movement patterns, scroll behavior, keystroke dynamics, click timing.
- Network signals: IP reputation, ASN reputation, historical behavior, proxy/VPN detection.
- Browser signals: Canvas fingerprint, WebGL fingerprint, WebRTC leaks, browser plugin enumeration.
- Request patterns: Request timing distribution, resource loading order, navigation patterns.
13. Web Application Firewall (WAF)
The Web Application Firewall (WAF) inspects HTTP traffic at the edge for malicious patterns targeting web applications. Unlike DDoS protection (which focuses on volumetric and protocol attacks), the WAF focuses on application-layer attacks — SQL injection, cross-site scripting, remote code execution, path traversal, and other OWASP Top 10 vulnerabilities.
WAF Architecture
Rule Engine Design
C#
// WAF rule evaluation engine
public class WafRuleEngine
{
private readonly List<WafRule> _rules;
private readonly RegexPool _regexPool;
private readonly RuleCompiler _compiler;
public WafResult Evaluate(HttpRequest request)
{
var context = BuildEvaluationContext(request);
foreach (var rule in _rules)
{
if (!rule.IsEnabled || !rule.AppliesToZone(request.ZoneId))
continue;
var matchResult = rule.Evaluate(context);
if (matchResult.IsMatch)
{
// Log the security event
LogSecurityEvent(new SecurityEvent
{
RuleId = rule.RuleId,
Action = rule.Action,
ClientIP = request.ClientIP,
URI = request.Uri,
MatchedData = matchResult.MatchedData,
Timestamp = DateTime.UtcNow
});
switch (rule.Action)
{
case WafAction.Block:
return WafResult.Blocked(rule);
case WafAction.Challenge:
return WafResult.Challenge(rule);
case WafAction.Simulate:
// Log but don't block (for testing rules)
continue;
case WafAction.ManagedChallenge:
return WafResult.ManagedChallenge(rule);
}
}
}
return WafResult.Allowed();
}
private EvaluationContext BuildEvaluationContext(
HttpRequest request)
{
return new EvaluationContext
{
// URI components
URI = request.Uri,
URIPath = request.Path,
URIQuery = request.QueryString,
URIPathDecoded = Uri.UnescapeDataString(request.Path),
URIQueryDecoded = Uri.UnescapeDataString(request.QueryString),
// Headers
UserAgent = request.Headers.GetValueOrDefault("User-Agent"),
Referer = request.Headers.GetValueOrDefault("Referer"),
AcceptLanguage = request.Headers.GetValueOrDefault("Accept-Language"),
AllHeaders = request.Headers,
// Body (for POST/PUT)
RequestBody = request.HasBody ? request.Body : null,
RequestBodyLength = request.ContentLength ?? 0,
// Client info
ClientIP = request.ClientIP,
ClientCountry = request.ClientCountry,
ClientASN = request.ClientASN,
HTTPMethod = request.Method,
HTTPVersion = request.HttpVersion,
// Pre-computed fields for fast rule evaluation
URILength = request.Uri.Length,
QueryParamCount = request.Query.Count,
HeaderCount = request.Headers.Count,
BodyMD5 = request.HasBody ?
Convert.ToHexString(MD5.HashData(request.Body)) : null
};
}
}
OWASP Top 10 Rule Examples
| Vulnerability | Detection Pattern | Typical Action |
|---|---|---|
| SQL Injection | Pattern match on URI params, body, headers for SQL keywords (UNION SELECT, OR 1=1, etc.) |
Block |
| Cross-Site Scripting (XSS) | Detect <script>, event handlers (onerror=), JavaScript URIs |
Block |
| Path Traversal | Detect ../, %2e%2e%2f, ..%252f (double encoding) |
Block |
| Remote Code Execution | Detect eval(, exec(, command injection patterns |
Block |
| Session Fixation | Detect session ID in URL parameters | Challenge |
| Bot Traffic | ML model classification based on behavioral analysis | Challenge |
14. Load Balancing at the Edge
Load balancing in a CDN operates at multiple levels: balancing traffic across edge servers within a PoP, balancing origin requests across origin servers, and balancing traffic across multiple origin shields. Each level uses different algorithms and health checking mechanisms.
Intra-PoP Load Balancing
Within a single edge PoP, incoming traffic is distributed across the server fleet. The distribution mechanism depends on the network architecture:
- Hardware load balancer (F5/A10): Traditional approach for Tier 1 PoPs. Handles 40+ Gbps per device, provides SSL offloading, and supports sophisticated health checks.
- ECMP (Equal-Cost Multi-Path): Multiple servers share the same virtual IP (VIP) via BGP ECMP. Packets are distributed across servers based on flow hash. Stateless and extremely high performance, but uneven distribution is possible.
- DSR (Direct Server Return): Load balancer only handles inbound traffic; responses bypass the load balancer and go directly to the client. Reduces load balancer bottleneck.
C#
// Origin load balancer with health checks and failover
public class OriginLoadBalancer
{
private readonly List<OriginTarget> _origins;
private readonly HealthChecker _healthChecker;
private readonly CircuitBreaker _circuitBreaker;
public OriginLoadBalancer(List<OriginTarget> origins)
{
_origins = origins;
_healthChecker = new HealthChecker(
interval: TimeSpan.FromSeconds(5),
timeout: TimeSpan.FromSeconds(3),
unhealthyThreshold: 3,
healthyThreshold: 2);
_circuitBreaker = new CircuitBreaker(
failureThreshold: 5,
recoveryTimeout: TimeSpan.FromSeconds(30));
}
public async Task<OriginTarget> SelectOrigin(
string zoneId, string contentKey)
{
var healthyOrigins = _origins
.Where(o => o.ZoneId == zoneId)
.Where(o => _healthChecker.IsHealthy(o.OriginId))
.Where(o => !_circuitBreaker.IsOpen(o.OriginId))
.OrderBy(o => o.CurrentConnections)
.ThenBy(o => o.Weight) // Lower weight = higher priority
.ToList();
if (healthyOrigins.Count == 0)
{
// All origins unhealthy: open circuit breaker and try
// the least-recently-failed origin
return _origins
.Where(o => o.ZoneId == zoneId)
.OrderBy(o => o.LastFailureTime)
.First();
}
// Weighted round-robin among healthy origins
return WeightedRoundRobin(healthyOrigins);
}
// Active health checking with TCP and HTTP probes
private async Task RunHealthCheck(OriginTarget origin)
{
while (true)
{
try
{
using var client = new TcpClient();
var connectTask = client.ConnectAsync(
origin.Address, origin.Port);
var timeout = Task.Delay(_healthChecker.Timeout);
var completed = await Task.WhenAny(connectTask, timeout);
if (completed == timeout || !client.Connected)
{
_healthChecker.RecordFailure(origin.OriginId);
continue;
}
// TCP health check passed; do HTTP check
var httpClient = new HttpClient
{
BaseAddress = new Uri(
$"https://{origin.Address}:{origin.Port}"),
Timeout = _healthChecker.Timeout
};
var response = await httpClient.GetAsync(
origin.HealthCheckPath);
if ((int)response.StatusCode >= 500)
{
_healthChecker.RecordFailure(origin.OriginId);
}
else
{
_healthChecker.RecordSuccess(origin.OriginId);
_circuitBreaker.RecordSuccess(origin.OriginId);
}
}
catch (Exception)
{
_healthChecker.RecordFailure(origin.OriginId);
}
await Task.Delay(_healthChecker.Interval);
}
}
}
Circuit Breaker Pattern
The circuit breaker prevents the CDN from repeatedly hammering a failing origin. When the failure count for an origin exceeds the threshold, the circuit "opens" — requests are immediately failed or served from stale cache for the configured recovery period. After the recovery period, the circuit enters a "half-open" state, allowing a single probe request. If it succeeds, the circuit closes; if it fails, it opens again.
stale-if-error directive). This provides graceful degradation — users see slightly outdated content rather than an error page. The maximum staleness period is configurable by the customer and defaults to 1 day.
15. Edge Computing & Serverless@Edge
Edge computing has transformed CDNs from passive caching layers into active compute platforms. Platforms like Cloudflare Workers, Fastly Compute, and Deno Deploy allow customers to run JavaScript, TypeScript, Rust, and even C++ code directly at the edge — in every PoP, within milliseconds of end users. This enables use cases like A/B testing, authentication, API gateway logic, personalization, and real-time content transformation, all without routing traffic back to a central origin.
Isolation Model
Edge compute isolates customer code using WebAssembly (Wasm) or V8 isolates (not full VMs or containers). This provides:
- Cold start < 1ms: V8 isolates are lightweight enough to create and destroy per-request if needed.
- Memory isolation: Each isolate has a strict memory limit (typically 128MB).
- CPU time limits: Request handlers have strict CPU time limits (typically 10-50ms).
- Network isolation: Customer code can only access allowed destinations (origin servers, KV stores).
C#
// Edge function example: A/B testing at the edge
public class AbTestingEdgeFunction
{
// Configuration stored in KV store, updated by control plane
private readonly AbTestConfig _config;
public async Task<EdgeResponse> HandleRequest(
EdgeRequest request)
{
// Check for existing A/B test cookie
var existingBucket = request.Cookies
.GetValueOrDefault("ab-test");
string bucket;
if (!string.IsNullOrEmpty(existingBucket) &&
_config.Buckets.ContainsKey(existingBucket))
{
bucket = existingBucket;
}
else
{
// Assign to bucket based on consistent hashing
bucket = AssignBucket(request.ClientIP, _config.TestId);
}
var bucketConfig = _config.Buckets[bucket];
// Rewrite URL to bucket-specific origin or path
var modifiedRequest = request with
{
URI = bucketConfig.PathRewrite(request.URI),
Headers = request.Headers
.Set("X-AB-Bucket", bucket)
.Set("X-AB-Test", _config.TestId)
};
// Fetch from origin
var response = await FetchFromOrigin(modifiedRequest);
// Add A/B test bucket to response
return response with
{
Headers = response.Headers
.SetCookie($"ab-test={bucket}; " +
$"Path=/; Max-Age=86400; HttpOnly"),
Headers = response.Headers
.Set("X-AB-Bucket", bucket)
};
}
private string AssignBucket(string clientId, string testId)
{
// Consistent hashing ensures same user always
// gets same bucket
var hash = SHA256.HashData(
Encoding.UTF8.GetBytes($"{clientId}:{testId}"));
var hashInt = BitConverter.ToUInt32(hash, 0);
var bucketIndex = hashInt % (uint)_config.Buckets.Count;
return _config.Buckets.Keys.ElementAt((int)bucketIndex);
}
}
Edge KV Storage
Edge functions often need access to configuration data, feature flags, or small datasets. Edge KV stores provide eventually-consistent key-value storage that is replicated to every edge PoP. Reads are served locally (sub-millisecond), while writes propagate globally within 30-60 seconds.
Edge Compute Use Cases
| Use Case | Description | Latency Benefit |
|---|---|---|
| A/B Testing | Route users to experiment variants based on cookies/headers | Eliminates origin round trip for bucket assignment |
| Authentication | Validate JWTs, check session cookies, enforce access control | Auth decisions in <1ms instead of 50-100ms origin round trip |
| Image Optimization | Resize, compress, and convert images on-the-fly | Edge processing eliminates origin compute load |
| API Gateway | Route, authenticate, rate-limit, and transform API requests | Reduces API latency for geographically distributed clients |
| Geolocation-based Content | Serve different content based on user's country/city | Content decision at edge without origin lookup |
| Bot Detection | Run JS challenges and behavioral analysis at edge | Challenge issued immediately without waiting for origin |
16. Real-Time Analytics & Logging
CDN analytics serve two audiences: customers (who need visibility into their own traffic, cache performance, and security events) and operations (who need fleet-wide monitoring, capacity planning, and incident detection). The analytics pipeline must handle enormous throughput — billions of log events per day — while providing sub-second query latency for real-time dashboards.
Data Pipeline Architecture
C#
// Edge-side log batching and async shipping
public class EdgeLogShipper
{
private readonly Channel<RequestLogEntry> _logChannel;
private readonly KafkaProducer<LogBatch> _kafkaProducer;
private readonly Timer _flushTimer;
private readonly int _batchSize = 1000;
private readonly TimeSpan _flushInterval = TimeSpan.FromSeconds(1);
public EdgeLogShipper(KafkaProducer<LogBatch> producer)
{
_kafkaProducer = producer;
_logChannel = Channel.CreateBounded<RequestLogEntry>(
new BoundedChannelOptions(100_000)
{
FullMode = BoundedChannelFullMode.DropOldest,
SingleReader = true
});
_flushTimer = new Timer(
async _ => await FlushBatch(),
null,
_flushInterval,
_flushInterval);
_ = ConsumeChannel();
}
public void LogRequest(RequestLogEntry entry)
{
// Non-blocking write; drop if channel is full
_logChannel.Writer.TryWrite(entry);
}
private async Task ConsumeChannel()
{
var batch = new List<RequestLogEntry>(_batchSize);
await foreach (var entry in _logChannel.Reader.ReadAllAsync())
{
batch.Add(entry);
if (batch.Count >= _batchSize)
{
await FlushBatch();
batch.Clear();
}
}
}
private async Task FlushBatch()
{
var batch = new LogBatch
{
EdgeNodeId = Environment.MachineName,
Timestamp = DateTime.UtcNow,
Entries = Interlocked.Exchange(
ref _currentBatch, new List<RequestLogEntry>())
};
if (batch.Entries.Count > 0)
{
await _kafkaProducer.ProduceAsync(
$"cdn-logs-{batch.Entries[0].ZoneId}",
batch);
}
}
}
Real-Time Anomaly Detection
The analytics pipeline includes an anomaly detection system that monitors traffic patterns in real-time. It detects:
- Traffic spikes: Sudden increases in request rate (may indicate DDoS or viral content).
- Cache hit ratio drops: Sudden decrease in cache hit ratio may indicate a cache configuration issue or an attack with unique URIs.
- Origin latency spikes: Increased origin response times may indicate origin overload or network issues.
- Error rate increases: Sudden increases in 5xx errors may indicate upstream failures.
- Geographic anomalies: Unexpected traffic patterns from unusual geographies may indicate targeted attacks.
RayId that is included in response headers and propagated through the entire request path (edge → shield → origin). This allows customers and support teams to trace a specific request through the entire system for debugging. Cloudflare pioneered this pattern, and it has become an industry standard.
17. Multi-CDN Strategy
Large enterprises and media companies increasingly use multiple CDN providers simultaneously — a practice known as Multi-CDN. This approach improves reliability (if one CDN goes down, traffic shifts to another), performance (each CDN may have different strengths in different regions), and negotiating leverage (avoiding vendor lock-in). Designing a multi-CDN architecture introduces additional complexity in traffic management, failover, and observability.
Multi-CDN Traffic Management
CDN Selection Strategies
| Strategy | Description | Pros | Cons |
|---|---|---|---|
| Primary/Secondary | All traffic to primary; failover to secondary on outage | Simple; predictable cost | Wastes secondary capacity; slow failover |
| Weighted Split | Traffic split 70/30 or 80/20 across providers | Better utilization; reduces single-provider risk | Complex cost modeling; cache duplication |
| Geo-Based | Different CDNs for different regions | Optimizes per-region performance | Complex DNS; regional failover limited |
| Real-Time Steering | DNS steers based on real-time performance metrics | Best performance; automatic failover | Most complex; requires monitoring infrastructure |
Real-Time CDN Steering
C#
// Multi-CDN real-time steering engine
public class CdnSteeringEngine
{
private readonly Dictionary<string, CdnProvider> _providers;
private readonly PerformanceMonitor _monitor;
private readonly DnsManager _dns;
public async Task<SteeringDecision> SelectCdn(
string clientIp, string requestUri)
{
var clientGeo = _geoDb.Lookup(clientIp);
var decisions = new List<CdnScore>();
foreach (var provider in _providers.Values)
{
// Get real-time performance metrics for this provider
// in the client's region
var metrics = _monitor.GetMetrics(
provider.Id, clientGeo.Region);
var score = new CdnScore
{
ProviderId = provider.Id,
// Weighted scoring model
Score =
(metrics.CacheHitRatio * 0.3) +
((1.0 / Math.Max(metrics.AvgLatencyMs, 1)) * 0.3) +
((1.0 - metrics.ErrorRate) * 0.2) +
((1.0 - metrics.OriginLatencyMs / 1000) * 0.2),
LatencyMs = metrics.AvgLatencyMs,
CacheHitRatio = metrics.CacheHitRatio,
ErrorRate = metrics.ErrorRate,
IsHealthy = metrics.ErrorRate < 0.05
&& metrics.AvgLatencyMs < 200
};
decisions.Add(score);
}
// Select best provider, excluding unhealthy ones
var selected = decisions
.Where(d => d.IsHealthy)
.OrderByDescending(d => d.Score)
.FirstOrDefault();
// Update DNS to direct future traffic
await _dns.UpdateSteeringRecord(
requestUri, selected.ProviderId,
ttl: 30); // Short TTL for quick failover
return new SteeringDecision
{
ProviderId = selected.ProviderId,
Scores = decisions,
Reason = $"Score: {selected.Score:F3}"
};
}
}
18. Reliability & Failure Modes
CDN reliability is paramount — a CDN outage affects every website and application that depends on it. The 2021 Fastly outage lasted only 49 minutes but took down Reddit, Amazon, The Guardian, and the New York Times. The 2022 Cloudflare outage (though brief) demonstrated that even the most resilient CDNs are not immune to configuration errors. Designing for reliability requires understanding failure modes and building appropriate safeguards.
Failure Mode Analysis
| Failure Mode | Impact | Detection | Mitigation |
|---|---|---|---|
| PoP network partition | Users routed to partitioned PoP see errors | BGP route withdrawal; health checks fail | Anycast failover; BGP withdrawal routes traffic to healthy PoPs |
| Edge server crash | Requests handled by failed server are dropped | Process monitor; health endpoint | Load balancer removes server; restart via process manager |
| Cache corruption | Garbage content served; potential security risk | Content hash validation; checksum verification | Content integrity checks at cache store/retrieve; quarantine corrupted entries |
| Origin failure | Cache misses result in 5xx errors | Health checks; error rate monitoring | Circuit breaker; serve stale (stale-if-error); failover to backup origin |
| DNS failure | Users cannot resolve CDN domain; site becomes unreachable | DNS monitoring from multiple vantage points | Multiple DNS providers; anycast DNS; short TTLs; DNSSEC |
| Certificate expiry | TLS errors; browsers reject connections | Certificate monitoring; expiry alerts | Automated renewal; certificate transparency monitoring; early renewal |
| Config push failure | Some PoPs run stale configuration | Config version tracking; edge sync monitoring | Gradual rollout; config version comparison; rollback capability |
| DDoS exhaustion | Legitimate traffic degraded during attack | Traffic anomaly detection; latency monitoring | Over-provisioned capacity; upstream ISP filtering; scrubbing centers |
Bulkhead Pattern
C#
// Bulkhead isolation to prevent cascade failures
public class BulkheadIsolatedRequestHandler
{
private readonly SemaphoreSlim _generalSlot;
private readonly SemaphoreSlim _premiumSlot;
private readonly SemaphoreSlim _apiSlot;
private readonly OriginLoadBalancer _origin;
public BulkheadIsolatedRequestHandler(int maxConcurrent = 10000)
{
// Partition capacity across customer tiers
_generalSlot = new SemaphoreSlim(
(int)(maxConcurrent * 0.5)); // 50% for general
_premiumSlot = new SemaphoreSlim(
(int)(maxConcurrent * 0.3)); // 30% for premium
_apiSlot = new SemaphoreSlim(
(int)(maxConcurrent * 0.2)); // 20% for API
}
public async Task<EdgeResponse> HandleRequest(
EdgeRequest request)
{
var semaphore = GetBulkhead(request.PlanTier);
if (!await semaphore.WaitAsync(TimeSpan.FromSeconds(5)))
{
return EdgeResponse.ServiceUnavailable(
"Server capacity temporarily exceeded");
}
try
{
return await ProcessRequest(request);
}
finally
{
semaphore.Release();
}
}
private SemaphoreSlim GetBulkhead(PlanTier tier) => tier switch
{
PlanTier.Premium => _premiumSlot,
PlanTier.Enterprise => _premiumSlot,
PlanTier.API => _apiSlot,
_ => _generalSlot
};
}
Chaos Engineering
Proactive reliability testing through chaos engineering is essential for CDNs. The practice involves deliberately injecting failures into the production system to verify that automated recovery mechanisms work correctly:
- Edge server kills: Randomly terminate edge server processes to verify load balancer failover works within seconds.
- Network partitions: Use
tc netemto simulate network partitions between PoPs to verify graceful degradation. - Origin unavailability: Block traffic to specific origin servers to verify circuit breaker and stale-serve behavior.
- Certificate invalidation: Revoke a certificate to verify automated renewal completes before expiry.
- Config corruption: Push invalid configurations to verify validation catches them before they reach production.
The most dangerous CDN failures are not hardware failures (which anycast handles gracefully) but configuration errors that propagate to all PoPs simultaneously. A misconfigured WAF rule that blocks all traffic, or a cache configuration that bypasses all content, can take down every customer instantly. Mitigation requires: staged rollouts (1% → 10% → 50% → 100% of PoPs), automated canary testing, instant rollback capability, and a "kill switch" configuration that bypasses all CDN logic and serves directly from origin.
19. Cost Estimation & Infrastructure Sizing
Operating a CDN is capital-intensive. The cost structure is dominated by bandwidth (which is often the most expensive line item), followed by hardware, real estate, power, and personnel. Understanding the cost model is essential for both building a CDN business and for estimating the cost of CDN services as a customer.
Cost Breakdown
| Cost Category | Percentage of Total | Notes |
|---|---|---|
| Bandwidth (transit + peering) | 35-45% | Largest cost; decreases with scale due to peering discounts |
| Hardware (servers, switches, NICs) | 20-25% | 3-4 year depreciation cycle; NVMe SSDs are a significant cost |
| Data center (colocation, power, cooling) | 15-20% | Varies wildly by location; Tier 1 metros most expensive |
| Personnel (engineering, ops, support) | 10-15% | 24/7 SRE teams across time zones; specialized networking engineers |
| Software licenses & tools | 3-5% | Monitoring, security scanning, development tools |
| Compliance & certifications | 1-2% | SOC 2, PCI DSS, ISO 27001 audits |
Infrastructure Sizing for 300 PoPs
| Component | Per PoP (Avg) | Total (300 PoPs) | Unit Cost | Total Cost |
|---|---|---|---|---|
| Edge Servers | 50 | 15,000 | $15,000 | $225M |
| NVMe SSDs | 200 | 60,000 | $500 | $30M |
| Network Switches | 5 | 1,500 | $30,000 | $45M |
| Bandwidth (annual) | ~10 Gbps avg | 3,000 Gbps | $20/Mbps/mo | $720M/year |
| Colocation (annual) | 10 racks | 3,000 racks | $8,000/rack/yr | $24M/year |
| Engineering Team | — | 500 engineers | $200K avg | $100M/year |
Unit Economics
- Bandwidth cost per GB: $0.005-0.02 depending on volume and peering agreements. Large CDNs negotiate rates of $0.002-0.005/GB through extensive peering.
- Server cost per request: A $15,000 server handling 500K req/s over 3 years costs approximately $0.01 per million requests.
- Storage cost per GB/month: $0.02-0.05 for NVMe SSD in a colocation facility.
- Purge cost: Essentially free — purge propagation uses existing pub/sub infrastructure and negligible bandwidth.
Cost Optimization Strategies
- Peering agreements: Establish direct peering with major ISPs to avoid transit costs. At sufficient scale, peering is free or reciprocal.
- Hardware optimization: Use custom server designs (like Open Compute Project) to reduce hardware costs by 20-30%.
- Cache efficiency: Higher cache hit ratios directly reduce bandwidth costs. Investing in better caching algorithms has a direct ROI.
- Location optimization: Not every PoP needs the same hardware. Tier 3 locations can use smaller, cheaper servers.
- Power efficiency: Use ARM-based servers for edge workloads — they offer 30-40% better performance per watt for cache-heavy workloads.
20. Interview Q&A Deep Dive
Q1: How would you handle a cache stampede when a popular piece of content goes viral?
Q2: How do you achieve sub-5-second global purge across 300+ PoPs?
Q3: How do you handle the thundering herd problem when origin shield fails?
Q4: Explain the trade-offs between cache hit ratio and content freshness.
Q5: How would you design TLS termination to handle 50 million TLS handshakes per second?
Q6: How do you detect and mitigate a DNS hijacking attack against your CDN?
Q7: Design a CDN for video streaming specifically — what's different from general HTTP CDN?
Q8: How do you handle a zero-day vulnerability in your edge server software across 300 PoPs?
Q9: How would you implement cache normalization to maximize hit ratios?
?b=2&a=1 and ?a=1&b=2 produce the same key; (2) Query string stripping — remove tracking parameters (utm_source, fbclid) that don't affect content; (3) Case normalization — lowercase hostname and path; (4) Encoding normalization — decode percent-encoded characters where safe (%20 → +); (5) Default document — /index.html and / serve the same content; (6) Protocol normalization — HTTP and HTTPS can share cache if origin content is identical. Each normalization step must be configurable per-zone, as some customers need to differentiate by these fields.