PHP 8.3.4、Amazon Linux 2023、FPM(pm=dynamic、pm.max_children=32)、MySQL 8.0.36 の構成で、夜間バッチ後の初回リクエストだけ「MySQL server has gone away」が出る件を追いました。アクセスは日中 1 時間あたり 1.2 万リクエスト程度、バッチは 3 万行を 500 件ずつ更新します。
まず FPM のログと MySQL の SHOW PROCESSLIST を突き合わせ、切断が起きるのは決まってワーカーが pm.max_requests に達して再生成された直後だと分かりました。再現手順は単純で、pm.max_requests=1 にして 2 回叩くだけです。
; www.conf
pm = dynamic
pm.max_children = 32
pm.max_requests = 500
次に、再接続をアプリ側で握るのではなく、接続を使い回さない方向に寄せました。具体的には mysqli の持続接続をやめ、リクエスト終了時に明示的に閉じます。
// 変更前: ホスト指定で持続接続
$mysqli = new mysqli('p:db.internal', $user, $pass, $db);
// 変更後: 通常接続 + finally で close
$mysqli = new mysqli('db.internal', $user, $pass, $db);
try {
$mysqli->query('SET SESSION wait_timeout = 60');
// ...
} finally {
$mysqli->close();
}
なぜこれで収まるかというと、持続接続はワーカーのプロセスに紐づくため、pm.max_requests による再生成や MySQL 側の wait_timeout を跨いだ瞬間に「生きているつもりの死んだ接続」が残るからです。ここでは性能より再現性を優先し、接続コストは 1 リクエストあたり数 ms 増える代わりに、gone away をゼロにしました。
ただしこの方法は、接続数が跳ね上がる高トラフィックでは効きません。max_connections に近づくと今度は「Too many connections」に化けます。実際、検証環境で 200 並列をかけたときに別のエラーへ置き換わったのを見ています。持続接続を残すなら、mysqli.reconnect に頼らず、クエリ前に ping() して死んでいたら張り直す方が安全でした。
もう一つの穴は、トランザクション中にワーカーが落ちた場合です。close を finally に置いても、プロセスが kill されればロールバックは MySQL 任せになります。バッチ側の分離レベルとロック待ち時間もセットで見ないと、この対策だけでは足りません。
同じように FPM の再生成と DB 接続の寿命がずれて困った経験のある方、どの層で吸収していますか。アプリ側の ping か、proxy か、それとも接続プールを別に立てていますか。


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