Wolna strona internetowa nie zawsze potrzebuje kolejnej wtyczki optymalizacyjnej. Czasem problem znajduje się głębiej: w konfiguracji serwera, warstwie cache albo sposobie, w jaki ruch przechodzi między Nginxem, Apache i aplikacją.
W jednym z projektów uporządkowałem architekturę środowiska opartego na ISPConfig 3. Celem było dodanie Varnish Cache przed Apache, przy zachowaniu terminacji SSL w Nginxie oraz poprawnej obsługi stron opartych na WordPressie i Craft CMS.
W praktyce przepływ wygląda tak:
Przepływ ruchu HTTPS w środowisku ISPConfig 3: Internet → Nginx SSL → Varnish Cache → Apache → WordPress lub Craft CMS.
Infografika do artykułu technicznego na potacki.com. Pokazuje prosty przepływ ruchu w architekturze wykorzystującej Nginx, Varnish Cache, Apache oraz aplikację opartą na WordPressie lub Craft CMS. Nginx odpowiada za obsługę HTTPS i przekazanie żądania dalej. Varnish zwraca gotową odpowiedź z cache, jeśli jest dostępna. Apache obsługuje backend aplikacji, a CMS generuje treść, gdy odpowiedź nie została jeszcze zapisana w cache'u.
Każda warstwa ma inne zadanie.
Nginx przyjmuje ruch przychodzący po HTTPS, odpowiada za terminację SSL i przekazuje żądanie dalej. Dzięki temu Varnish nie musi samodzielnie obsługiwać certyfikatów.
Jeżeli strona została wcześniej wygenerowana i może być bezpiecznie przechowywana, Varnish zwraca gotową odpowiedź bez uruchamiania aplikacji.
W praktyce oznacza to mniej zapytań docierających do Apache, PHP i bazy danych.
Jeżeli Varnish nie ma jeszcze gotowej odpowiedzi albo strona nie powinna zostać zapisana w cache, żądanie trafia do Apache.
Dopiero wtedy uruchamiany jest backend aplikacji.
WordPress albo Craft CMS przygotowują odpowiedź HTML wtedy, gdy nie ma jej jeszcze w cache albo gdy dana strona musi pozostać dynamiczna.
Po wygenerowaniu publicznej strony odpowiedź może zostać ponownie zapisana i wykorzystana przy kolejnych wejściach.
Wtyczka CDN Cache & Preload dla Craft CMS potrafi rozgrzewać cache na podstawie mapy strony XML. Po wyczyszczeniu pamięci podręcznej pobiera kolejne adresy z sitemapy i odbudowuje gotowe odpowiedzi HTML w warstwie cache.
Dzięki temu pierwsza osoba odwiedzająca podstronę nie musi czekać, aż aplikacja ponownie wygeneruje całą odpowiedź.
Podobny mechanizm preloadu jest dostępny również w popularnych rozwiązaniach dla WordPressa, takich jak WP Rocket czy FlyingPress. Sama zasada jest prosta: zamiast czekać na przypadkowe wejścia użytkowników, system sam przechodzi po najważniejszych adresach i przygotowuje cache wcześniej.
Jeżeli przed serwerem działa dodatkowo Cloudflare, można dodać kolejną warstwę cache. Wymaga to jednak odpowiednich reguł, ponieważ Cloudflare domyślnie nie przechowuje stron HTML.
Po poprawnej konfiguracji gotowe odpowiedzi mogą być serwowane bliżej użytkownika, a część ruchu nie musi docierać do aplikacji, PHP ani bazy danych.
Taka architektura dobrze radzi sobie zarówno ze zwykłym ruchem, jak i z krótkimi skokami obciążenia. Nie wystarczy jednak włączyć kilka mechanizmów i uznać temat za zamknięty.
Cache powinien przyspieszać strony publiczne, ale nie może obejmować miejsc zależnych od sesji użytkownika, koszyka, formularzy ani panelu administracyjnego.
Trzeba ustalić, które adresy można bezpiecznie przechowywać, kiedy je odświeżać i w jaki sposób usuwać stare wersje po zmianie treści.
Zobacz plugin CDN Cache & Preload dla Craft CMS
Samo uruchomienie Varnisha, Cloudflare albo wtyczki cache nie oznacza jeszcze, że architektura działa poprawnie.
Najwięcej problemów pojawia się wtedy, gdy poszczególne warstwy nie wiedzą, kiedy zapisać odpowiedź, kiedy ją pominąć i kiedy usunąć starą wersję strony.
Pierwszy problem pojawia się po edycji wpisu, strony albo produktu. Jeżeli cache nie zostanie poprawnie wyczyszczony, użytkownik nadal może zobaczyć starą wersję treści.
W prostym środowisku wystarczy usunąć jeden zapisany wariant strony. Przy kilku warstwach sytuacja jest bardziej złożona. Stara odpowiedź może nadal znajdować się w Varnishu, Cloudflare albo lokalnym cache’u aplikacji.
Dlatego purge powinien obejmować nie tylko pojedynczy adres URL, ale też powiązane miejsca.
Po aktualizacji wpisu warto odświeżyć:
W bardziej rozbudowanych projektach lepiej myśleć o purge jako o procesie, a nie pojedynczym poleceniu.
Nie każda strona może zostać zapisana jako gotowy HTML. Problem pojawia się wtedy, gdy cache obejmie widoki zależne od użytkownika.
Dotyczy to między innymi:
Jeżeli taka strona trafi do cache’u, użytkownik może zobaczyć dane przygotowane dla innej sesji albo nieaktualny stan koszyka.
Dlatego reguły cache powinny jasno rozdzielać strony publiczne od miejsc dynamicznych.
Dobrze skonfigurowany system nie próbuje cache’ować wszystkiego. Przechowuje tylko to, co rzeczywiście można bezpiecznie współdzielić pomiędzy użytkownikami.
Częstym problemem są również cookies. Wiele konfiguracji omija cache, gdy tylko w żądaniu pojawi się określone ciasteczko.
To ma sens w przypadku zalogowanych użytkowników albo koszyka WooCommerce. Problem zaczyna się wtedy, gdy cache jest wyłączany również przez cookies analityczne, marketingowe albo techniczne, które nie wpływają na zawartość strony.
W efekcie Varnish zaczyna przekazywać dużą część ruchu dalej do Apache i PHP, mimo że użytkownicy otrzymują dokładnie tę samą treść.
Warto więc sprawdzić, które cookies rzeczywiście wymagają pominięcia cache, a które można bezpiecznie ignorować.
Im prostsze i bardziej świadome reguły, tym większa liczba odpowiedzi może być zwracana bez obciążania backendu.
Nagłówki HTTP decydują o tym, jak długo odpowiedź może być przechowywana i kto może z niej korzystać.
Błędne ustawienie Cache-Control potrafi całkowicie zablokować działanie cache albo zrobić coś odwrotnego: pozwolić na przechowywanie treści, które powinny pozostać prywatne.
Najważniejsze różnice dotyczą dyrektyw takich jak:
public
private
no-cache
no-store
max-age
s-maxage
Dla publicznych stron warto używać reguł, które pozwalają warstwie pośredniej przechowywać odpowiedź.
Dla panelu użytkownika, koszyka i formularzy potrzebne są znacznie ostrożniejsze ustawienia.
Nie warto kopiować tych samych nagłówków dla całej strony. Lepszy efekt daje rozdzielenie odpowiedzi publicznych, prywatnych i dynamicznych.
Największą korzyścią z Varnisha jest to, że gotowa odpowiedź może zostać zwrócona bez uruchamiania aplikacji.
Jeżeli jednak reguły cache są zbyt zachowawcze albo niespójne, każde żądanie nadal trafia do Apache, PHP i bazy danych.
Wtedy serwer wykonuje dokładnie tę samą pracę wiele razy, mimo że wynik mógłby zostać zapisany wcześniej.
Warto obserwować, ile odpowiedzi kończy się jako HIT, a ile jako MISS, PASS albo BYPASS.
HIT
odpowiedź została zwrócona z cache
MISS
brakuje gotowej wersji i backend musi ją wygenerować
PASS lub BYPASS
cache został świadomie pominięty
Pojedyncze pominięcia są normalne. Problem pojawia się wtedy, gdy większość publicznego ruchu nadal dochodzi do Apache i PHP.
Dobrze skonfigurowany cache nie polega na przechowywaniu jak największej liczby stron.
Publiczne strony powinny być szybko zwracane w gotowym HTML. Widoki dynamiczne muszą pozostać poza cache. Zmiana treści powinna uruchamiać purge i ponowne rozgrzanie najważniejszych adresów.
Cookies nie powinny niepotrzebnie wyłączać cache dla anonimowych użytkowników.
Dopiero takie podejście realnie ogranicza liczbę zapytań trafiających do Apache, PHP i bazy danych.
Najprostszy test polega na porównaniu kilku kolejnych odpowiedzi dla tego samego adresu URL.
Pierwsze wejście może wygenerować MISS, ponieważ strona nie znajduje się jeszcze w pamięci podręcznej. Kolejne powinny już zwracać HIT.
Warto też obserwować czas odpowiedzi, nagłówki HTTP oraz logi Varnisha.
Sam dobry wynik w narzędziu do testowania wydajności nie zawsze wystarcza. Cache powinien działać stabilnie również po aktualizacji treści, zmianie plików CSS i JavaScript oraz przy wejściach anonimowych użytkowników z różnymi cookies.
Najlepsza konfiguracja cache to nie ta, która przechowuje wszystko.
Najlepsza jest ta, która wie, co można bezpiecznie zapisać, kiedy to usunąć i jak ograniczyć pracę backendu bez ryzyka pokazania użytkownikowi niewłaściwej treści.
Zobacz case study: Varnish Cache dla ISPConfig 3 z Nginx SSL Termination