Poniższy artykuł rozpoczyna dwuczęściową serię, której celem jest przybliżenie idei cachowania stron, z wykorzystaniem narzędzia jakim jest Varnish, oraz sposobu na uzyskanie lepszych rezultatów, poprzez wdrożenie modelu bazującego na ESI (Edge Side Includes). Zacznijmy jednak od podstaw…

Cachowanie – podstawy

Wszystkim technicznym osobom, związanym z tworzeniem aplikacji webowych, znany jest zapewne bazowy model komunikacji klient – serwer. Za każdym razem, gdy klient odwiedza  dowolną stronę w ramach naszej aplikacji, jego żądanie o poszukiwaną zawartość trafia do serwera, który generuje odsyłaną odpowiedź. „Za każdym razem” jest tutaj kluczowym stwierdzeniem. Szybko możemy zauważyć, że każdy klient zobaczy, dla przykładu, dokładnie tą samą stronę główną, co dla serwera będzie oznaczało wykonanie tej samej pracy. Bardzo wiele razy… i niepotrzebnie.

varnish1
Odpowiedzią jest wprowadzenie do swojej architektury warstwy cachującej. Jej założenia realizuje  Varnish – reverse proxy pełniące rolę pośrednika w komunikacji między klientem a aplikacją. Jak każdy inny mechanizm cachujący ma on stosunkowo proste zadanie – przetrzymywać jak najwięcej zawartości w pamięci podręcznej, której odpytanie zawsze będzie dużo szybsze od odpytania źródła (w naszym przypadku – serwera aplikacyjnego).

Co zyskujemy wdrażające takie rozwiązanie? Aplikacje, które piszemy potrafią w ramach pojedynczej odsłony wykonywać dużo wymagających operacji – złożonych obliczeniowo lub pamięciowo czy mocno obciążających serwery bazodanowe. O ile w kontekście pojedynczego żądania ciężko mówić o zbyt dużym obciążeniu wykorzystywanych maszyn – jak by nie patrzeć moc przerobowa dzisiejszych serwerów pozwala na wiele – to wraz ze wzrostem użytkowników odwiedzających nasze serwisy problem zaczyna przybierać na wadze. Dzięki warstwie cachującej większość z tego ruchu w ogóle nie dotrze do naszej aplikacji!

Varnish – jak działa

Sercem Varnisha jest maszyna stanowa napisana w C. Jej model jest trochę bardziej rozbudowany od standardowego przepływu mechanizmów cachujących. Przepływ maszyny prezentuje zamieszczony poniżej graf. Jego postać jest stosunkowo ogólna i poszczególne elementy mogą odbiegać od obecnej wersji – przykładowo w chwili pisania artykułu nazwy dwóch stanów w wersji 4.0 uległy zmianie.

varnish2

Każdy ze stanów ma swoje zadanie i fabrycznie realizuje logikę zawartą w silniku Varnisha (przypomnijmy, że jest to de facto program C). Poszczególne akcje maszyny stanowej mogą być podsumowane następująco:

  • RECV – punkt wejścia – najważniejsza jest tutaj podejmowana decyzja o dalszej ścieżce, jaką obierze przetwarzane żądanie.
  • PIPE – pusty przebieg. Varnish nie bierze dalszego udziału w komunikacji, ograniczając się do przekazania żądania i odpowiedzi serwera aplikacyjnego.
  • PASS – podobnie jak poprzednia akcja z tą różnicą, że odpowiedź aplikacji poddana zostanie procesom, przez które przeszłaby zawartość wyciągnięta z pamięci podręcznej.
  • HASH – pierwszy etap „właściwego” przebiegu, w którym wykorzystywana jest pamięć podręczna. Następuje tutaj wyznaczenie klucza cache.
  • LOOKUP – przeszukanie pamięci podręcznej z pomocą wyznaczonego wcześniej klucza. Zależnie od rezultatu wybierana jest jedna z dwóch następnych akcji.
  • HIT – reakcja na odnalezienie treści w cache.
  • MISS – akcja podejmowana w sytuacji, gdy żądana treść nie zawiera się w cache lub jest nieaktualna.
  • FETCH – przekazanie żądania do serwera aplikacyjnego celem uzyskania najświeższej odpowiedzi.
  • DELIVERY – dostarczenie treści, wyciągniętej z pamięci podręcznej lub otrzymanej od aplikacji, do klienta.
  • ERROR – obsługa błędów.

Każde podsumowanie określa jedynie co powinno dziać się na poszczególnych etapach procesu oraz co dzieje się przy domyślnej konfiguracji. Twórcy Varnisha dają nam możliwość wprowadzenia własnej logiki poprzez pliki konfiguracyjne VCL.

VCL

Varnish Configuration Language to prosty język przygotowany na potrzeby samego Varnisha. Jego składnia nie odbiega od popularnych dzisiaj języków programowania natomiast wykaz jego składowych nie należy do najbardziej obszernych. W efekcie jest łatwy do przyswojenia i na tyle funkcjonalny aby spełnić swoje zadania.

VCL jest językiem przejściowym. W jego obrębie wykorzystywane są obiekty reprezentujące takie elementy komunikacji jak żądanie klienta, żądanie do serwera aplikacyjnego i jego odpowiedź, czy odpowiedź dla klienta i obiekt cache. Wprowadzając zmiany w tych obiektach mamy wpływ na przekazywane treści.

Kod VCL tłumaczony jest na kod C i ten dopiero, w postaci bibliotek, dołączany do procesów  obsługujących żądania. W ten sposób nasze funkcje zastępują domyślną logikę w poszczególnych stanach.

Domyślna logika

Dzięki domyślnym ustawieniom na poziomie Varnisha zatrzymują się wszystkie żądania oprócz:

  • typów innych niż GET i HEAD,
  • zawierających nagłówki autoryzujące,
  • zawierające ciasteczka.

Wbrew pozorom jest to duża część ruchu i w większości sytuacji może to wystarczyć. Serwer aplikacyjny odciążany jest bowiem od dostarczania treści statycznej takiej jak np. obrazki. Warto zastanowić się nad ostatnim punktem powyższej listy – ciasteczkami. Żądania zawierające ciasteczka nie podlegają cachowi co jest w pełni uzasadnione – obecność ciastka sesyjnego mówi nam, że mamy do czynienia z użytkownikiem, którego strona może być w pewnym stopniu spersonalizowana. W sklepach internetowych takim elementem jest przykładowo miniatura koszyka.

Należy jednak zastanowić się czy tak drobny, w porównaniu do całej treści, wycinek strony powinien zaważyć o całkowitej rezygnacji z dobrodziejstw cachowania? Otóż nie i jak się okazuje istnieje mechanizm stanowiący odpowiedź na ten problem. Mowa tutaj o Edge Side Includes, któremu poświęcony będzie następny artykuł.