Co CDN robi z żądaniem użytkownika

Użytkownik w Tokio, który otwiera stronę hostowaną na serwerze w Wirginii, płaci za ten dystans dwa razy: raz za dotarcie żądania, raz za powrót odpowiedzi. Światłowód działa z ułamkiem prędkości światła, a sama ta podróż kosztuje już 150-200ms w obie strony, zanim serwer źródłowy w ogóle zacznie coś robić. Dołóż zapytanie do bazy danych i jeden czy dwa wolne przeskoki sieciowe, a strona, która tuż obok ładuje się natychmiast, po drugiej stronie globu wydaje się ociężała.

CDN, czyli content delivery network, rozwiązuje ten problem dla treści, które da się skopiować: obrazów, CSS, paczek JavaScript, fragmentów wideo, czasem całych stron HTML. Sieć serwerów rozstawionych blisko użytkowników trzyma kopie tej treści i odpowiada bezpośrednio, więc większość żądań nigdy nie przekracza oceanu. Serwer źródłowy nadal trzyma wersję autorytatywną. CDN stoi przed nim i przechwytuje tyle żądań, ile potrafi obsłużyć z kopii.

Jak wybierany jest PoP, który odpowie na żądanie

Operator CDN uruchamia serwery w wielu lokalizacjach fizycznych, zwykle nazywanych Points of Presence, czyli PoP: centra danych w dużych miastach albo blisko nich, każde trzyma fragment cache’owanej treści i szybką ścieżkę z powrotem do serwera źródłowego dla wszystkiego innego. O tym, który PoP obsłuży dane żądanie, decydują dwa mechanizmy, a duże sieci CDN korzystają z obu naraz.

Routing oparty na DNS rozwiązuje tę samą nazwę hosta na różne adresy IP, zależnie od tego, skąd przychodzi zapytanie. Zapytanie DNS o assets.example.com z resolvera we Frankfurcie dostaje w odpowiedzi adres IP PoP-u we Frankfurcie; to samo zapytanie z São Paulo dostaje zupełnie inny adres.

Anycast idzie o krok dalej. Wiele PoP-ów ogłasza ten sam adres IP przez BGP, a zwykły routing internetowy decyduje, do którego z nich trafi dany pakiet, na podstawie topologii sieci, a nie geografii. CDN nigdy nie podejmuje tej decyzji. Failover też jest szybszy: gdy PoP odpada z sieci, routery przestają wysyłać do niego ruch, bez żadnej zmiany DNS do rozgłoszenia.

1
2
3
4
5
6
klient (Tokio)  ---DNS/anycast--->  najbliższy PoP  ---cache hit--->  odpowiedź
                                          |
                                    cache miss
                                          |
                                          v
                                serwer źródłowy (Wirginia)

Tak czy inaczej, klient nigdy nie rozmawia bezpośrednio z serwerem źródłowym o treść, którą da się cache’ować. Rozmawia z tym PoP-em, który wybrała warstwa routingu, a ten PoP albo już ma treść, albo pobiera ją raz i zapamiętuje. Każdy PoP robi robotę reverse proxy stojącego przed serwerem źródłowym: kończy połączenie klienta i decyduje, co dalej, tyle że z podpiętym cache i tysiącami kopii działających naraz na całym świecie.

Cache hit i cache miss: jak sprawdzić, co dostałeś

Pierwsze żądanie o dany URL przez konkretny PoP to zawsze cache miss. PoP nie ma niczego zapisanego, więc pobiera treść z serwera źródłowego, zapisuje kopię zgodnie z regułami z odpowiedzi i ją zwraca. Każde kolejne żądanie o ten sam URL, od dowolnego klienta skierowanego do tego samego PoP-u, to już hit: obsłużony z pamięci PoP-u, bez udziału serwera źródłowego, dopóki kopia nie wygaśnie albo nie zostanie wyczyszczona.

Jedna komenda mówi ci, co dostałeś:

1
curl -I https://assets.example.com/app.css

Odpowiedź z cache nosi nagłówek konkretnego dostawcy, który nazywa wynik. Cloudflare dodaje CF-Cache-Status z wartościami takimi jak HIT, MISS, EXPIRED (TTL się skończył, więc PoP pobrał treść na nowo), REVALIDATED (serwer źródłowy potwierdził, że stara kopia wciąż jest aktualna) albo DYNAMIC (CDN uznał, że ta odpowiedź w ogóle nie nadaje się do cache’owania). Odpowiednik u Fastly to X-Cache: HIT albo X-Cache: MISS. Uruchom to samo żądanie dwa razy na cache’owalnym URL-u, a drugie powinno zmienić się z MISS na HIT.

1
2
3
4
5
HTTP/2 200
cache-control: public, max-age=3600
cf-cache-status: HIT
age: 412
content-type: text/css

age mówi, ile sekund temu PoP pobrał tę kopię z serwera źródłowego. Tutaj to 412 sekund w ramach TTL wynoszącego 3600 sekund, więc kopii zostało niemal godzina, zanim kolejne żądanie wywoła świeże pobranie.

Jak nagłówek Cache-Control mówi CDN, co cache’ować

CDN nie zgaduje, co cache’ować. Mówi mu to serwer źródłowy, przez nagłówek Cache-Control w każdej odpowiedzi.

DyrektywaCo robi
max-age=Nile sekund dowolny cache (przeglądarka albo CDN) może trzymać odpowiedź
s-maxage=Njak długo shared cache (CDN) może ją trzymać — nadpisuje max-age konkretnie dla CDN, przeglądarki nadal trzymają się max-age
publicjawnie zezwala na cache’owanie nawet dla odpowiedzi, które inaczej wyglądałyby na prywatne (np. z nagłówkiem Authorization)
privatecache’ować może tylko przeglądarka; CDN nie może
no-cachecache może przechowywać odpowiedź, ale musi ją zrewalidować u serwera źródłowego przed ponownym zwróceniem
no-storenigdy nie cache’uj tego nigdzie, ani w przeglądarce, ani w CDN

Dla statycznego zasobu z nazwą pliku zawierającą hash (app.a3f9c1.css), typowe ustawienie jest agresywne: Cache-Control: public, max-age=31536000, immutable. Nazwa pliku zmienia się za każdym razem, gdy zmienia się treść, więc rok jest bezpieczny. Nieaktualna kopia app.a3f9c1.css to sprzeczność sama w sobie, bo ta dokładna nazwa zawsze wskazuje tylko na jedną wersję pliku.

Dla HTML-a albo odpowiedzi API, którą chcesz cache’ować, ale świeższą, Cache-Control: public, s-maxage=60, max-age=0 trzyma ją w CDN przez minutę, podczas gdy przeglądarki rewalidują za każdym razem. To ten sam instynkt krótkiego TTL i wzorca cache-aside, który stoi za działaniem cache w Redis, tylko wymuszony przez nagłówek HTTP zamiast kodu aplikacji.

Pomyl się w drugą stronę, a zaczniesz wyciekać dane. Serwowanie strony przypisanej do konkretnego użytkownika z Cache-Control: public, max-age=3600 i bez Vary na ciasteczku sesji oznacza, że CDN może podać cache’owaną stronę użytkownika A użytkownikowi B.

Jak wyczyścić nieaktualny cache w CDN

Czasem TTL to za mało: wypuściłeś poprawkę i nie możesz czekać godzinę, aż dotrze do wszystkich. Każdy CDN udostępnia do tego API do czyszczenia cache albo odpowiednią akcję w panelu. Mówisz mu, że dany URL, albo cała strefa, jest teraz nieważna, a kolejne żądanie zostaje potraktowane jak cache miss, bez względu na to, ile TTL zostało.

Tańszy nawyk to w ogóle nie potrzebować czyszczenia. Wersjonuj nazwę pliku hashem treści albo numerem w query stringu, żeby każdy deploy był nowym URL-em z własnym wpisem w cache, a stara kopia pod starym adresem przestała być w ogóle wywoływana. Czyszczenie zostaw na sytuacje, których nie da się obejść: incydent, żądanie usunięcia treści z powodów prawnych, cache zatruty przez źle skonfigurowany nagłówek Vary.

Kiedy CDN nie pomaga i ile to kosztuje

Spersonalizowanych odpowiedzi w praktyce nie da się cache’ować. Panel po zalogowaniu, strona koszyka, cokolwiek przypisanego do sesji — kolejne żądanie i tak zechce innej treści. Skierowanie CDN na takie endpointy nie daje nic poza złudzeniem, że dodałeś cache, a przy błędnych regułach realnie ryzykujesz, że prywatna odpowiedź jednego użytkownika trafi do drugiego.

CDN dokłada też warstwę, przez którą trzeba przebijać się przy debugowaniu. “Lokalnie działa, na produkcji nie” czasem znaczy, że kod jest w porządku, a PoP serwuje odpowiedź zcache’owaną sprzed twojej poprawki; żądanie nie dotrze do serwera źródłowego, dopóki TTL nie wygaśnie albo nie wyczyścisz cache.

Kształt ruchu też ma znaczenie. Wewnętrzne narzędzie albo usługa regionalna bez międzynarodowych odbiorców płaci za PoP-y, których nigdy nie użyje, i odzyskuje opóźnienie, którego nigdy nie miała. CDN rozwiązuje dwa konkretne problemy: podróż w obie strony do odległych użytkowników i powtarzalne obciążenie serwera źródłowego. Jeśli żaden z nich cię nie dotyczy, to infrastruktura bez zadania do wykonania.

Jak sprawdzić, czy CDN już działa przed twoim serwerem

Uruchom curl -I na statycznym zasobie strony, którą utrzymujesz. Jeśli odpowiedź niesie cache-control, nagłówek age albo status konkretnego dostawcy jak CF-Cache-Status czy X-Cache, coś już cache’uje przed twoim serwerem źródłowym, niezależnie od tego, czy skonfigurowałeś to świadomie, czy dorzucił to twój hosting. Jeśli tych nagłówków brakuje na zasobach, o które proszą użytkownicy z więcej niż jednego regionu, to konkretny powód, żeby dodać CDN: Cloudflare, AWS CloudFront i Fastly stawiają domenę za darmo albo w modelu rozliczenia za użycie w niecałą godzinę, a kod po stronie serwera źródłowego nie musi się zmienić.

Powiązane artykuły