Czym jest cache i jak pomaga przyspieszyć oprogramowanie?

22 września 2022
Jakub Rojek Jakub Rojek
Wygenerowane za pomocą: https://openai.com/dall-e-2/
Kategorie: Dla klientów, Podstawy IT, Web

Słowo "cache" jest bardzo często używane w rozmowach o oprogramowaniu i jest dość ciekawe. Nie tylko dlatego, że niesie ze sobą obietnicę poprawy wydajności, ale też dlatego, że... rzadko konkretnie mówi o tym, jak to będzie realizowane i z jakimi konsekwencjami się wiąże. To raczej takie słowo wytrych. Najczęściej pada stwierdzenie, że "jak będzie źle, to coś scache'ujemy", ale jest to dość mętne dla klienta, a w dodatku nie stanowi remedium na wszystko. Co nie zmienia faktu, że to bardzo użyteczna technika, gdy zostanie odpowiednio wykorzystana.

Zastanówmy się zatem, co w ogóle kryje się pod tym pojęciem oraz na jakich poziomach można z niego skorzystać. Jest to bowiem dość szerokie spektrum działań o podobnym charakterze, które może być aplikowane w wielu miejscach oprogramowania i to najczęściej niezależnie. A może nie być w ogóle użyte z uwagi na jego wady. Jakie? Czytajcie dalej.

Czym w ogóle jest cache?

Jest to inne określenie pamięci podręcznej, a więc miejsca, w którym zapisywane są już raz wyliczone lub pobrane informacje, aby później użyć ich ponownie i nie wykonywać przy tym drugi (i kolejny) raz czasochłonnych operacji. Dodatkowo, obecność takiego magazynu angażuje nie serwer (a więc niepotrzebne jest kosztowne żądanie do zdalnej maszyny), ale dysk, który jest "na miejscu". W ten sposób można też nawet zwiększyć przepustowość serwera, który jest wykorzystywany tylko wtedy, kiedy jest to konieczne.

Przykładowo, wyobraźcie sobie, że często wyświetlacie listę dokumentów, która ładuje się przez 10 sekund. Byłoby to znośne, gdyby taka lista była potrzebna raz na dzień, ale ten czas oczekiwania staje się kulą u nogi, gdy regularnie pracujecie w danym systemie i musicie niemalże co minutę odświeżać zawartość takiego ekranu, aby możliwie szybko zareagować na pojawienie się nowej umowy. Cache może w tym pomóc - system pobierze listę w tradycyjny sposób tylko za pierwszym razem, a potem przez pewien czas (np. 5 minut) przy każdym odświeżaniu danych będzie korzystał z tej odpowiedzi. W efekcie tylko pierwsze wyświetlenie dokumentów zabierze 10 sekund, a kolejne - pojedyncze sekundy lub nawet ich ułamek.

Oczywiście, łatwo można dostrzec zaletę w postaci przyspieszenia aplikacji i to na wielu poziomach, gdyż lista dokumentów to tylko przykład. Ale widać też największą wadę - jeśli w ciągu tych 5 minut pojawi się jakaś nowa umowa, to system jej nie pokaże. Co więcej, dane w systemie mogą być niespójne, gdyż użytkownik może uzyskać powiadomienie mailowe o tym, że pojawił się nowy dokument w systemie, ale na liście on się nie znajdzie. W niektórych przypadkach jest to jak najbardziej nieszkodliwe (np. liczba polubień naszego tweeta na Twitterze), ale w innych - jak choćby przytoczonym przykładzie z umowami - już może stanowić niedogodność.

Dlatego podobnie, jak w przypadku omawianej już optymalizacji, wykorzystanie cache'owania powinno być dobrze przemyślane i możliwie jak najmniej uderzać w biznesowy aspekt systemu. Pamięć podręczna nie zastąpi też przyspieszenia aplikacji w zwykły sposób, a więc poprzez skrócenie pierwotnego czasu wykonywania określonych operacji. Natomiast może przydać się przy wspólnych, rzadko zmieniających się zbiorach danych lub w bardzo popularnych serwisach. Pomijając fakt, że tak naprawdę każdy z nas korzysta z cache'a nawet o tym nie wiedząc, ale o tym za chwilę.

Co można cache'ować?

Przykład przedstawiony powyżej to tylko namiastka tego, co można zapisywać w pamięci podręcznej i później odtwarzać. Tak, jak wspominałem, cache może działać na wielu poziomach. Oto uproszczony schemat, w jaki sposób wygląda korzystanie z aplikacji webowej przez użytkownika.

Uproszczony schemat korzystania z aplikacji webowej

To dość dobrze powinno nam zademonstrować miejsca, w których może działać cache'owanie, jeśli programista dobrze to wykorzystać.

Przeglądarka

Użytkownicy nie zawsze zdają sobie z tego sprawę, ale pobranie strony internetowej prawie nigdy nie ogranicza się do jednego pliku - osobno ściągane są m.in. pliki CSS, JS, obrazki oraz inne media. I właśnie te wszystkie pliki przeglądarki najczęściej same z siebie starają się zapisywać w pamięci podręcznej oraz z niej później korzystać. To dlatego zazwyczaj pierwsze wejście na daną stronę trwa ciut dłużej niż jej kolejne uruchomienie - mimo że treść (np. aktualności na stronie głównej) może się zmienić, to przeglądarka sama z siebie potrafi przywołać choćby pliki definiujące wygląd serwisu i wstawić je bez pytania serwera, skrając tym samym czas ładowania strony.

Programista może to wykorzystać, choć prawda jest taka, że najczęściej nie musi nic dodatkowo robić poza odpowiednim podziałem strony na pliki. Wręcz przeciwnie - zdarzają się sytuacje, w których cache'owanie przeglądarek jest wręcz za silne, szczególnie na stronach opartych o javascriptowe frameworki frontendowe. Dlatego tak samo, jak stosuje się sztuczki cache'ujące, tak można stosować triki zapobiegające temu procederowi, np. poprzez odpowiednie nagłówki w odpowiedziach HTTP, tagi META czy dodawanie parametrów do skryptów.

Aplikacja frontendowa

Blisko z poprzednim punktem związana jest także sama aplikacja kliencka, czyli taka, której pliki są pobierane i często cache'owane przez przeglądarkę (w przeciwieństwie do aplikacji serwerowej, która pozostaje po stronie - nie zgadniecie - serwera). Jednak poza "automatycznymi" technikami opisanymi wyżej, dodatkowo można wysłać do boju takie mechanizmy jak ciasteczka (cookies), local storage oraz session storage. Może nie do końca stosuje się to w samym cache'owaniu, ale choćby w ten sposób zapisywane są najczęściej np. ustawione filtry list, które potem mogą zostać odtworzone, aby użytkownik nie marnował czasu na ponowne skonfigurowanie wszystkich opcji. Jednak łatwo można wyobrazić sobie także wykorzystanie tych mechanizmów jako cache'a - jeśli aplikacja frontendowa pobierze jakieś dane, które chcemy "trzymać", to można wykorzystać do tego właśnie określony lokalny magazyn i po ponownym wejściu na daną podstronę, zamiast jeszcze raz wysyłać żądanie do serwera, skorzystać z zapisanych informacji. Wszystko zależy od sytuacji i potrzeb.

Serwer HTTP

Gdy żądanie dociera do serwera, przechodzi przez serwer HTTP, który zarządza aplikacjami znajdującymi się na maszynie. I tutaj również mamy możliwość konfiguracji w taki sposób, aby np. NGINX korzystał ze swojej pamięci podręcznej zamiast odwoływać się do plików strony. Pamiętajmy też o tym, że zarówno Apache, jak i NGINX pozwalają automatycznie dodawać pewne nagłówki do odpowiedzi, a co za tym idzie - również te, które dotyczą cache'owania. Jedną z oznak dobrego systemu odtwarzania lokalnie zapisanych danych jest to, że dzieje się to możliwie automatycznie, bez dodatkowych operacji, które przecież również zajmują czas.

A jakby tego było mało, istnieją takie rozwiązania jak Varnish Cache, które mogą być bardzo pomocne w szczególnie obciążonych serwisach, natomiast wymagają już więcej pracy, aby je umiejętnie wprowadzić do środowiska aplikacji.

Aplikacja backendowa i baza danych

Czyli wielokrotnie wspominana już aplikacja serwerowa. Połączyłem tę sekcję z bazą danych, gdyż w tych aspektach programiści mają chyba największe pole do popisu, a mocno wiążą się one ze sobą. Można bowiem:

  • cache'ować same dane aplikacji, a więc pregenerowane pliki strony, jeśli to tzw. monolit,
  • zarządzać wywołaniami zarówno z właściwej bazy danych, jak i magazynu, gdzie będą przechowywane niedawno pobrane dane (Memcached lub Redis),
  • odkładać obliczone pośrednie dane w bazie danych (przykładowo, jeśli bardzo często wywołujemy jedno podzapytanie, to może zamiast tego warto wywoływać je co jakiś czas i później wykorzystywać zapisany wynik).

Co prawda, nie wymieniłem wielu punktów, ale każdy z nich jest "bogaty" i daje wiele możliwości. A to właśnie na cache'owaniu wyników zapytań czy obliczeń (które mają miejsce na backendzie) najczęściej można najwięcej zyskać. Nie zawsze się to opłaca lub jest wręcz mile widziane, ale warto choćby myśleć o tym, jak przystosować poszczególne partie kodu na przyszłość do tej formy optymalizacji.

Kiedy stosować cache'owanie?

Poniekąd już o tym wspomnieliśmy, ale warto zebrać to w jednym miejscu. Przede wszystkim, cache'owaniu warto się przyglądać w przypadku:

  • operacji, których wynik rzadko się zmienia,
  • części kodu, które dla każdego użytkownika dadzą ten sam wynik,
  • długo trwających obliczeń,
  • operacji, które są wywoływane bardzo często,
  • danych, których aktualność nie jest krytyczna,
  • plików rzadko ulegających modyfikacji.

To na pewno nie jest pełna lista, ale drogowskaz, który może pomóc w choćby częściowej analizie naszego oprogramowania pod tym kątem. Natomiast warto również mieć na względzie to, że cache'owanie ma swoje konsekwencje i najczęściej prowadzi do niespójnych lub opóźnionych informacji. Dlatego tak ważne jest przemyślenie, co może ulegać odtwarzaniu z pamięci podręcznej, w jaki sposób oraz w jakim odcinku czasu. Pamiętajmy bowiem, że cache nie jest wieczny - możemy sterować tym, po jakim czasie aplikacja ponownie wywoła "pełne" przetwarzanie operacji, której wynik wcześniej został scache'owany. Warto też wspomnieć o tym, iż użycie pamięci podręcznej wiąże się ze zwiększonym zapotrzebowaniem na przestrzeń dyskową (i to zarówno u użytkownika, jak i na serwerze), ale w obecnych czasach jest to pomijalny nakład. Już bardziej należy zainteresować się tym, że cache to kolejny obszar, który może ulec cyberatakom.

Podsumowanie

Cache'owanie na pewno jest jednym z pewniejszych sposobów na poprawienie wydajności aplikacji i zazwyczaj łatwiejszym niż "prawdziwa" optymalizacja. Ale czy zawsze lepszym? To zależy od sytuacji i tego, co ma być w ten sposób potraktowane. Wprowadzenie tej koncepcji do oprogramowania zawsze wiąże się z pewnym kompromisem pomiędzy szybkością a spójnością danych, dlatego nie powinno być wprowadzane bezrefleksyjnie. Co nie zmienia faktu, że w wielu przypadkach "działa" automatycznie i daje wyjątkowo dobre rezultaty, bez których nie wyobrażamy sobie dzisiejszego przebywania w Internecie.

Pozdrawiam i dziękuję - Jakub Rojek.

Lubimy pisać, nawet bardzo, ale na co dzień tworzymy aplikacje webowe i mobilne. Sprawdź niektóre z wykonanych przez programów.

O autorze

Jakub Rojek

Główny programista i współwłaściciel Wilda Software, z wieloletnim doświadczeniem w tworzeniu i rozwoju oprogramowania, ale także w pisaniu tekstów na różnorakich blogach. Zaprawiony w boju analityk i architekt systemów IT. Jednocześnie absolwent Politechniki Poznańskiej i okazjonalny prowadzący zajęcia na tej uczelni. W wolnych chwilach oddaje się graniu w gry wideo (głównie w karcianki), czytaniu książek, oglądaniu futbolu amerykańskiego i e-sportu, odkrywaniu cięższej muzyki oraz wytykaniu innym błędów językowych.

Jakub Rojek