Historia optymalizacji WordPressa na współdzielonym hostingu

Miałem ostatnio pacjenta w postaci WordPressa z Astra/Spectra i kilkoma dodatkowymi pluginami. TTFB dramatycznie wysoki, 3-4 sekundy. Serwer współdzielony, więc nie ma jak zainstalować APM w stylu New Relic i zbadać, co go bolało od środka.

Dokonałem zatem profilowania strony przez plugin Code Profiler Pro (korzystasz? Jeśli nie - zerknij sobie na ten plugin https://nintechnet.com/codeprofiler/) i moim oczom ukazał się taki wynik:

Rezultat wydajności podczas testu Code Profiler Pro - strona 1

Dodatkowo pobrałem sobie cały call stack z badania w formie CSV (Scripts Performance plus Methods and Functions Performance) i mogłem przeanalizować funkcja po funkcji, plik po pliku, co takiego powoduje, że WP Forms ładuje się tak długo na każdym żądaniu. Wkleiłem to do Claude Code i poprosiłem o analizę:

Rezultat wydajności podczas testu Code Profiler Pro - performance

Rekomendacje, jakie dostałem, to między innymi: ładować WP Forms warunkowo, tylko na tych stronach, gdzie formularz faktycznie jest. No super, ale akurat w tej witrynie formularze są praktycznie na każdej podstronie.

Co więcej, sprofilowałem też kokpit WordPressa (wp-admin). Taki sam test. A tam? To samo:

Rezultat wydajności podczas testu Code Profiler Pro pod kątem wydajności dashboardu WordPressa (wp-admin)

Czyli WP Forms ładowany jest zawsze i wszędzie. Nawet na żądaniach, gdzie żadnego formularza nie ma.

Jako że tę stronę miałem oczywiście skopiowaną lokalnie oraz na stagingu, zrobiłem dokładnie ten sam test na moim serwerze testowym. A tam? Zobacz sam:

Rezultat testów w Code Profiler Pro na innej stronie

Ten sam kod źródłowy, ta sama baza. Inny serwer.

Jaki nasuwa się wniosek? Współdzielony serwer na produkcji do wymiany i sprawa załatwiona.


ALE.
 Na tym samym współdzielonym serwerze, a raczej pod tym samym kontem klienta, stoi jeszcze kilkanaście innych stron na WordPressie. Tak się składa, że bliźniacza strona (co prawda na Elementorze, ale z tej samej rodziny) też ma WP Forms i tam strona oraz wp-admin ładują się błyskawicznie. Więc jak to w końcu jest? Ten sam hostingodawca, podobne projekty, a na jednym jest problem z WP Forms, a na drugim nie?

Spędziłem sporo czasu, szukając wyjaśnienia, dlaczego Code Profiler Pro pokazuje WP Forms na 1,9 sekundy per request na produkcji, podczas gdy ta sama wtyczka na stagingu (ta sama instalacja WP, to samo WP Forms, nawet lepszy zestaw wtyczek) zachowuje się normalnie. Przeszedłem przez kilka hipotez:
- kolejka Action Schedulera (pusta)
- liczba formularzy (4, śmiesznie mało)
- zimny OPcache (OPcache działa poprawnie)
- "neighbour noise" na shared hostingu (zmienność TTFB była niska, więc nie)
- CPU throttle (produkcja miała wręcz szybszy benchmark MD5 niż staging)

Żadne z tego nie tłumaczyło dwóch sekund różnicy.
 Dopiero, gdy dołożyłem do mojego pliku diagnostycznego linijkę var_dump(realpath_cache_size()) i odpaliłem oba serwery obok siebie, wyszło to:
produkcja (LiteSpeed LSAPI plus CloudLinux LVE): realpath_cache_size() = 0
staging (PHP-FPM na Ubuntu, bez LVE): realpath_cache_size() = 9947

Dziewięć tysięcy bajtów różnicy w tym jednym pomiarze wyjaśniło wszystko. Na prodzie PHP nie trzyma cache'u między requestami, więc każdy include w WP Forms (381 plików) robi świeży stat. Na LVE, z narzutem syscalla, wychodzi z tego 1,5-2 sekundy extra. I to właśnie było widoczne jako "WP Forms jest wolny". Ale to nie WP Forms. To serwer. Na stagingu ta sama wtyczka działa, bo jej 381 plików ląduje w istniejącym, "rozgrzanym" realpath cache.

Przykład rezultatu WOW Audit i informacji o realpath_cache (OPcache)

Zapytasz pewnie, czym jest ten realpath cache. Już tłumaczę.

Realpath cache to bardzo niskopoziomowy mechanizm PHP. Gdy kod robi require '/home/.../wp-content/plugins/xxx/file.php', PHP musi rozwiązać tę ścieżkę na poziomie systemu plików. Sprawdzić, czy /home istnieje, czy /home/twojuser istnieje, i tak katalog po katalogu, wszystko przez syscalle stat(). Realpath cache zapisuje ten wynik w pamięci procesu PHP, żeby kolejne require pod ten sam katalog nie robiły tej samej pracy od zera.

Na zdrowym hostingu (typowo PHP-FPM z sensownie ustawionym pm.max_requests) jeden worker PHP obsługuje setki albo tysiące requestów, a realpath cache rośnie między nimi. Typowy WordPress z WP Forms, Astrą, Spectrą i Yoastem ładuje 1000-2000 plików PHP na request. Po paru requestach cache ma kilkanaście tysięcy bajtów i setki wpisów, a każdy kolejny include idzie przez cache, nie przez dysk.

Na źle skonfigurowanym hostingu (typowo LiteSpeed LSAPI z agresywnym recyklowaniem workerów, albo PHP-FPM z pm.max_requests = 1) worker resetuje cache po każdym requeście. Każdy include robi pełny łańcuch stat() na nowo. Na CloudLinuxie z LVE każdy taki syscall ma jeszcze dodatkowy narzut izolacji, 100-300 mikrosekund. Pomnóż to przez 1500 plików i 10 syscalli na każdy i wychodzi 1,5-4 sekundy czystego overheadu na każdym pojedynczym requeście. Ten czas nie idzie na nic. Idzie na powtarzanie pracy, która powinna być już zcache'owana.

Dlatego diagnoza serwera jest tak istotna, a plik diagnostyczny jako jeden z pierwszych kroków na serwerze jest MEGA WAŻNY.


Każdy audyt robiony według playbooka WOW ma ten test automatycznie w sekcji 1 skryptu diagnostycznego. Nie trzeba pamiętać, żeby dopisywać kod ad hoc. Po uruchomieniu wow-audit-check.php pierwsze, co widać, to "Realpath cache: 0 B" albo na przykład "9947 B", z automatyczną oceną kolorystyczną. A jeśli SAPI to LiteSpeed LSAPI i cache jest pusty, dochodzi dodatkowy czerwony wiersz z konkretną diagnozą i podpowiedzią, żeby uderzyć do hostingu. Bo dokładnie to trzeba wtedy zrobić na współdzielonym serwerze :)

I teraz najważniejsze.
 Gdybym nie wiedział o tym fakcie z konfiguracji serwera, mógłbym pochopnie stwierdzić: "drogi kliencie, to wina WP Forms, wymieniamy na inną wtyczkę". Gdybym uruchomił stronę na stagingu, zobaczył, że ta sama strona z tą samą bazą działa na innym serwerze o niebo lepiej, i na tym poprzestał, powiedziałbym: "drogi kliencie, to wina serwera, zmieniamy hosting i będzie dobrze".

W obu przypadkach naraziłbym klienta albo na stratę czasu, albo na większy wydatek.
 A dzięki diagnozie w ramach WOW wiem PRECYZYJNIE, co jest powodem problemu na serwerze. Bez zgadywania.

Dowodem na to, że opisane działania (+ wiele innych już na samym frontendzie) przyczyniły się do GIGANTYCZNEJ poprawy wydajności na stronie są wyniki chociażby CrUX: 

Wyniki wydajności strony na CrUX Vis

Jeśli chcesz podejrzeć resztę wyników - tutaj masz link do pełnego raportu CrUX tej strony.

I to jest cała różnica między klikaniem "ptaszków" w pluginach a procesem. Tego uczę w WOW: Wielka Optymalizacja WordPressa. Diagnozy, która mówi Ci dokładnie, gdzie leży problem, zanim ruszysz cokolwiek poprawiać. Jeśli dołączysz - dostęp do WOW audytu oczywiście również będziesz miał(a) :) Zapraszam.

Dołącz do WOW

A jeśli potrzebujesz pomocy z Twoim wolnym WordPressem - chętnie zobaczę, co da się zrobić. Skontaktuj się.