Sama instalacja Redisa nie przyspiesza strony. Zysk pojawia się dopiero wtedy, gdy aplikacja rzeczywiście zapisuje w pamięci podręcznej wyniki kosztownych operacji, a konfiguracja hostingu nie ogranicza jej do symbolicznych 32–64 MB RAM. Podobnie jest z OPcache: może być aktywny, ale przy zbyt małej pamięci lub limicie plików będzie stale usuwał skompilowany kod i kompilował go ponownie.
Redis, Memcached i OPcache działają na różnych etapach obsługi żądania. OPcache przyspiesza wykonywanie kodu PHP. Redis i Memcached przechowują gotowe dane, na przykład wynik zapytania SQL, obiekt produktu albo fragment konfiguracji aplikacji. Nie są zamiennikami. W typowej aplikacji PHP najlepszy efekt daje OPcache połączony z jednym dobrze wdrożonym cache’em obiektowym, a nie uruchomienie wszystkich dostępnych mechanizmów bez kontroli.
Trzy warstwy cache’u i trzy różne źródła oszczędności
Żądanie do aplikacji PHP przechodzi przez kilka kosztownych etapów. Serwer musi uruchomić kod, pobrać dane, wykonać logikę biznesową i zbudować odpowiedź. Każdy rodzaj cache’u skraca inną część tej drogi.
OPcache zapisuje w pamięci współdzielonej skompilowany kod bajtowy PHP. Bez niego interpreter musi przy każdym żądaniu odczytywać pliki PHP, analizować ich składnię i kompilować je do postaci wykonywalnej. OPcache wykonuje tę pracę raz, a kolejne żądania korzystają z gotowego kodu.
To ma znaczenie zwłaszcza w rozbudowanych aplikacjach:
- WordPress z wieloma wtyczkami potrafi ładować kilka tysięcy plików PHP;
- WooCommerce uruchamia znacznie więcej kodu niż prosta strona firmowa;
- Laravel i Symfony korzystają z autoloadera, kontenera usług oraz licznych klas frameworka;
- systemy CRM i sklepy B2B często wykonują dziesiątki modułów przy jednym żądaniu.
OPcache nie przechowuje jednak wyników zapytań do bazy ani gotowych danych użytkownika. Jeżeli aplikacja za każdym razem pobiera te same ustawienia z MySQL, mechanizm nadal wykona zapytanie. Tę część pracy ogranicza cache obiektowy, realizowany najczęściej przez Redis albo Memcached.
Redis i Memcached przechowują dane w RAM, gdzie dostęp jest szybszy niż odczyt z dysku lub ponowne wykonanie zapytania SQL. Typowe zastosowania obejmują:
- wyniki powtarzalnych zapytań;
- konfigurację aplikacji;
- dane sesji;
- listy produktów i kategorii;
- wyniki obliczeń;
- tokeny i blokady;
- odpowiedzi z zewnętrznych API;
- fragmenty wygenerowanych widoków.
Trzecią warstwą jest cache całych stron, obsługiwany na przykład przez Nginx FastCGI Cache, LiteSpeed Cache, Varnish albo wtyczkę CMS. Jeżeli serwer zwróci gotowy dokument HTML bez uruchamiania PHP, oszczędność jest większa niż w przypadku samego Redisa. Ten mechanizm nie zawsze może jednak objąć koszyk, panel klienta, formularze z indywidualnymi tokenami czy treści zależne od zalogowanego użytkownika.
Dlatego kolejność wdrożenia ma znaczenie:
- OPcache powinien być aktywny praktycznie w każdej produkcyjnej aplikacji PHP.
- Cache pełnych stron należy zastosować tam, gdzie odpowiedź nie jest indywidualna.
- Redis lub Memcached warto wdrażać wtedy, gdy aplikacja wykonuje dużo powtarzalnych zapytań albo kosztownych obliczeń.
W praktyce najsłabszy efekt daje Redis uruchomiony na małej stronie z pięcioma podstronami, która jest już obsługiwana przez skuteczny cache HTML. Znacznie więcej zyskuje sklep WooCommerce, portal ogłoszeniowy albo panel SaaS, gdzie znacznej części odpowiedzi nie można przechować jako jednej statycznej strony.
Redis czy Memcached: wybór zależy od danych, nie od popularności
Memcached jest prostym magazynem klucz–wartość. Dane trafiają do pamięci, otrzymują czas życia i mogą zostać usunięte, gdy serwer potrzebuje miejsca. Nie należy traktować go jako trwałej bazy. Restart usługi oznacza utratę zawartości cache’u, co jest normalnym zachowaniem, a nie awarią.
Jego zaletą jest prostota. Dobrze sprawdza się, gdy aplikacja potrzebuje tylko operacji typu:
- zapisz wartość pod kluczem;
- odczytaj wartość;
- usuń wartość;
- ustaw termin wygaśnięcia.
Memcached wykorzystuje podział pamięci na klasy elementów o określonych rozmiarach. Przy nietypowym rozkładzie danych może dojść do sytuacji, w której jedna klasa jest przepełniona i usuwa aktywne wpisy, chociaż w innych klasach nadal pozostaje wolna pamięć. To jedna z jego mniej oczywistych niedogodności. Sam wykres całkowitego zużycia RAM nie wystarcza — trzeba obserwować liczbę usunięć oraz statystyki poszczególnych klas.
Redis jest bardziej rozbudowany. Oprócz zwykłych ciągów znaków obsługuje między innymi hashe, listy, zbiory, zbiory uporządkowane, liczniki i strumienie. Może służyć nie tylko jako cache, ale również jako magazyn sesji, kolejka, system blokad lub mechanizm limitowania liczby żądań.
Redis oferuje także opcjonalny zapis danych na dysk. Nie oznacza to jednak, że cache musi być trwały. W instalacji przeznaczonej wyłącznie do buforowania można ograniczyć lub wyłączyć mechanizmy trwałości, jeżeli po restarcie aplikacja potrafi bezpiecznie odbudować dane. Pozwala to zmniejszyć liczbę operacji dyskowych, ale wymaga jednoznacznego rozdzielenia cache’u od informacji, których utrata byłaby problemem.
Przydział pamięci nie powinien obejmować całego RAM serwera. Redis zużywa pamięć nie tylko na wartości, ale też na klucze, metadane, struktury wewnętrzne, połączenia oraz procesy związane z zapisem danych. Na własnym VPS bezpieczniej pozostawić zapas dla systemu, PHP-FPM, serwera WWW i bazy danych.
Przykładowo na serwerze z 4 GB RAM przydzielenie Redisowi 3 GB zwykle jest błędem. MySQL i procesy PHP zaczną konkurować o pozostałą pamięć, a system może uruchomić swap lub zakończyć proces przez mechanizm OOM. W niewielkiej aplikacji rozsądny punkt startowy to często 128–256 MB, w średnim sklepie 256–512 MB, a większe limity powinny wynikać ze statystyk, nie z założenia, że więcej zawsze oznacza szybciej.
Po osiągnięciu limitu Redis stosuje wybraną politykę usuwania kluczy. Najczęściej spotykane warianty to:
- allkeys-lru — usuwa rzadziej używane klucze z całego zbioru;
- allkeys-lfu — preferuje usuwanie kluczy używanych najrzadziej;
- volatile-lru lub volatile-lfu — obejmuje tylko klucze z ustawionym terminem wygaśnięcia;
- volatile-ttl — usuwa klucze, którym pozostało najmniej czasu;
- noeviction — nie usuwa danych i zwraca błąd po wyczerpaniu pamięci.
Dla typowego cache’u aplikacyjnego rozsądnym punktem startowym jest allkeys-lru albo allkeys-lfu. Polityka noeviction ma sens, gdy odrzucenie zapisu jest bezpieczniejsze niż automatyczne usunięcie danych, ale dla zwykłego cache’u często kończy się błędami w aplikacji.
Redis jest zwykle lepszym wyborem, gdy potrzebne są:
- sesje użytkowników;
- blokady rozproszone;
- liczniki i limity zapytań;
- kolejki;
- kilka rodzajów danych;
- atomowe operacje na wartościach;
- możliwość dokładniejszego sterowania usuwaniem kluczy.
Memcached pozostaje sensowny, gdy cache ma być wyłącznie prostym, ulotnym magazynem wartości. Nie daje jednak automatycznej przewagi szybkości, którą użytkownik zauważy bez pomiarów. W wielu aplikacjach różnica między tymi mechanizmami jest mniejsza niż różnica między dobrze i źle zaprojektowanym kluczem cache’u.
Istotny jest także hosting. W ofertach współdzielonych hasło „Redis dostępny” może oznaczać współdzieloną instancję, limit kilkudziesięciu megabajtów, ograniczoną liczbę połączeń albo brak dostępu do konfiguracji. Przed zakupem trzeba sprawdzić:
- czy instancja jest prywatna dla konta;
- jaki jest limit pamięci;
- jaka polityka eviction obowiązuje;
- ile połączeń może otworzyć aplikacja;
- czy połączenie odbywa się przez lokalny socket, czy TCP;
- czy hosting udostępnia statystyki trafień, błędów i usunięć;
- czy Redis jest objęty automatycznym restartem i monitoringiem.
Lokalny socket Unix zwykle ogranicza narzut komunikacji, gdy aplikacja i Redis pracują na tej samej maszynie. Nie naprawi jednak aplikacji, która tworzy setki niepotrzebnych kluczy albo ustawia nieprzemyślane TTL.
Konfiguracja OPcache i cache’u obiektowego bez zgadywania
W OPcache najczęściej psują wydajność trzy rzeczy: za mało pamięci, zbyt niski limit plików oraz niewłaściwa obsługa zmian kodu.
Parametr opcache.memory_consumption określa wielkość pamięci przeznaczonej na skompilowany kod. Dla małej strony PHP punktem startowym może być 64–128 MB. WordPress z rozbudowanym zestawem wtyczek, WooCommerce albo aplikacja frameworkowa często wymaga 128–256 MB. Duże wdrożenia mogą potrzebować więcej, ale ustawienie 512 MB bez sprawdzenia wykorzystania jest tylko rezerwacją pamięci, której serwer może potrzebować gdzie indziej.
Parametr opcache.max_accelerated_files powinien przewyższać rzeczywistą liczbę plików PHP używanych przez aplikację. Przy zbyt niskiej wartości część kodu nie zostanie objęta cache’em. Dla prostych projektów wystarcza kilka tysięcy wpisów. W rozbudowanych instalacjach bezpieczniejszym punktem startowym jest 10 000–20 000, a decyzję należy potwierdzić statystykami OPcache.
opcache.validate_timestamps odpowiada za sprawdzanie, czy pliki PHP zostały zmienione. Gdy jest włączony, OPcache okresowo porównuje znaczniki czasu. Częstotliwość określa opcache.revalidate_freq.
Na hostingu współdzielonym i w projektach aktualizowanych ręcznie bezpieczniej pozostawić walidację włączoną. Wyłączenie jej zmniejsza liczbę operacji sprawdzających, ale po wdrożeniu nowej wersji kodu wymagane jest kontrolowane zresetowanie OPcache albo restart procesu PHP-FPM. Bez tego aplikacja może wykonywać starszą wersję kodu mimo obecności nowych plików na dysku.
Automatyczny deployment powinien więc zawierać kolejno:
- wdrożenie kompletu nowych plików;
- wykonanie migracji w odpowiednim momencie procesu;
- wyczyszczenie cache’u aplikacyjnego;
- invalidację lub reset OPcache;
- test żądania kontrolnego;
- sprawdzenie logów PHP-FPM i aplikacji.
Resetowanie cache’u przed zakończeniem kopiowania plików jest ryzykowne. Pierwsze żądania mogą skompilować niepełny zestaw kodu albo trafić na niespójność między starą i nową wersją.
W Redisie i Memcached kluczowe są TTL oraz sposób unieważniania danych. Jeden globalny czas życia rzadko działa dobrze. Ustawienia aplikacji można buforować przez godzinę lub dłużej, ale stan magazynowy produktu, cena promocyjna czy uprawnienia użytkownika wymagają krótszego TTL albo natychmiastowego usunięcia wpisu po zmianie.
Praktyczny podział może wyglądać następująco:
- konfiguracja rzadko zmieniana: 30–120 minut;
- listy kategorii i dane słownikowe: 10–60 minut;
- wyniki kosztownych raportów: 5–30 minut;
- dane często zmieniane: 30–300 sekund;
- odpowiedzi z zewnętrznego API: zgodnie z limitem API i tolerancją na nieaktualność;
- sesje: zgodnie z rzeczywistym czasem bezczynności użytkownika.
Krótki TTL nie zawsze jest bezpieczny. Jeżeli tysiące kluczy wygasną jednocześnie, aplikacja może nagle zasypać bazę danych zapytaniami. To zjawisko określa się jako cache stampede. Ogranicza się je przez dodanie niewielkiej losowości do czasu wygaśnięcia, użycie blokady podczas odtwarzania wpisu albo odświeżanie danych przed upływem TTL.
Cache należy oceniać na podstawie pomiarów. Minimum to:
- hit rate, czyli udział trafień w cache;
- liczba odczytów zakończonych brakiem wpisu;
- liczba eviction;
- zużycie pamięci;
- liczba aktywnych połączeń;
- czas odpowiedzi aplikacji;
- liczba zapytań SQL na żądanie;
- obciążenie CPU i czas pracy PHP;
- restarty procesów PHP-FPM i usług cache.
Wysoki hit rate nie gwarantuje dobrego projektu. Można osiągnąć 95% trafień, buforując dane tanie w odczycie, a jednocześnie pozostawić bez cache’u jedno zapytanie trwające 800 ms. Priorytetem powinny być operacje najdroższe, a nie najczęstsze.
Trzeba też uważać na cache danych zależnych od użytkownika. Błąd w konstrukcji klucza może spowodować pokazanie jednemu klientowi danych innego klienta. Klucz powinien uwzględniać wszystkie elementy wpływające na wynik, na przykład identyfikator użytkownika, język, walutę, rolę, sklep, wersję konfiguracji i parametry zapytania.
W WordPressie samo uruchomienie Redisa na serwerze nie wystarczy. Potrzebna jest obsługa trwałego object cache przez aplikację, zazwyczaj realizowana za pomocą odpowiedniej wtyczki lub warstwy dostarczanej przez hosting. Po aktywacji trzeba sprawdzić, czy panel potwierdza połączenie, czy liczba kluczy rośnie i czy kolejne odsłony ograniczają liczbę zapytań do bazy. Komunikat „Redis enabled” w ofercie hostingowej nie jest dowodem, że WordPress z niego korzysta.
FAQ
Czy Redis przyspieszy każdą stronę WordPress?
Nie. Najwięcej zyskują rozbudowane instalacje, WooCommerce, serwisy członkowskie i strony wykonujące wiele powtarzalnych zapytań. Prosta witryna obsługiwana przez cache pełnych stron może nie wykazać zauważalnej różnicy.
Czy można używać Redis i Memcached jednocześnie?
Technicznie tak, ale w większości typowych aplikacji nie ma takiej potrzeby. Dwa systemy zwiększają zużycie RAM, komplikują monitoring i utrudniają ustalenie, która warstwa przechowuje dane. Jeden poprawnie wdrożony mechanizm jest zwykle lepszy.
Czy OPcache przechowuje dane z bazy?
Nie. OPcache zapisuje skompilowany kod PHP. Do przechowywania wyników zapytań, sesji i obiektów aplikacji służy Redis, Memcached albo inny cache danych.
Jaki TTL ustawić w Redisie?
Nie istnieje jedna poprawna wartość. Dla danych dynamicznych rozsądnym punktem startowym jest 30–300 sekund, dla konfiguracji 30–120 minut. Dane krytyczne powinny być usuwane z cache’u bezpośrednio po zmianie, zamiast czekać na wygaśnięcie.
Ile pamięci przeznaczyć na Redis?
Mała aplikacja często zaczyna od 128–256 MB, średni sklep od 256–512 MB. Limit należy zwiększać dopiero wtedy, gdy statystyki pokazują częste eviction, wysoki hit rate oraz realny zapas RAM na bazę danych i procesy PHP.
Czy wyłączenie opcache.validate_timestamps jest bezpieczne?
Tylko wtedy, gdy proces wdrożenia zawsze resetuje OPcache po zmianie kodu. Na hostingu bez kontroli nad PHP-FPM bezpieczniej pozostawić walidację włączoną.
Pierwszym krokiem nie powinno być dokupienie większego pakietu ani włączenie kolejnej wtyczki. Najpierw sprawdź, czy OPcache jest aktywny, ile zapytań SQL wykonuje jedno żądanie i czy aplikacja faktycznie korzysta z Redisa lub Memcached. Jeżeli cache działa, skontroluj hit rate, eviction i limit pamięci. Najczęściej najdroższym błędem okazuje się nie brak cache’u, lecz buforowanie niewłaściwych danych, zbyt niski limit RAM albo brak poprawnego czyszczenia wpisów po zmianie treści.
