osu-web icon indicating copy to clipboard operation
osu-web copied to clipboard

⚡️ Move deployment image to AlmaLinux and frankenphp

Open ThePooN opened this issue 2 weeks ago • 2 comments

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

ThePooN avatar Dec 09 '25 23:12 ThePooN

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

ThePooN avatar Dec 10 '25 00:12 ThePooN

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.

peppy avatar Dec 11 '25 06:12 peppy

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.

image image image image

source: https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/

ThePooN avatar Dec 12 '25 16:12 ThePooN

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.

ThePooN avatar Dec 12 '25 23:12 ThePooN

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.

ThePooN avatar Dec 13 '25 03:12 ThePooN