Web application on Windows Server, Apache 2.4 with mod_php, PHP 7.4.9 to 8.2.35
Industry: Web portals · Duration: 4 hours
Challenge
The home page of the application is rendered in PHP; after it, the browser calls about 20 AJAX scripts in parallel. All scripts include a shared bootstrap file that loads the configuration, the autoloader and the template engine, calls session_start() and writes a value to $_SESSION.
After the PHP upgrade, the client's team reported that the application had become "unevenly slower". Suspects were a generally slower PHP 8 and the sessions. A session_write_close() in one of the AJAX scripts helped, but nobody knew why, or whether PHP 8 had changed how sessions behave. The question: can this be fixed without rewriting the application?
Solution
We rebuilt the application in two Docker containers, php:7.4-apache and php:8.2-apache, Apache with mod_php as at the client, with the same files and the real template engine on every call. A load generator behaved like a browser: one logged-in session, in each round 21 parallel calls with the same cookie, the home page and 20 AJAX calls. A continuous test added six clients sending AJAX calls without pause while the home page was requested every two seconds.
Both versions behaved identically; it was not a regression of PHP 8. The default files handler takes an exclusive, blocking lock on the session file in session_start() and holds it until session_write_close() or the end of the script. All calls of one user therefore run one after another: twenty calls of 1.5 seconds each are 30 seconds for the last one, and a home page clicked meanwhile joins the same queue. On Windows, max_execution_time counts real time, so waiting for the lock alone uses up the limit and ends in a fatal error.
The fix is a switch in the shared bootstrap file: a script that only reads the session releases the lock right after reading it. One line per script, without restructuring the application.
Results
- 20 parallel AJAX calls of 1.5 s each, time of the last
- From 30.0 s to 1.5 s
- Home page waiting during the parallel calls
- From 1.6 to 1.8 s to 0.00 s
- Change to the code
- One switch in the bootstrap file, one line per script
- Behaviour of the session lock
- PHP 7.4 and PHP 8.2 identical