Redis接続のオーバーヘッドを殺す:Persistent ConnectionsによるPHP-FPM/Redis間通信の最適化
高トラフィックなWordPress環境において、ボトルネックは往々にしてデータベースそのものよりも「その手前」のオブジェクトキャッシュ層にある。多くのエンジニアがRedisを導入して満足するが、その実、`PhpRedis`や`Predis`がリクエストごとにTCPハンドシェイクを繰り返している事実に気づいていない。
この記事では、TCP/IPスタックの挙動とPHP-FPMのプロセスライフサイクルを考慮し、Redis接続を永続化(Persistent Connections)させることで、ミリ秒単位のオーバーヘッドを削ぎ落とす手法を解説する。
—
1. なぜ「接続」がボトルネックとなるのか
標準的なWordPressのオブジェクトキャッシュ実装では、リクエストのたびにRedisサーバーへの新しい接続が確立される。これを分解すると、以下のステップが毎回実行されていることになる。
1. DNS解決(もしホスト名指定なら)
2. TCP 3-way Handshake(SYN, SYN-ACK, ACK)
3. Redis認証(AUTHコマンド)
4. 接続のクローズ(FIN/ACK)
高負荷時、PHP-FPMのワーカープロセス数が増大すればするほど、Redis側で`TIME_WAIT`状態のソケットが量産される。これはカーネルのポート枯渇を招き、最悪の場合、OSレベルで接続拒否が発生する。我々が目指すべきは、この「接続確立コスト」をゼロにすることだ。
2. 実践:PhpRedisにおける永続接続の構成
WordPressの `wp-config.php` またはドロップインプラグインで、`PhpRedis`を利用している場合、接続パラメータの調整が必要だ。`persistent` フラグを有効にすることで、PHP-FPMのプロセスは一度開いたソケットを再利用する。
設定例:Object Cache Drop-in (例: `object-cache.php`)
/
- Redis接続の最適化:Persistent Connectionの有効化
- @param string $host Redisホスト
- @param int $port Redisポート
- @param float $timeout 接続タイムアウト
- @param string $persistent_id 接続を特定する一意の識別子
/
$redis = new Redis();
// 重要: 第4引数に一意のIDを渡すことで、PHP-FPMのプロセス間で
// 接続プールを共有(または固定化)する。
$redis->pconnect(
‘127.0.0.1’,
6379,
0.5, // 接続タイムアウト
‘wp_redis_pool’ // パーシステントID
);
// 認証が必要な場合
$redis->auth(‘your_secret_password’);
// 接続後、シリアライザーを設定してオーバーヘッドをさらに削減
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
なぜ `pconnect` なのか
`connect` はスクリプト終了時にソケットをクローズするが、`pconnect` はPHP-FPMのプロセスが終了するまでソケットを維持する。PHP-FPMの `pm.max_children` が一定数に収束している環境であれば、Redis側で「接続の再利用」が極めて効率的に行われる。
3. カーネルレベルとRedis側の防衛策
接続を永続化すると、Redis側には「常に開かれた接続」が残る。ここで注意すべきは `maxclients` 設定と、ソケットのタイムアウト設定だ。
Redis設定 (`redis.conf`)
接続維持のため、タイムアウトを無効化(あるいは長めに設定)
timeout 0
接続数をPHP-FPMのワーカー数に合わせる
maxclients = (PHP-FPM max_children) (Redisへの同時接続数)
maxclients 10000
TCP Backlogを増強し、接続リクエストの急増を捌く
tcp-backlog 65535
4. 観測:レイテンシの検証
これを実装した際、`strace` を用いてシステムコールをトレースすると、劇的な変化が見て取れる。
PHP-FPMプロセスを対象にシステムコールを追跡
strace -p
最適化前:
`connect()` -> `sendto()` -> `recvfrom()` -> `close()` が毎リクエストごとに観測される。
最適化後:
初回の `connect()` のみが実行され、以降のリクエストでは `sendto()` と `recvfrom()` のみになる。これにより、カーネルのコンテキストスイッチ回数が削減され、CPU負荷が劇的に下がる。
5. アーキテクトの戒め:注意点
この手法にはトレードオフが存在する。
- メモリリークの温床: PHPの拡張モジュールにメモリリークがある場合、永続接続はプロセスが再起動するまでそのリークを保持し続ける。必ず最新の `php-redis` 拡張を使用すること。
- 接続数の上限: `pconnect` を使うと、PHP-FPMのワーカー数分だけRedisへの接続が常駐する。Redisサーバーの `maxclients` を超えないよう、算術的に設計せよ。
- Unix Domain Socket: ネットワーク帯域に余裕があるなら、TCPではなくUnix Domain Socket (`/var/run/redis/redis.sock`) を使用することで、TCP/IPスタックの処理自体をバイパスできる。これが真の極限だ。
WordPressは、その柔軟性の代償として「非効率なリソース消費」を許容する設計になっている。しかし、コアの奥底にある通信レイヤーを掌握すれば、スケールアウトを必要としない「圧倒的な垂直スケーリング」が可能になる。
次にコードを書くときは、単に「動くもの」ではなく「カーネルがどう処理するか」を想像しながら実装してほしい。それがエンジニアとしての境界線を突破する鍵だ。