Jak konfigurować HTTP caching dla API

Jak konfigurować HTTP caching dla API

Ten artykuł opisuje praktyczne podejście do konfigurowania HTTP caching dla API — od podstawowych zasad po zaawansowane techniki stosowane z CDN i reverse proxy. Przedstawione wskazówki wyjaśniają, kiedy cache pomaga przyspieszyć odpowiedzi, jak poprawnie ustawić nagłówki i jakie pułapki unikać przy uwierzytelnianiu, dynamicznych treściach i invalidacji. Celem jest dostarczenie konkretnych reguł, które można zastosować w typowych scenariuszach projektowania API.

Podstawy i zasady działania cache w HTTP

Cache w kontekście HTTP pozwala przechowywać kopie odpowiedzi bliżej klienta (przeglądarka, CDN) lub pośredników (reverse proxy), co redukuje opóźnienia i obciążenie serwera źródłowego. Najważniejsza zasada to rozdzielenie odpowiedzi na te, które są bezpiecznie ponownie używalne, oraz na te, które wymagają każdorazowego odświeżenia. W praktyce to oznacza, że metody GET i HEAD są głównymi kandydatami do cache’owania, podczas gdy POST, PUT i DELETE zwykle nie są cache’owane bez dodatkowych mechanizmów.

Podstawowe pojęcia, które warto znać:

  • Cache-Control — główny nagłówek kontrolujący politykę pamięci podręcznej.
  • ETag — walidator zasobu wykorzystywany do warunkowych zapytań.
  • Last-Modified — alternatywny walidator oparty na dacie modyfikacji.
  • 304 Not Modified — odpowiedź wskazująca, że zawartość nie uległa zmianie i można użyć wersji w cache.

Konfiguracja nagłówków Cache-Control i inne kluczowe dyrektywy

Najważniejszym mechanizmem jest nagłówek Cache-Control. Powinien być ustawiany świadomie, bo determinuje zachowanie zarówno przeglądarek, jak i pośredników. Typowe dyrektywy:

  • max-age= — ile czasu odpowiedź jest świeża bez dodatkowej walidacji.
  • s-maxage — analogiczne do max-age, ale dotyczy wyłącznie współdzielonych cache (CDN, proxy). Przydatne, gdy chcemy różnych TTL dla klienta i CDN.
  • public — odpowiedź może być buforowana przez dowolny cache (w tym CDN).
  • private — odpowiedź powinna być trzymana tylko w cache użytkownika (np. przeglądarka), nie w cache współdzielonym.
  • no-store — nie przechowywać odpowiedzi na dysku; używane dla danych wrażliwych.
  • no-cache — cache może przechować odpowiedź, ale przed ponownym użyciem musi zweryfikować u źródła (warunkowe zapytanie).
  • must-revalidate — po wygaśnięciu odpowiedzi cache musi odnowić ją u źródła przed użyciem.
  • stale-while-revalidate i stale-if-error — rozszerzenia pozwalające zwracać przeterminowaną wersję podczas rewalidacji lub gdy źródło jest niedostępne.

Przykładowe polityki

Dobrym punktem wyjścia dla API, które zwraca rzadko zmieniające się zasoby, jest ustawienie:

  • Cache-Control: public, max-age=3600, s-maxage=3600

Dla danych spersonalizowanych:

  • Cache-Control: private, max-age=60

Dla bardzo wrażliwych informacji:

  • Cache-Control: no-store

Walidatory: ETag, Last-Modified i zapytania warunkowe

Walidatory pozwalają uniknąć pobierania pełnej treści, gdy zasób się nie zmienił. Mechanizmy te zmniejszają transfer danych i obciążenie serwera.

  • ETag — wartość unikalnie identyfikująca wersję zasobu. Serwer generuje ją (np. hash zawartości) i klient przekazuje w If-None-Match. Jeśli ETagy pasują, serwer zwraca 304.
  • Last-Modified — służy do porównania z If-Modified-Since. Jest prostszy, ale mniej precyzyjny niż ETag (problemy z dokładnością czasów).

W praktyce ETagy są bardziej wiarygodne dla dynamicznych treści. Pamiętaj o rozróżnieniu walidatorów silnych i słabych (weak ETag — prefiks W/), co ma znaczenie, gdy porównanie powinno uwzględniać drobne różnice typu formatowania.

Cache a autoryzacja i prywatne dane

Domyślnie odpowiedzi z nagłówkiem Authorization nie powinny być buforowane przez współdzielone cache, chyba że jawnie to pozwolimy (np. Cache-Control: public, s-maxage=…). Najbezpieczniejsze praktyki:

  • Nie cache’uj odpowiedzi zawierających dane wrażliwe bez silnej kontroli dostępu.
  • Używaj private dla treści specyficznych dla użytkownika.
  • Jeśli twoje API wymaga tokenów, rozważ mechanizm cache’owania po stronie CDN tylko dla wybranych endpointów i z krótkim TTL.

Strategie invalidacji i modelowania klucza cache

Invalidacja cache to często najtrudniejszy fragment projektowania. Dostępne podejścia:

  • Ustalanie krótkiego TTL (prostota kosztem częściej odświeżeń).
  • Wysyłanie zdarzeń invalidacji do CDN/reverse proxy (purge API). Skuteczne, gdy potrafisz wskazać precyzyjnie URL-e do usunięcia.
  • Wersjonowanie zasobów przez URL (np. /v1/resources/123?v=20260718) — przydatne, gdy chcesz zachować stary cache przy wprowadzaniu zmian w schemacie.
  • Cache key — decyduje, które atrybuty żądania wpływają na unikalność wpisu (metoda, host, ścieżka, query string, nagłówki). Precyzyjne modelowanie klucza pozwala uniknąć kolizji i nadmiarowego cache’owania.

Praktyczne reguły invalidacji

W większości systemów:

  • Stosuj purge dla konkretnych URL zamiast globalnego kasowania.
  • Utrzymuj mapę zależności: które zasoby wpływają na inne (np. zmiana zasobu A wymusza odświeżenie listy B).
  • W razie wątpliwości preferuj krótsze TTL przy krytycznych danych.

Wykorzystanie CDN i reverse proxy

CDN i reverse proxy (np. Varnish, Fastly, AWS CloudFront) mogą znacząco poprawić wydajność. Najważniejsze kwestie:

  • Rozróżnij TTL dla s-maxage (CDN) i max-age (przeglądarka). Pozwala to serwerowi przyznać inną żywotność treści na CDN niż u klienta końcowego.
  • Dostosuj Vary jeśli odpowiedź zależy od nagłówków (np. Accept-Language). Użycie Vary rozszerzy liczbę wpisów w cache, więc stosuj świadomie.
  • Używaj mechanizmów purge i surrogate-control dostępnych w CDN do precyzyjnej invalidacji.

Obsługa parametrów zapytania, cookie i nagłówków Vary

Zasady:

  • Query string — decyduje o kluczu cache. Niektóre CDNy traktują różne kombinacje parametrów jako ten sam wpis, jeśli nie skonfigurujesz inaczej. Znormalizuj kolejność parametrów i ignoruj nieistotne parametry.
  • Cookies — mogą unieważniać cache. Jeśli cookies nie zmieniają treści, usuń je z żądania kierowanego do cache lub uwzględnij tylko konkretne cookie w kluczu.
  • Vary — używaj tylko gdy odpowiedź zmienia się w zależności od konkretnego nagłówka (np. User-Agent lub Accept). Każdy dodatkowy nagłówek w Vary zwiększa liczbę wariantów w cache.

Specjalne przypadki: GraphQL, agregaty i fragmentacja

GraphQL i inne endpointy, które zwracają dane zależne od zapytań, stanowią wyzwanie:

  • Jeśli odpowiedź zależy od treści zapytania (np. różne pola w GraphQL), cache’owanie całej odpowiedzi per zapytanie może być kosztowne. Rozważ cache’owanie wyników pozycjonowanych do fragmentów lub użycie dedykowanego mechanizmu cache (dataloader, cache per resolver).
  • Stosuj apollo cache lub cache fragmentów na CDN, gdy zapytania są często powtarzane.

Monitorowanie, testowanie i metryki

Skuteczna polityka cache wymaga monitoringu i testów. Mierz:

  • Hit ratio — odsetek trafień w cache vs missów.
  • Średni czas odpowiedzi i przepustowość przy hitach/missach.
  • Częstość invalidacji i koszty związane z purge.

Warto posiadać testy integracyjne symulujące różne scenariusze (uwierzytelniony ruch, ruch anonimowy, aktualizacje danych) oraz testy obciążeniowe, by sprawdzić, czy polityki cache nie prowadzą do nieoczekiwanych zachowań.

Najlepsze praktyki i checklist

Podstawowa lista kontrolna przy konfiguracji cache:

  • Zidentyfikuj, które endpointy mogą być cache’owane (GET/HEAD) i które nie.
  • Ustal domyślną politykę Cache-Control i dostosuj ją per endpoint.
  • Wdroż ETag lub Last-Modified dla warunkowej rewalidacji.
  • Użyj s-maxage dla CDN i public/private dla kontroli współdzielonego cache.
  • Planuj mechanizmy invalidacji (purge, wersjonowanie URL, krótkie TTL).
  • Monitoruj hit ratio i optymalizuj klucze cache oraz Vary.
  • Bezpieczeństwo: nie cache’uj danych wrażliwych i uważaj na Authorization/Set-Cookie.

Wdrożenie krok po kroku

Prosty plan implementacji:

  • Krok 1: Audyt endpointów — które zwracają statyczne/dynamiczne/indywidualne dane.
  • Krok 2: Ustal polityki Cache-Control i walidatory (ETag/Last-Modified).
  • Krok 3: Wdróż konfigurację na serwerze oraz w CDN z różnymi s-maxage w razie potrzeby.
  • Krok 4: Przetestuj scenariusze: pierwszy request (miss), kolejny (hit), zmiana zasobu (purge + miss), warunkowe GET (304).
  • Krok 5: Monitoruj i optymalizuj na podstawie metryk.

Przykłady błędów do uniknięcia

Typowe pomyłki:

  • Brak walidatora (ETag/Last-Modified) przy krótkim max-age — powoduje pełne pobieranie przy każdym odświeżeniu.
  • Ustawienie public dla treści spersonalizowanych — ryzyko wycieku danych użytkowników.
  • Nieprzemyślane użycie Vary — prowadzi do eksplozji wpisów w cache i niskiego hit ratio.
  • Ignorowanie wpływu cookies i Authorization na cache key.