PHP 8.3のFPMでpm.max_childrenを動的に調整する手順

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

PHP 8.3.4、Ubuntu 22.04のFPM環境で、アクセスが急増すると502が頻発する現象に遭遇した。pm.max_childrenを静的に設定していたが、夜間バッチと日中で負荷が全く異なり、固定値では無駄が多い。そこで、負荷に応じて動的に子プロセス数を調整する方法を試した。

まず、FPMの設定でpm = dynamicとし、pm.max_childrenを上限、pm.start_servers、pm.min_spare_servers、pm.max_spare_serversを調整する。これだけでも負荷変動に追従できるが、急激な増加には間に合わない。そこで、pm.max_childrenを変更するスクリプトをcronで回すことにした。以下がそのスクリプトだ。

#!/bin/bash
# /usr/local/bin/update_fpm_max_children.sh

FPM_CONF="/etc/php/8.3/fpm/pool.d/www.conf"
FPM_SERVICE="php8.3-fpm"

# 現在の負荷を取得(過去1分の平均)
LOAD=$(cat /proc/loadavg | awk '{print $1}')
CPU_CORES=$(nproc)

# 負荷がコア数の70%を超えたら最大、30%未満なら最小
if (( $(echo "$LOAD > $CPU_CORES * 0.7" | bc -l) )); then
  TARGET=20
elif (( $(echo "$LOAD < $CPU_CORES * 0.3" | bc -l) )); then
  TARGET=5
else
  # 線形補間(5〜20の範囲)
  TARGET=$(echo "scale=0; 5 + ($LOAD / ($CPU_CORES * 0.7)) * 15" | bc)
fi

# 設定ファイルを更新
sed -i "s/^pm.max_children = .*/pm.max_children = $TARGET/" "$FPM_CONF"

# 設定をリロード(graceful)
systemctl reload $FPM_SERVICE

これをcronで5分ごとに実行する。reloadはgracefulなので、既存のリクエストは切断されない。

なぜこうするか。FPMのdynamicはあくまでアイドルプロセス数を調整するが、max_children自体は静的だ。負荷が高騰したときにmax_childrenが低いと、リクエストがキューに溜まり502になる。逆に常に高いとメモリを無駄に消費する。動的に変えることで、負荷に応じたリソース配分ができる。ただし、reloadは設定ファイルを再読み込みするだけで、既存のプロセスは終了しないため、max_childrenを減らしてもすぐにはプロセス数は減らない。増やすときはすぐに反映される。

この方法が失效するのは、負荷が急激に変動する場合だ。5分間隔では追いつかない。また、loadavgはCPU負荷であり、I/O待ちが多い場合は適切に反映されない。さらに、sedで設定を書き換えるため、手動で編集した内容が上書きされる危険がある。設定ファイルはバージョン管理し、スクリプトはテンプレートから生成する方が安全だ。

別の落とし穴として、reload中に新しい接続が来ると、一時的に502になることがある(reloadはマスタープロセスがシグナルを受け取り、ワーカーを順次再起動するため、瞬間的にワーカーが不足する)。これを避けるには、max_childrenを変更するのではなく、pm = ondemandにしてpm.process_idle_timeoutを短くする手もある。ただし、ondemandはプロセス生成のオーバーヘッドが大きい。

最後に質問だ。FPMの子プロセス数を動的に調整する場合、どの指標(loadavg、CPU使用率、リクエストキュー長など)を監視しているか。また、設定変更の頻度と502のトレードオフをどう考えているか、共有してほしい。

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

0 条评论

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

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