PHP 8.3のFPMでpreloadを効かせつつメモリを増やさない設定

这篇内容暂时只有 日本語 版本,以下是原文。

PHP 8.3.7 + php-fpm 8.3.7、Alpine 3.19 のコンテナで、opcache.preload を入れたらワーカーのRSSが起動直後に 180MB まで跳ねた件。アプリは Laravel 11 ではなく素の DI コンテナ構成で、クラスファイルは約 3,200、preload 対象は 400 弱。FPM の pm は dynamic、pm.max_children は 24、opcache.memory_consumption は 128MB のまま。現象は「preload は効いているのに、各ワーカーが同じだけメモリを持ってしまう」でした。

まず確認したのは preload が本当に走っているか。

php-fpm -tt 2>&1 | grep -i preload
php -i | grep -E 'opcache.preload|memory_consumption'

ここで opcache.preload_user を指定しないと root 実行時以外は静かに無効化されるので、opcache.preload_user = www-data を明示。preload スクリプトは以下。

<?php
$base = '/app/src';
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($base));
foreach ($it as $f) {
    if ($f->getExtension() !== 'php') continue;
    $path = $f->getPathname();
    if (str_contains($path, '/tests/')) continue;
    opcache_compile_file($path);
}

ポイントは require ではなく opcache_compile_file にしたこと。依存解決を preload 時に走らせると、その時点の autoloader 状態がワーカーに固定され、後から composer dump-autoload した差分が反映されない事故が出ます。コンパイルだけなら失敗しても warning で済み、FPM 起動を止めません。

次にメモリ。preload した分は共有メモリに載るので、opcache.memory_consumption を 128 から 192 に上げ、opcache.interned_strings_buffer を 16 に。代わりに pm.max_children を 24 から 18 に落としました。1リクエストあたりのピークは 40MB 前後なので、18 × 40 + 共有 190MB で 1GB に収まる計算。dynamic のままだと負荷時に max_children まで増えて OOM したので、pm=static にして上限を固定しています。

効かない条件は明確で、preload スクリプト内で require や autoload を叩くと、クラス定義の順序に依存した壊れ方をします。また OPcache の validate_timestamps=1 のままだと preload 済みファイルでも stat が走り、デプロイ直後に古い定義が混在しました。本番は validate_timestamps=0 と opcache.file_update_protection=0 の組み合わせで、デプロイ時に FPM を reload する前提にしています。

もう一つ踏んだのは、preload 対象に declare(strict_types=1) の有無が混在していると、コンパイルは通るのに実行時に型エラーになるケース。preload は「読み込み」であって「検証」ではないので、CI で php -l とは別に、全ファイルを実際に autoload して叩くスモークを回すようにしました。

同じ構成で preload を入れた人、pm.max_children と memory_consumption の配分はどう決めていますか。特に static にした後、デプロイ時の reload で共有メモリが解放されるタイミングをどう観測しているか知りたいです。

0
赞同度 100% · 0 条评论 · 5 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。