プライム・ストラテジー「KUSANAGI」開発チームの石川です。
今回は ユーザーフォーラムの投稿 に問い合わせが寄せられたことがある 「php-fpm が OPcache の JIT に原因があって Segmentation Fault を起こすケース」 について、確認方法と対処方法、そして原因などを紹介します。
いきなり難しい言葉が4つも出てきたので、まずはこちらから説明します。
- php-fpm: PHP をウェブサーバにおいて効率よく動作させる仕組み (FastCGI Process Manager) です。KUSANAGIは nginx / httpd の両方において、php-fpmで動作させています。
- OPcache: PHP がスクリプト (*.php) のコンパイル済みバイトコードを共有メモリ上にキャッシュすることで、PHPの実行を高速化する仕組みです。KUSANAGIはデフォルトで有効化しています。
- JIT: PHP 8.0より導入された、ジャストインタイム (Just In Time) コンパイラという PHP を速く動作させるための仕組みです。詳細は「PHP重鎮の廣川氏によるコラム」を参照ください。KUSANAGIはデフォルトで有効化しています。
- Segmentation Fault: ソフトウェア実行時に発生するエラーの1つです。プロセスが異常終了します。
発生する現象
ユーザが直接的に見る現象としては、ウェブサイトで「HTTP 502 Bad Gateway」がエラーとして表示されます。
502 Bad Gateway
nginx
ウェブサイトの負荷が高い状態であったり、WordPress等の自動更新が行われている場合にも同様のエラーが表示される場合があります。
しかし、「HTTP 502 Bad Gateway」が継続的 (1時間以上) に発生する場合はこのケースに該当する可能性があります。
確認方法
ウェブサーバのログ
まずは「HTTP 502 Bad Gateway」が発生したウェブサイトのプロファイルにあるウェブサーバのログを確認しましょう。
使用しているウェブサーバによって異なるので、どちらが実行されているかは kusanagi status コマンド で確認してください。
- nginxの場合:
/home/kusanagi/プロファイル名/log/nginx/access.log
または/home/kusanagi/プロファイル名/log/nginx/ssl_access.log
- httpdの場合:
/home/kusanagi/プロファイル名/log/httpd/access.log
または/home/kusanagi/プロファイル名/log/httpd/ssl_access.log
以下のように「HTTP 502 Bad Gateway」が発生したログが出力されます。
0.002 - - (IPアドレス) - - [28/Feb/2023:16:42:59 +0900] "GET / HTTP/2.0" 502 552 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36" "-"
php-fpmのログ
次にphp-fpmのログ /var/opt/kusanagi/log/php-fpm/error.log
を確認します。
以下のように Segmentation Fault「signal 11(SIGSEGV)」が発生したログが出力されます。継続的に発生する場合は、このようなログが複数出力されています。
[28-Feb-2023 16:42:59] WARNING: [pool www] child 392282 exited on signal 11 (SIGSEGV) after 24469.890975 seconds from start
[28-Feb-2023 16:42:59] NOTICE: [pool www] child 404335 started
対処方法
OPcacheのJITに関する設定を変更する必要があります。
/etc/opt/kusanagi/php.d/extensions/10-opcache.ini
のKUSANAGI 9のデフォルト値は以下のようになっています。 (該当箇所のみ抜粋)
opcache.jit = 1254
これを以下のように変更してください。
opcache.jit = 1205
その後にphp-fpmを再起動することで変更が反映されます。 kusanagi php コマンドを実行してください。
$ kusanagi php
php completed.
エラーが発生する原因
PHPの開発元でも 議論 になっているのですが、残念ながら詳細な発生原因までは明らかになっていません。これは確実に再現する環境を作るのが難しいところにあります。
当社のお客様の環境でもこの事象が発生するケースがありましたが、KUSANAGIには「HTTP 502 Bad Gateway」が頻発すると自動的にnginxやphp-fpmを再起動する仕組みがあります。この仕組みによってphp-fpmが再起動するとしばらく発生しなくなるというように、常に発生するわけでもありませんでした。
ここからは私見となりますが、PHP スクリプトファイルの更新が頻繁に行われる環境のように、PHP スクリプト (*.php) と共有メモリ上のキャッシュに差が生じたときに Segmentation Fault が発生するように感じました。
まず、OPcache は PHP スクリプト (*.php) のコンパイル済みバイトコードを共有メモリ上にキャッシュする仕組みです。JIT はこの OPcache のバイトコードを更に直接マシン語に変換する仕組みになっています。
KUSANAGIのデフォルト設定である opcache.jit = 1254
はトレーシング (tracing) モードです。このモードではコードのプロファイリングよって頻繁に実行される部分 (ホットコード) をコンパイルするという動作になります。このプロファイリング時は同じコードと判断したホットコードが、実際に動作する時は同じではなかったことでエラーの原因になったのではないかと推測しています。
なお、対処で変更した opcache.jit = 1205
は関数 (function) モードです。ホットコードを見付けるのではなく、常に PHP スクリプトの読み込み時にコンパイルをするという動作になります。そのため、このエラーが発生しなくなります。
ちなみに opcache.jit = off
を指定することで、完全に JIT を無効にすることもできます。しかし、これは推奨しません。理由は後述します。
設定変更による性能差
さて、エラーが発生する可能性があるのであれば、初めから opcache.jit = 1205
(関数モード) をデフォルトにすればよい、と思うかもしれません。
KUSANAGI が opcache.jit = 1254
(トレーシングモード) をデフォルトにしているのは、トレーシングモードと関数モードで性能に差があるためです。
以下は当社のテスト環境において、インストールしたばかりのWordPressで性能を比較したものになります。
opcache.jit | トレーシングモードとの比較 |
---|---|
トレーシングモード | 100.00% |
関数モード | 101.89% |
無効 | 142.58% |
- KUSANAGI 9 (CentOS Stream 8)
- Microsoft Azure 東日本リージョン D4as_v4インスタンス 32GB Premium SSD LRS
- PHP 8.1.16
- 30多重におけるHTTPS 1リクエストの処理時間の比較
このようにトレーシングモードと比較して、関数モードでは数パーセント、無効にした場合には40%以上の性能劣化が見られました。そのため、KUSANAGIのデフォルトであるトレーシングモードのままにしておき、このケースのエラーが発生した場合にのみ関数モードに変えることを推奨します。