Gdzie żyje cache: ile kopii jednej odpowiedzi istnieje naraz

Ta sama odpowiedź serwera może w jednej chwili leżeć w kilku cache'ach naraz - pamięci aplikacji, Service Workerze, cache HTTP, na CDN - a całą stronę trzyma jeszcze bfcache. Każdy ma innego właściciela i co innego go czyści, więc „wyczyść cache" trafia w jeden z nich, czasem w żaden. Zostaje pytanie, od którego zaczyna się każde takie debugowanie: która warstwa trzyma starą kopię i co ją unieważnia.
Wdrażasz poprawkę i robisz twardy refresh (Ctrl+Shift+R, na Macu Cmd+Shift+R), żeby przeglądarka pobrała stronę od nowa - a tam dalej stara wersja. Pierwsza myśl: „wyczyść cache” - i zwykle pomaga. Ale „wyczyść cache” to nie jedna czynność, tylko pytanie: który? Bo w chwili, gdy patrzysz na tę nieaktualną stronę, ta sama odpowiedź serwera leży w kilku schowkach naraz, a „wyczyść” trafia w jeden z nich, czasem w żaden.
Te schowki różni jedna rzecz, teraz dla nas najważniejsza: co usuwa z nich starą kopię. Z jednego znika sama po zwykłym odświeżeniu, z drugiego nie wyczyści jej żaden przycisk w przeglądarce, a do trzeciego z przeglądarki w ogóle nie sięgniesz. Więc nie czyśćmy wszystkiego na oślep. Potraktujmy to jak szukanie winnego: każdy schowek zdradza się czym innym, a kilka prostych pytań eliminuje podejrzanych jeden po drugim.
#Pierwsze pytanie: czy pomaga zwykłe odświeżenie?
Zacznij od najtańszego ruchu: przeładuj stronę zwykłym odświeżeniem (F5). Jeśli stara wersja zniknęła, winny siedział w samej stronie - w pamięci aplikacji.
To pierwszy „cache” - choć tak naprawdę żaden, bo nie jest wbudowanym mechanizmem przeglądarki ani serwera, tylko twoim własnym kodem. Zmienna w module, która trzyma raz pobraną listę, dopóki strona jest otwarta. Wspólny store ze stanem aplikacji (Redux, Zustand), do którego zapisałeś odpowiedź, żeby drugi komponent nie pobierał jej po raz drugi. Cache zapytań w bibliotece do pobierania danych (React Query, SWR) - pod spodem to samo: wynik zapamiętany pod kluczem i oddawany bez ruszania sieci. Wszystko to żyje w pamięci otwartej strony i ginie razem z nią, więc pełne przeładowanie zmiata je do zera, zanim cokolwiek niżej zdąży się odezwać. (Wyjątek: jeśli świadomie zapisujesz taki cache na trwałe, do localStorage albo IndexedDB, przeżyje odświeżenie - wtedy najpierw wyklucz tę warstwę.) Dlatego tyle błędów „znika po odświeżeniu” - i dlatego o tej warstwie najłatwiej zapomnieć, bo nikt nie nazywa swojej zmiennej cache’em, dopóki nie odda starych danych.
#Odświeżenie nie pomogło? Schodzimy głębiej
Jeśli stara wersja trzyma się mimo odświeżenia, kopia leży głębiej niż pamięć strony - na dysku albo w sieci. Tu zaczyna się druga grupa podejrzanych, każdy z inną kryjówką; idziemy po kolei, od tego, co najbliżej twojego kodu, w stronę serwera.
Najpierw Service Worker. Jeśli go napisałeś, przechwytuje żądanie przed całą resztą i może oddawać pliki ze swojego Cache API bez końca, bo ten schowek czyści wyłącznie twój kod - sprzątanie w activate i podbicie wersji w nazwie schowka. I tu kryje się pułapka, na której łapie się najwięcej osób: „wyczyść cache” w przeglądarce go nie rusza, a twardy refresh co najwyżej omija go na jedno wejście (Chrome ładuje wtedy stronę tak, jakby workera nie było) - ale go nie czyści, więc przy następnym zwykłym wejściu znów dostajesz stare dane. Zajrzyj w Application → Service Workers w DevTools; jeśli wisi tam stary worker, to on jest winny, a nie żaden nagłówek.
Nie ma Service Workera albo to nie on? Zostaje cache HTTP, ten sterowany nagłówkami serwera. Wbrew nazwie to nie jeden schowek, a dwa: świeżo użyte odpowiedzi siedzą w memory cache (w RAM, na czas życia karty), reszta na dysku w disk cache. W zakładce Network rozpoznasz je po dopisku (memory cache) albo (disk cache) przy pliku. Tu starą kopię usuwa ten sam mechanizm, co w pierwszej części - świeżość i rewalidacja: kopia wygasa, przeglądarka dopytuje serwer, a jeśli jest nowa wersja - pobiera ją. Twardy refresh działa tu bezpośrednio: wysyła żądania z nagłówkiem, który każe pominąć obie warstwy HTTP i pobrać wszystko od nowa. Dlatego u ciebie pomaga, a na cudzym komputerze nie - bo użytkownik tego skrótu nie wciśnie.
A jeśli nawet twardy refresh nie pomaga? Wtedy stara kopia jest już poza twoją maszyną. Pierwszy przystanek w sieci to nie origin (twój serwer źródłowy), tylko CDN - cache współdzielony, który trzyma jedną kopię dla wielu użytkowników, z własnym czasem życia (ustawia go nagłówek s-maxage). Tej kopii nie wyczyścisz z przeglądarki żadnym skrótem; znika, gdy wygaśnie jej czas życia albo gdy ktoś ręcznie ją wyczyści (purge) na samym CDN. Dlatego wdrożenie często kończy się automatycznym purge - bez tego świeży plik z originu nie przebije się przez CDN, który dalej oddaje stary. Dotyczy to głównie HTML i odpowiedzi API; pliki z hashem w nazwie (z części pierwszej) purge’a nie potrzebują, bo nowa treść to nowy URL, którego CDN i tak jeszcze nie ma.
#Podejrzany spoza listy: bfcache
Jest jeszcze jeden schowek, ale nie pasuje do powyższego śledztwa, bo nie leży na drodze żadnego żądania. Wszystkie dotychczasowe odpowiadały na pytanie „czy mam ten plik?”. Ten odpowiada na zupełnie inne: „czy mam całą tę stronę?”.
To bfcache (back/forward cache). Gdy przechodzisz na inną stronę (klikasz link albo idziesz dalej), przeglądarka nie wyrzuca poprzedniej od razu. Zamraża ją w całości - drzewo DOM, stan JavaScriptu, pozycję scrolla - i odkłada na bok. Klikasz „wstecz” i strona wraca w ułamku sekundy, dokładnie taka, jaką zostawiłeś, bo nic nie powstaje od nowa. Żadne żądanie nie leci, bo to nie kopia pliku, tylko zamrożona, żywa strona. Objaw bywa tu wręcz odwrotny niż w reszcie posta: nie „stara treść mimo odświeżenia”, a „po cofnięciu widzę stan, którego już nie powinno być” - stary formularz, nieodświeżone saldo.
To jedyny schowek, którego nie konfigurujesz „pod siebie”. Nie wybierasz, co i na jak długo w nim ląduje - strona albo się kwalifikuje, albo nie. Twoja rola to nie zepsuć go przypadkiem.
#Mapa: kto trzyma kopię i co ją usuwa
Całe śledztwo w pięciu wierszach - dla każdego schowka to, co go opróżnia:
| Schowek | Co trzyma | Co go opróżnia |
|---|---|---|
| pamięć aplikacji | dane w stanie aplikacji | przeładowanie strony albo twój kod |
| Service Worker | pary żądanie/odpowiedź | sprzątanie w activate + nowa wersja schowka |
| cache HTTP (RAM + dysk) | odpowiedzi z nagłówkami | wygaśnięcie max-age/ETag; twardy refresh u ciebie |
| CDN | publiczne odpowiedzi | s-maxage albo ręczny purge |
| bfcache | snapshot całej strony | no-store, unload, kolejna nawigacja |
Wzrok leci do ostatniej kolumny, bo to ona jest sednem. Każdy wiersz czyści się inaczej i ma innego właściciela: nagłówki HTTP i CDN ustawia serwer, Service Workera i pamięć aplikacji - ty w kodzie, twardy refresh - użytkownik. „Wyczyść cache” w przeglądarce trafia tylko w część z nich i dlatego tak często nie pomaga.
Sprawdź to sam: ustaw, w której warstwie utknęła stara kopia, i zobacz, co naprawdę robi każda akcja - i czego „wyczyść cache” w ogóle nie dotyka.
Wybierz, gdzie siedzi stara kopia i co robisz, żeby się jej pozbyć. Zobacz, czy ta akcja w ogóle sięga tej warstwy.
Service Workera czyści wyłącznie twój kod: sprzątanie w activate i nowa wersja schowka. Twardy refresh omija go na jedno wejście, ale go nie czyści - przy następnym zwykłym wejściu znów oddaje stare.
bfcache stoi tu osobno - nie leży na drodze żadnego żądania, więc żadna z tych akcji go nie dotyczy.
#Od czego zacząć następnym razem
Nie zaczynaj od czyszczenia wszystkiego. Zacznij od pytań, które eliminują podejrzanych po kolei:
- Odśwież. Stara wersja zniknęła? To była pamięć aplikacji - twój stan, nie żaden nagłówek.
- Nadal stare dane? Zajrzyj w Application → Service Workers. Wisi stary worker? To on, a „wyczyść cache” go nie ruszy.
- Nie ma workera? Sprawdź w Network, czy plik leci z
disk cachei jaki maCache-Control. Tu rządzi świeżość, a twardy refresh ją obchodzi. - Nawet twardy refresh nie pomaga? Kopia jest poza tobą, na CDN - ruszy ją tylko purge po tamtej stronie.
- To nie „stare dane mimo odświeżenia”, a dziwny stan po „wstecz”? Wtedy to bfcache, nie cache pliku.
„Wyczyściłem cache, a dalej stare dane” to nie jeden problem, tylko pięciu różnych podejrzanych. Każdą z nich czyści się inaczej, więc nie chodzi o to, żeby wyczyścić wszystko, tylko żeby wiedzieć, która trzyma starą kopię.
Komentarze
Wczytywanie komentarzy…