⚡️ Move deployment image to AlmaLinux and frankenphp
Seeing great performance uplift in synthetic benchmarks with frankenphp against swoole in my production-like deployment.
I have not run them on dedicated resources so I won't provide precise numbers, but I'm never seeing any less than 30% more req/sec, often more.
Memory usage during the synthetic benchmarks is also twice lower, but the usage pattern is so different than real production usage, I'm not considering it any more than encouraging.
frankenphp requires PHP compiled with ZTS. There is only one repository providing such builds, that is FrankenPHP's own repos, but only their RPM repos allow us to bind ourselves to a specific PHP version. So I switched the image to be based on AlmaLinux 9 instead, which is very close to RHEL, with strong community adoption and ships all the packages we need, including multiple versions of Node.
Both AlmaLinux and the PHP repo support arm64. The image builds fine but I haven't tested it as I don't possess hardware on this platform. The build is so much faster though, thanks to dnf being so much faster than apt especially under ARM emulation.
The image is also 160MB lighter so we'll see overall faster production deployments.
If anything I have a pre-built image currently up as ghcr.io/thepoon/osu-web:frankenphp-deployment-gha-test (amd64/arm64).
notbakaneko mentioned on Discord merging this may currently be blocked by some swoole-specific calls:
note that only swoole supports Octane::tasks() that's being used by chat to send messages without blocking the worker on waiting for response
I have ensured that no loaded extension is missing other than swoole.
# docker.io/pppy/osu-web:2025.1204.0
php > print_r(get_loaded_extensions());
Array
(
[0] => Core
[1] => date
[2] => libxml
[3] => openssl
[4] => pcre
[5] => zlib
[6] => filter
[7] => hash
[8] => json
[9] => pcntl
[10] => random
[11] => Reflection
[12] => SPL
[13] => session
[14] => standard
[15] => sodium
[16] => mysqlnd
[17] => PDO
[18] => xml
[19] => calendar
[20] => ctype
[21] => curl
[22] => dom
[23] => mbstring
[24] => FFI
[25] => fileinfo
[26] => ftp
[27] => gd
[28] => gettext
[29] => iconv
[30] => igbinary
[31] => intl
[32] => exif
[33] => mysqli
[34] => pdo_mysql
[35] => pdo_sqlite
[36] => Phar
[37] => posix
[38] => readline
[39] => shmop
[40] => SimpleXML
[41] => sockets
[42] => sqlite3
[43] => sysvmsg
[44] => sysvsem
[45] => sysvshm
[46] => tokenizer
[47] => xmlreader
[48] => xmlwriter
[49] => xsl
[50] => zip
[51] => redis
[52] => swoole
[53] => ds
[54] => Zend OPcache
)
# ghcr.io/thepoon/osu-web:frankenphp-deployment-gha-test
php > print_r(get_loaded_extensions());
Array
(
[0] => Core
[1] => date
[2] => libxml
[3] => openssl
[4] => pcre
[5] => zlib
[6] => calendar
[7] => ctype
[8] => curl
[9] => dom
[10] => json
[11] => filter
[12] => hash
[13] => iconv
[14] => SPL
[15] => mbstring
[16] => pcntl
[17] => session
[18] => standard
[19] => Phar
[20] => posix
[21] => random
[22] => readline
[23] => Reflection
[24] => exif
[25] => SimpleXML
[26] => sockets
[27] => sodium
[28] => tokenizer
[29] => trader
[30] => xml
[31] => xmlreader
[32] => xmlwriter
[33] => FFI
[34] => apcu
[35] => ds
[36] => fileinfo
[37] => ftp
[38] => gd
[39] => gettext
[40] => intl
[41] => msgpack
[42] => mysqlnd
[43] => PDO
[44] => shmop
[45] => sqlite3
[46] => sysvmsg
[47] => sysvsem
[48] => sysvshm
[49] => xsl
[50] => zip
[51] => igbinary
[52] => mysqli
[53] => pdo_mysql
[54] => pdo_sqlite
[55] => redis
[56] => Zend OPcache
)
~~As far as testing goes, I haven't done more than test whether~~ it builds, runs, can display some pages from an unseeded database and browse wiki/news on my production-like instance (can provide a link privately).
I intend to rather use staging as a seeded test env.
edit: all phpunit and browser tests seem to pass using the analogous development image https://github.com/ppy/osu-web/pull/12596
Is there any reading material available on how it achieves the 30% boost? I'd kinda want to know what's the deal there before going full steam ahead in this direction.
FrankenPHP's author made a talk 3 years back when they announced the project, in which they mention they're heavily leveraging Go's very optimized net/http libraries, Goroutines, and PHP's ZTS mode.
Swoole, despite using the similar worker model, is not using ZTS.
source: https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/
Have replaced jhead by exiv2 and jpegtran to allow upgrading to AlmaLinux 10.
We're building assets with Node.js 22 instead of 20 now, which seems fine as far as I can tell.
Making frankenphp the default Octane server in config broke CI, as it wasn't running off the Docker environment. It failed as the frankenphp binary shipped by default misses critical extensions.
I refactored the workflow to use the Docker dev set-up instead, which should also make it much easier to maintain going forward, along side other fixes.