PHP 8.3.6 / Ubuntu 22.04 / nginx + php-fpm(pool は static、pm.max_children=32)の環境で、opcache.preload を設定したのに preload 済みのはずのクラスが毎リクエスト autoload される現象に当たった。opcache_get_status() の preload_statistics を見ると scripts が 0 件、エラーログにも何も出ない。APCu のヒット率だけが妙に低い、というのが最初の気づきだった。
やったことは単純で、まず preload スクリプトを fpm の起動ユーザーで直接実行して、require が黙って失敗していないか確認した。
sudo -u www-data php -d opcache.enable_cli=1 \
-d opcache.preload=/var/www/app/config/preload.php \
-d opcache.preload_user=www-data \
-r 'var_dump(opcache_get_status()["preload_statistics"]);'
ここで scripts が 0 なら、preload.php の中で opcache_compile_file() が失敗している。原因はほぼ「preload 対象が composer の autoload 経由でしか解決できない」ケースだった。preload.php の先頭で vendor/autoload.php を require していないと、opcache_compile_file() はファイルパスを解決できずに false を返す。ただし警告は出ない。これが「静かに効かない」の正体。
修正は preload.php の先頭に autoload を読み込ませるだけ。
<?php
require __DIR__ . '/../vendor/autoload.php';
$files = [
__DIR__ . '/../src/Kernel.php',
__DIR__ . '/../src/Http/Router.php',
];
foreach ($files as $f) {
if (opcache_compile_file($f) === false) {
error_log('preload failed: ' . $f);
}
}
なぜこれで直るかというと、opcache_compile_file() は実行時ではなくコンパイル時点でファイルを解決するので、その時点の include_path と autoloader の登録状態に依存する。fpm の起動時に preload が走るタイミングでは、まだリクエスト用の autoloader は登録されていない。だから preload.php 側で明示的に読み込む必要がある。
代償もある。preload したファイルは opcache の共有メモリに固定されるので、デプロイのたびに fpm を reload しないと反映されない。opcache.validate_timestamps=1 でも preload 済みスクリプトは再読み込みされないため、開発環境で preload を有効にすると「コードを直したのに反映されない」と沼る。自分は dev では preload を切っている。
失效する境界は他にもある。opcache.preload_user を指定しないと root 起動の fpm では動くが、www-data 起動だと permission denied で無言で落ちる。あと preload 対象に trait や interface だけを入れると、実装クラス側で解決が必要になり効果が薄い。逆に巨大なフレームワーク全体を preload すると共有メモリを食い潰して opcache のヒット率が下がる。手元では 200 ファイル、共有メモリ 128MB で落ち着いた。
みんなの環境では preload_statistics の scripts 件数、実際に何件くらい入っている?


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