Opublikowano
Wzrost zużycia pamięci pod FrankenPHP i przejście na Swoole
Dwie aplikacje Laravel, które uruchamiałem na FrankenPHP, z czasem zużywały coraz więcej pamięci i CPU bez odpowiadającego wzrostu ruchu. Przyczyną nie był błąd w kodzie aplikacji, tylko niedopasowanie między sposobem, w jaki ją zbudowałem, a modelem trwałych workerów, którego FrankenPHP używa zamiast tradycyjnego cyklu żądań PHP-FPM.
- PHP
- FrankenPHP
- Swoole
- Laravel
- zarządzanie pamięcią
- niezawodność produkcyjna
Objaw: wzrost bez odpowiadającego obciążenia
Dotknięte aplikacje zużywały wyraźnie więcej RAM-u i CPU niż wynikałoby to z obsługiwanego ruchu — wzrost, który wyglądał mniej jak normalne użycie, a bardziej jak coś, co nie jest zwalniane. Nie było jednego momentu awarii; zużycie po prostu rosło, aż zaczynało powodować przerwy w działaniu usługi.
Dlaczego PHP-FPM to ukrywa, a FrankenPHP nie
W PHP-FPM każdy request działa w świeżym procesie roboczym, a jego pamięć jest zwalniana po zakończeniu requestu, więc większość nagromadzonego stanu i zapomnianych referencji znika automatycznie. FrankenPHP zamiast tego utrzymuje uruchomioną aplikację w długożyjącym procesie roboczym obsługującym wiele requestów, bliżej sposobu działania trwałego serwera znanego z innych języków. Wszystko, co po cichu narasta między requestami — statyczna właściwość, rosnąca tablica w długożyjącym serwisie, binding w kontenerze trzymający referencję — rośnie dalej zamiast się resetować.
Nie projektowałem tych aplikacji z myślą o tej różnicy w momencie ich konteneryzacji; różnica stała się jasna dopiero, gdy szukałem przyczyny wzrostu zużycia pamięci.
Rozwiązanie: przejście na Swoole
Przeniosłem dotknięte aplikacje z FrankenPHP na Swoole. Swoole uruchamia Laravela w ten sam, trwały sposób, ale w tym przypadku z mniejszym narzutem pamięciowym, kosztem nieco niższego throughput requestów niż przy FrankenPHP. Dla tych aplikacji uniknięcie powtarzającego się wzrostu zużycia pamięci było ważniejsze niż różnica w throughput, więc ten kompromis był wart podjęcia.
Co to zmienia dziś przy trwałym runtime PHP
Praktyczny wniosek wykracza poza tę jedną parę runtime’ów: przeniesienie aplikacji Laravel z PHP-FPM na dowolny model trwałych workerów oznacza sprawdzenie jej pod kątem stanu, który wcześniej resetował się automatycznie przy każdym requeście. Taki audyt traktuję dziś jako część wdrażania trwałego runtime, nie jako opcjonalny dodatek — i sprawdzam zachowanie pamięci pod długotrwałym obciążeniem, zanim oprę na tym wdrożenie produkcyjne.