🌍 Geographic Latency
CDN reduces latency by caching content closer to users worldwide. 10-30x faster!
Imagine a user in Tokyo trying to load a website hosted in New York. Every request travels 10,000+ kilometers, adding 200-300ms of latency just from distance.
The problem: Geographic distance creates unavoidable latency. Even with fast networks, physics limits speed of light.
The solution: CDN (Content Delivery Network) - cache content at edge locations worldwide, closer to users.
CDN = Network of distributed servers that cache content at edge locations close to users.
example.com/image.jpg)Content that doesn’t change:
Why static content is perfect:
Content that changes, but can still be cached:
Considerations:
Content that shouldn’t be cached:
Design your APIs to work well with CDN caching:
Cache-Control header controls caching behavior:
1from flask import Flask, jsonify, make_response2
3app = Flask(__name__)4
5@app.route('/products')6def get_products():7 products = fetch_products()8
9 response = make_response(jsonify(products))10
11 # Cache for 5 minutes (public = CDN can cache)12 response.headers['Cache-Control'] = 'public, max-age=300'13
14 # ETag for validation15 response.headers['ETag'] = generate_etag(products)16
17 return response18
19@app.route('/user/profile')20def get_user_profile():21 profile = fetch_user_profile()22
23 response = make_response(jsonify(profile))24
25 # Don't cache (private, user-specific)26 response.headers['Cache-Control'] = 'private, no-cache'27
28 return response29
30@app.route('/static/image.jpg')31def get_image():32 # Static content - cache for 1 year33 response = make_response(send_file('image.jpg'))34 response.headers['Cache-Control'] = 'public, max-age=31536000'35 return response1import org.springframework.http.HttpHeaders;2import org.springframework.http.ResponseEntity;3
4@RestController5public class ApiController {6
7 @GetMapping("/products")8 public ResponseEntity<List<Product>> getProducts() {9 List<Product> products = fetchProducts();10
11 return ResponseEntity.ok()12 .header(HttpHeaders.CACHE_CONTROL, "public, max-age=300")13 .header(HttpHeaders.ETAG, generateETag(products))14 .body(products);15 }16
17 @GetMapping("/user/profile")18 public ResponseEntity<UserProfile> getUserProfile() {19 UserProfile profile = fetchUserProfile();20
21 return ResponseEntity.ok()22 .header(HttpHeaders.CACHE_CONTROL, "private, no-cache")23 .body(profile);24 }25
26 @GetMapping("/static/image.jpg")27 public ResponseEntity<Resource> getImage() {28 // Static content - cache for 1 year29 Resource image = new ClassPathResource("image.jpg");30 return ResponseEntity.ok()31 .header(HttpHeaders.CACHE_CONTROL, "public, max-age=31536000")32 .body(image);33 }34}| Directive | Meaning | Example |
|---|---|---|
public | CDN can cache | public, max-age=3600 |
private | Only browser can cache | private, max-age=3600 |
no-cache | Revalidate before use | no-cache |
no-store | Don’t cache at all | no-store |
max-age=N | Cache for N seconds | max-age=300 (5 min) |
must-revalidate | Must revalidate when expired | must-revalidate |
Add version to static assets to enable long caching:
1❌ Bad: /static/app.js (can't cache long - might change)2✅ Good: /static/app.v1.2.3.js (can cache forever - new version = new URL)How it works:
app.v1.2.4.js)1from flask import Flask, url_for2
3app = Flask(__name__)4
5# In your template6@app.route('/')7def index():8 version = "1.2.3"9 return f"""10 <html>11 <link rel="stylesheet" href="/static/styles.v{version}.css">12 <script src="/static/app.v{version}.js"></script>13 </html>14 """1@Controller2public class WebController {3
4 @GetMapping("/")5 public String index(Model model) {6 String version = "1.2.3";7 model.addAttribute("cssVersion", version);8 model.addAttribute("jsVersion", version);9 return "index";10 }11}12
13// In template (Thymeleaf example)14// <link rel="stylesheet" th:href="@{/static/styles.v${cssVersion}.css}">15// <script th:src="@{/static/app.v${jsVersion}.js}"></script>Split your API into cacheable and non-cacheable endpoints:
Example:
/products → Cacheable (same for all users)/user/cart → Not cacheable (user-specific)/public/profile/:id → Cacheable (public profile)/user/profile → Not cacheable (private, authenticated)ETag = hash of content. Allows “conditional requests”:
1Request: GET /products2 If-None-Match: "abc123"3
4Response: 304 Not Modified (if ETag matches)5 or 200 OK with new ETagBenefits:
Simplest pattern - CDN only for static files:
When to use:
CDN caches API responses too:
When to use:
At the code level, design your services to be cache-friendly:
1from abc import ABC, abstractmethod2from typing import Optional3from datetime import datetime, timedelta4
5class ProductService(ABC):6 @abstractmethod7 def get_products(self) -> list:8 pass9
10class DatabaseProductService(ProductService):11 def get_products(self) -> list:12 # Fetch from database13 return db.query("SELECT * FROM products")14
15class CacheableProductService(ProductService):16 def __init__(self, db_service: ProductService, cache: CacheClient):17 self.db_service = db_service18 self.cache = cache19 self.cache_key = "products:all"20 self.ttl = 300 # 5 minutes21
22 def get_products(self) -> list:23 # Try cache first24 cached = self.cache.get(self.cache_key)25 if cached:26 return cached27
28 # Cache miss - fetch from DB29 products = self.db_service.get_products()30
31 # Cache it32 self.cache.set(self.cache_key, products, ttl=self.ttl)33
34 return products35
36 def invalidate_cache(self):37 """Call this when products are updated"""38 self.cache.delete(self.cache_key)39
40class HTTPResponseBuilder:41 @staticmethod42 def build_cacheable_response(data, ttl: int = 300):43 """Build HTTP response with proper cache headers"""44 return {45 'data': data,46 'headers': {47 'Cache-Control': f'public, max-age={ttl}',48 'ETag': generate_etag(data)49 }50 }51
52 @staticmethod53 def build_private_response(data):54 """Build HTTP response that shouldn't be cached"""55 return {56 'data': data,57 'headers': {58 'Cache-Control': 'private, no-cache'59 }60 }1import java.util.List;2import java.util.Optional;3
4interface ProductService {5 List<Product> getProducts();6}7
8class DatabaseProductService implements ProductService {9 public List<Product> getProducts() {10 // Fetch from database11 return db.query("SELECT * FROM products");12 }13}14
15class CacheableProductService implements ProductService {16 private final ProductService dbService;17 private final CacheClient cache;18 private final String cacheKey = "products:all";19 private final int ttl = 300; // 5 minutes20
21 public CacheableProductService(ProductService dbService, CacheClient cache) {22 this.dbService = dbService;23 this.cache = cache;24 }25
26 public List<Product> getProducts() {27 // Try cache first28 Optional<String> cached = cache.get(cacheKey);29 if (cached.isPresent()) {30 return deserialize(cached.get());31 }32
33 // Cache miss - fetch from DB34 List<Product> products = dbService.getProducts();35
36 // Cache it37 cache.set(cacheKey, serialize(products), ttl);38
39 return products;40 }41
42 public void invalidateCache() {43 // Call this when products are updated44 cache.delete(cacheKey);45 }46}47
48class HTTPResponseBuilder {49 public static Map<String, Object> buildCacheableResponse(Object data, int ttl) {50 Map<String, Object> response = new HashMap<>();51 response.put("data", data);52
53 Map<String, String> headers = new HashMap<>();54 headers.put("Cache-Control", "public, max-age=" + ttl);55 headers.put("ETag", generateETag(data));56 response.put("headers", headers);57
58 return response;59 }60
61 public static Map<String, Object> buildPrivateResponse(Object data) {62 Map<String, Object> response = new HashMap<>();63 response.put("data", data);64
65 Map<String, String> headers = new HashMap<>();66 headers.put("Cache-Control", "private, no-cache");67 response.put("headers", headers);68
69 return response;70 }71}| Provider | Best For | Key Features |
|---|---|---|
| Cloudflare | General purpose | Free tier, DDoS protection, global network |
| AWS CloudFront | AWS ecosystem | Integrated with S3, Lambda@Edge |
| Fastly | Dynamic content | Real-time purging, edge computing |
| Akamai | Enterprise | Largest network, advanced features |
🌍 Geographic Latency
CDN reduces latency by caching content closer to users worldwide. 10-30x faster!
📦 Static First
Static content (images, CSS, JS) is perfect for CDN. Cache for long time.
🔧 Cache-Friendly APIs
Use proper HTTP headers, version URLs, separate cacheable from non-cacheable.
🏷️ Version URLs
Version static assets (app.v1.2.3.js) to enable long caching without invalidation.