CDN edge network concept page. Anycast PoPs (100-300 globally) routing users to nearest edge, tiered cache hierarchy (edge -> mid-tier -> origin shield -> origin), HTTP cache headers (Cache-Control, ETag, Vary, stale-while-revalidate), purge / invalidate (URL purge vs surrogate-key purge), TLS termination at edge with Let's Encrypt + 0-RTT, WAF (OWASP rules), bot management (JS challenge / Turnstile), DDoS scrubbing (L3/L4/L7), edge compute (Cloudflare Workers / Lambda@Edge), HLS/DASH streaming. Four scenarios: cold cache miss (200ms full path), hot cache hit (10ms edge), purge after content update with fan-out, attack mitigation (DDoS + bot + WAF + edge compute). Two ADRs covering CDN provider choice (Cloudflare vs Fastly vs CloudFront vs Akamai) and Cache-Control header strategy.
CDN — это слой между пользователем и origin, который физически решает проблему скорости света. Между Берлином и Сан-Франциско ~150ms RTT при идеальной маршрутизации; ни один origin в одной точке мира не может стабильно отдавать <50ms всему глобусу. CDN решает это, ставя 100–600 PoPs (Points of Presence) рядом с пользователем и кэшируя там contents.
Знать CDN нужно не для того, чтобы выучить наизусть параметры Cache-Control, а чтобы:
Любая публичная веб-система с >10K DAU без CDN — это техдолг, а не оптимизация. Cloudflare free tier (100GB/мес) покрывает большинство стартапов до Series A.
CDN — это многоуровневый read-through кэш, distributed по миру. Edge PoPs близко к пользователю; mid-tier (regional cache) консолидирует трафик в регионе; origin shield (один PoP per region) дедуплицирует concurrent misses перед origin'ом. Anycast IP + BGP делают так, что один и тот же IP отвечает из ближайшего PoP — без логики на клиенте.
Ключевая интуиция: CDN — это не один кэш, а иерархия кэшей. Edge miss → mid hit (часто) → shield hit (почти всегда) → origin (редко). Hit ratio на каждом уровне умножается: 70% edge × 50% mid × 80% shield = только 3% реквестов доходят до origin. Это и есть «90%+ кэш hit».
Вторая интуиция: cacheability определяется HTTP-заголовками, а не магией CDN. Cache-Control: public, max-age=31536000, immutable на статике с content-hash в URL — это разница между «origin спам» и «CDN отдаёт за тебя». Если origin шлёт Cache-Control: no-cache — CDN deferential, отдаёт каждый запрос в origin. Конфигурация CDN важна, но HTTP headers — это primary control plane.
Третья интуиция: purge ≠ cache fill. Invalidate говорит «забудь это значение»; следующий запрос триггерит fresh fetch. Cloudflare покрывает globally за ~5s, Fastly ~150ms (surrogate-key purge — best-in-class), CloudFront/Akamai 5–60min. Если purge медленный — content-addressable URLs (hero.a3f7d2.jpg) делают это non-issue: новый deploy = новый URL, старый сам экспайрится.
Пять групп:
user-eu, user-us, user-asia) — пользователи в трёх регионах. Anycast routes каждого к ближайшему PoP.pop-eu, pop-us, pop-asia) — anycast PoPs, 100–600 в мире у tier-1 vendors. Caching, TLS, security.tls, waf, bot, ddos, workers) — security pipeline на каждом PoP + edge compute (Workers / Lambda@Edge).mid-eu, mid-us, shield) — regional cache (Frankfurt mid, Ashburn mid) + origin shield (1 PoP per region, дедуплицирует concurrent misses).origin-lb, app, object-store, purge-api) — твоя инфра: LB, app server, S3/R2, purge management API.Edges — физические соединения: anycast user→PoP, security pipeline внутри PoP, cache hierarchy edge→mid→shield→origin, purge fan-out из management API во все tiers. Анимация (FlowBuilder) показывает четыре сценария по этим же edges — cold miss идёт вниз через всю иерархию, hot hit обрывается на edge, purge fan-out параллельно во все PoPs, атаки купируются на security layer.
Cold cache miss — путь через всю иерархию. Первый запрос на новый asset: anycast routes пользователя к ближайшему PoP (5ms RTT), пайплайн TLS → WAF → bot → DDoS scrub → cache lookup даёт MISS на edge. Запрос проваливается в mid-tier (10ms) — MISS. Mid → origin shield — MISS. Shield делает реальный transatlantic fetch к origin (~80ms), app сервер вытаскивает blob из S3 (50ms). Response летит обратно вверх с заголовками Cache-Control: public, max-age=31536000, immutable, и каждый tier по пути заполняет свой кэш. Total latency ~200ms — это дорогой one-time cost на версию asset'а, амортизируется на миллион follow-up hits.
Hot cache hit — 10ms response, origin не видит запрос. Второй пользователь (Tokyo) запрашивает тот же URL: anycast routes к PoP Tokyo, TLS 0-RTT resumption (handshake skipped), cache lookup → HIT. Response летит обратно с Age: 142 (cached 142 секунды назад). Total latency ~10ms. Origin не получил запрос вообще. При 95% global cache hit ratio на 1M запросов origin видит 50K — scaling становится тривиальным. Bonus: ETag revalidation возвращает 304 Not Modified без body — экономит bandwidth.
Purge / invalidate после deploy. CMS publish обновил hero.jpg в S3, но все PoPs продолжают отдавать старую версию (TTL не истёк, immutable header даже хуже). App вызывает purge API: POST /purge { url: "/img/hero.jpg" } или (лучше) { tag: "asset:hero" }. CDN purge fan-out параллельно в shield + mid + все edge PoPs. Cloudflare ~5s globally, Fastly ~150ms (surrogate-key — invalidates 1000+ URLs одной командой), CloudFront/Akamai 5–60min (slow path). Следующий запрос триггерит fresh fetch через всю иерархию. Best practice: content-addressable URLs (hero.a3f7d2.jpg) — новый deploy = новый URL, purge не нужен.
DDoS / bot / WAF — атаки умирают на edge. Три класса threats: (1) L3 SYN flood 100K pps с spoofed IPs — anycast distributes по 300 PoPs (333 pps на каждый), DDoS scrubbing дропает malformed SYNs, origin не видит ни пакета. (2) L7 HTTP flood 10K rps от одного IP на expensive endpoint — rate limit (config 100 rps per IP) возвращает 429 Too Many Requests, attacker отрезан на edge. (3) Headless Chrome scraper — bot management fingerprint (TLS JA3, no mouse movement) injects Turnstile/JS challenge, Selenium не решает, blocked. (4) SQL injection через curl — WAF OWASP rule 942100 matches, 403 Forbidden, request не дошёл до app. Edge — это primary security layer. Origin firewall — defence-in-depth, но 99% bad traffic умирает на PoP. Bonus: Workers / Lambda@Edge выполняют код прямо на PoP (A/B routing, geo-personalize, JWT verify) без round-trip к origin.
ADR-1: CDN provider — Cloudflare как default, исключения по жёстким причинам.
Контекст: четыре tier-1 vendors с разной философией. Cloudflare — global anycast (~310 PoPs), free tier до 100GB/мес, instant purge ~5s, integrated WAF/bot/Workers/R2/Pages/Stream, flat pricing (Pro/Business/Enterprise), dev-friendly (wrangler, Terraform). Fastly — programmable VCL (теперь Wasm Compute@Edge), instant purge ~150ms (best-in-class), surrogate-key invalidate, ~80 PoPs (меньше но fatter), per-request + per-GB pricing (предсказуемо для high-traffic). AWS CloudFront — глубокая интеграция с S3/EC2/Lambda/API Gateway, ~600 PoPs (включая small edges), один AWS invoice, но purge медленный (5–60min), pricing complex. Akamai — legacy enterprise, 350K+ servers, лучший в video / large file streaming, дорого (контракты $$$$), 24/7 white-glove поддержка.
Решение: default — Cloudflare для большинства новых SaaS / web apps (free tier позволяет начать с нуля, всё в одном dashboard, instant purge решает 90% операционных проблем). CloudFront — если AWS-native архитектура (S3 origins, Lambda backends, ALB) и хочется один billing, готовы терпеть медленный purge. Fastly — когда нужен programmable edge (custom routing logic в VCL/Wasm), instant surrogate-key purge для CMS/e-commerce, или high-volume video с предсказуемым billing. Akamai — только если уже enterprise customer с подписанным контрактом для tier-1 broadcast.
Последствия: lock-in на vendor APIs (purge, WAF rules, Workers код) — abstraction слой (Cedexis / NS1 multi-CDN) даёт portability за счёт complexity. Если можно избежать — лучше единственный vendor с хорошим dev experience.
ADR-2: Cache-Control стратегия по типу контента.
Контекст: команды часто либо не выставляют cache headers вообще (CDN использует defaults — непредсказуемо), либо ставят no-cache на всё «на всякий случай» (убивает CDN — origin получает 100% запросов). Заголовков много: max-age (browser TTL), s-maxage (CDN TTL, может быть >> browser), stale-while-revalidate (отдаём stale пока асинхронно обновляем), stale-if-error (отдаём stale если origin лёг), no-store (никогда не кэшировать), private (только browser), must-revalidate, Vary (cache отдельно по headers). ETag/Last-Modified для conditional requests (304 экономит bandwidth). Surrogate-Control / Surrogate-Key (Fastly) — отдельный TTL для CDN, теги для группового invalidate.
Решение по типу контента: static с content hash в URL (app.a3f7d2.js, image.png?v=hash) — public, max-age=31536000, immutable (год TTL, browser никогда не проверяет). HTML страницы — public, max-age=0, s-maxage=300, stale-while-revalidate=600, stale-if-error=86400 (browser всегда revalidate, CDN 5 минут, отдаёт stale при revalidation, выживает 24h при downed origin). API responses (read-only) — s-maxage=60 + Surrogate-Key: user-{id} product-{id} для targeted purge при mutation. User-specific (logged-in pages, cart) — private, no-store. Всегда — ETag на динамическое (экономит bandwidth), Vary: Accept-Encoding (иначе gzipped улетает к browser без gzip).
Последствия: правильные headers делают origin отдавать 5% трафика; неправильные — 100%. Никогда не кэшировать с Authorization без Vary: Authorization (cache poisoning между пользователями — security incident).
ADR-3: Purge стратегия — content-addressable URLs > surrogate-key > URL purge.
Контекст: каждый deploy / publish инвалидирует контент. Три способа: (1) URL purge — точечный invalidate по URL, медленный fan-out, нужен список всех URLs. (2) Surrogate-key purge (Fastly) — invalidate по тегу, одна команда покрывает 1000+ URLs за 150ms. (3) Content-addressable URLs — новый deploy = новый URL, purge не нужен вообще, старый сам экспайрится.
Решение: default — content-addressable URLs для всей статики (build pipeline генерирует app.{hash}.js). Никаких purge call'ов на deploy, zero downtime, старый и новый кэшируются независимо. Для CMS / API responses, где URLs stable — surrogate-key purge (если Fastly) или URL purge (Cloudflare instant). Антипаттерн: «purge entire CDN» на каждый deploy — caches горят, origin спам, latency скачки в первые минуты после deploy.
Последствия: build pipeline становится сложнее (нужны hashes в asset names, обновление references в HTML), зато purge становится non-issue. Trade-off — added complexity на этапе build vs operational simplicity в production.
Cache-Control: no-cache на всё «на всякий случай». Origin получает 100% запросов, CDN бесполезен. Каждый endpoint должен явно отвечать на вопрос «можно ли это кэшировать и сколько». No-cache — для PII / cart / checkout, не default.Authorization header без Vary: Authorization. CDN кэширует response пользователя A, отдаёт его пользователю B. Cache poisoning между пользователями = security incident. Browse history, PII, токены — всё может утечь./api/user/profile напрямую — отдашь профиль пользователя A пользователю B. Если нужно — vary по user ID в URL (/api/user/{id}/profile) с private, no-store или edge compute, проверяющий auth.max-age=3600 на segments — они immutable по сути.Vary: Accept-Encoding. CDN кэширует gzipped вариант, отдаёт его клиенту без gzip support — клиент видит garbage. Обязательно Vary: Accept-Encoding.CDN — это «да» для всего public web. Реальные случаи отказа:
«Внутри VPC можно без CDN» — обычно верно для service-to-service. «Public сайт без CDN» — техдолг, который вылезет на первой DDoS или скачке трафика.