Aplikacja webowa na Windows Server, Apache 2.4 z mod_php, PHP 7.4.9 na 8.2.35
Branża: Portale internetowe · Czas trwania: 4 godziny
Wyzwanie
Strona główna aplikacji jest renderowana w PHP, a po jej załadowaniu przeglądarka uruchamia równolegle około 20 wywołań AJAX. Wszystkie skrypty dołączają wspólny plik startowy, który ładuje konfigurację, autoloader i silnik szablonów, wywołuje session_start() i zapisuje wartość do $_SESSION.
Po aktualizacji PHP zespół klienta zgłosił, że aplikacja „spowolniła nierównomiernie”. Pod uwagę brano ogólnie wolniejsze PHP 8 i obsługę sesji. Doraźnie pomogło dodanie session_write_close() w jednym ze skryptów AJAX, ale nie było jasne, dlaczego działa ani czy PHP 8 zmieniło zachowanie sesji. Pytanie brzmiało: czy da się to naprawić bez przepisywania programu?
Rozwiązanie
Odtworzyliśmy aplikację w dwóch kontenerach Docker, php:7.4-apache i php:8.2-apache, czyli Apache z mod_php jak u klienta, z tymi samymi plikami i prawdziwym silnikiem szablonów przy każdym wywołaniu. Generator obciążenia zachowywał się jak przeglądarka: jedna zalogowana sesja, w każdej rundzie 21 równoległych wywołań z tym samym cookie, strona główna i 20 wywołań AJAX. Do tego test ciągły: sześciu klientów wysyła wywołania AJAX bez przerwy, a co 2 sekundy ładowana jest strona główna.
Obie wersje zachowały się identycznie, to nie była regresja PHP 8. Domyślny handler sesji files zakłada w session_start() wyłączną, blokującą blokadę na pliku sesji i trzyma ją do session_write_close() albo do końca skryptu. Wszystkie wywołania jednego użytkownika wykonują się więc po kolei: dwadzieścia wywołań po 1,5 s to 30 s dla ostatniego z nich, a strona główna kliknięta w tym czasie staje w tej samej kolejce. Na Windows max_execution_time liczy czas rzeczywisty, więc samo czekanie na blokadę zużywa limit i kończy się błędem krytycznym.
Rozwiązaniem jest przełącznik we wspólnym pliku startowym: skrypt, który sesję tylko czyta, zwalnia blokadę zaraz po odczycie. Jedna linia na skrypt, bez przebudowy aplikacji.
Wyniki
- 20 równoległych wywołań AJAX po 1,5 s, czas ostatniego
- Z 30,0 s do 1,5 s
- Oczekiwanie strony głównej podczas równoległych wywołań
- 0,00 s zamiast 1,6 do 1,8 s
- Zmiana w kodzie
- Jeden przełącznik w pliku startowym, jedna linia na skrypt
- Zachowanie blokady sesji
- PHP 7.4 i PHP 8.2 tak samo