ワーカーモード

ワーカーモードは、一度だけブートストラップしてその後多数のリクエストを処理する永続的な PHP プロセスを実行します。そのため、PHP の起動コストはリクエストごとにではなく一度だけ支払われます。リクエストごとに PHP の状態を破棄して再構築するのではなく、アプリケーションはオートローダー、設定、データベース接続を一度だけ読み込み、ワーカーの生存期間を通じてそれらを再利用します。

仕組み

  1. ワーカーモードを有効化します。 WORKER_MODE_ENABLED=true を設定し、ENTRY_FILE をブートストラップスクリプトに向けます。これにより、プール内のすべての PHP ワーカーでワーカーモードが有効になります。
  2. 一度だけブートストラップします。 PHP が起動し、外側のスコープを一度だけ実行します。オートローダーの登録、設定の読み込み、データベース接続、その他の初期化コードは一度だけ実行されます。
  3. リクエストループに入ります。 oxphp_worker(callback) を呼び出します。OxPHP は受信した HTTP リクエストをコールバックへディスパッチし始めます。
  4. リクエスト間でリセットします。 スーパーグローバル($_GET$_POST$_SERVER$_COOKIE$_FILESphp://input)、出力バッファ、レスポンスヘッダーは自動的にリセットされます。ソフトリセットは、外側のスコープでブートストラップされたリソースを保持しつつ、リクエストごとの状態をクリーンアップします。
  5. 外側のスコープは維持されます。 oxphp_worker() より前に定義された変数、静的プロパティ、データベース接続、オートローダーは、そのワーカーが処理するすべてのリクエストにわたって利用可能なまま残ります。
Note

ワーカーモードはルーティングの挙動を変更します。ディスク上の静的ファイルにマッチしないすべてのリクエストは、404 を返す代わりにワーカーへディスパッチされます。詳細は ルーティング を参照してください。

設定

Variable Default Description
WORKER_MODE_ENABLED false 永続的なワーカーモードを有効にします。true1yes を受け付けます。ENTRY_FILE.php スクリプトを指している必要があります
ENTRY_FILE (未設定) ワーカーのブートストラップスクリプトへのパス。相対パスの場合は DOCUMENT_ROOT を基準に解決されます。.. セグメントと絶対パスが許可されます(公開ドキュメントルートの外に置かれたワーカーブートストラップはサポートされているレイアウトです)
WORKER_MAX_MEMORY_MIB 0 リサイクル前のワーカーごとの最大 PHP メモリ量(MiB 単位)。0 = 無制限
WORKER_FILE からの移行

レガシーの WORKER_FILE 変数は引き続きパースされ(起動時に WARN が出ます)、WORKER_MODE_ENABLED=true ENTRY_FILE=$WORKER_FILE と同じように振る舞います。新しいデプロイでは明示的なペアを使用してください。レガシーの形式は将来のリリースで削除されます。

アプリケーション駆動のリサイクルには、リクエストハンドラーの内部から OxPHP\Server\Worker::scheduleExit() を呼び出します。ワーカーは現在のリクエストが完了した後にクリーンに終了します。

ワーカースクリプトの記述

ワーカースクリプトは 2 つの部分から構成されます。起動時に一度だけ実行される外側のスコープと、リクエストごとに実行される oxphp_worker() に渡されるコールバックです。

worker.php
<?php // Outer scope: runs once at startup require __DIR__ . '/../vendor/autoload.php'; $config = parse_ini_file(__DIR__ . '/../config/app.ini'); $db = new PDO($config['dsn'], $config['user'], $config['pass'], [ PDO::ATTR_PERSISTENT => true, ]); $app = new MyApp\Application($config, $db); // Request loop: runs for every request oxphp_worker(function () use ($app) { $app->handle(); }); // Shutdown: runs when the worker exits $app->terminate();

リセットされるものと維持されるもの

OxPHP はリクエスト間でソフトリセットを実行します。リクエストごとの状態は自動的にクリーンアップされ、外側のスコープでブートストラップされたものはワーカーの生存期間を通じて残ります。

リクエスト間でリセットされるもの
  • スーパーグローバル$_GET$_POST$_SERVER$_COOKIE$_FILESphp://input は新しいリクエストデータで再投入されます
  • 出力バッファ — すべての出力バッファがフラッシュされ、クリーンアップされます
  • レスポンスヘッダー — HTTP ステータスコードとヘッダーはデフォルトにリセットされます
  • エラー状態 — 直近のエラー情報(メッセージ、ファイル、行、タイプ)と接続ステータスがクリアされます。ユーザーが登録したエラーハンドラー(set_error_handler())、例外ハンドラー(set_exception_handler())、および error_reporting() のレベルはリクエスト間で維持されます
リクエスト間で維持されるもの
  • 外側のスコープの変数oxphp_worker() より前に定義され、use でキャプチャされたすべてのもの
  • 静的プロパティ — クラスの静的プロパティはその値を保持します
  • データベース接続 — PDO、MySQLi、その他の永続的な接続は開いたままになります
  • オートローダー — 登録されたオートローダー(Composer、カスタム)は有効なまま残ります
  • 読み込み済みのクラスと関数 — 以前に読み込まれたすべてのクラス、インターフェース、トレイト、関数

リサイクル

ワーカーは、以下のいずれかの条件が満たされると自動的にリサイクルされます(新しい PHP プロセスで再起動されます)。

  • 最大メモリ超過 — ワーカーの PHP メモリ使用量が WORKER_MAX_MEMORY_MIB MiB を超えた場合
  • アプリケーションによる終了要求 — ハンドラーが Worker::scheduleExit() を呼び出した場合。アプリケーション制御のホットリロード、ファイルの mtime に基づくリロード、リクエストごとのブートストラップ再実行などに役立ちます
  • 連続エラー — ワーカーが 3 回連続してハンドラーの失敗(致命的エラー、タイムアウト、未処理の例外)に遭遇した場合。なお、exit()/die() の呼び出しは失敗としてカウントされません

ワーカーがリサイクルされると、PHP プロセスは終了し、新しいプロセスが起動して、ワーカースクリプトの外側のスコープを再実行します。メモリベースおよびスケジュールされた終了の場合、ワーカーが終了する前に現在のリクエストが正常に完了します。エラーベースのリサイクルの場合、ワーカーは失敗したリクエストの後に終了します。

開発時のリロード

ワーカーモードはブートストラップの状態(オートローダー、DI コンテナ、DB 接続)をメモリ内に保持するため、外側のスコープで実行されたコードの変更を反映するには opcache.validate_timestamps=1 だけでは不十分です。開発時のループには 2 つの選択肢があります。

  • リクエストごとにリサイクルする。 すべてのハンドラー呼び出しの最後に OxPHP\Server\Worker::current()->scheduleExit() を呼び出します(たとえば OXPHP_DEV 環境変数フラグで制御します)。現在のリクエストは正常に完了し、その後ワーカーは終了して再生成され、外側のスコープが再実行されます。これはワーカーモードのパフォーマンス上の利点を FPM スタイルのリロードセマンティクスと引き換えにします。アクティブな開発において最もシンプルで信頼性の高いアプローチです。
  • ワーカーをウォームに保ち、リクエストハンドラーをリロードする。 scheduleExit() を完全にスキップし、opcache.validate_timestamps=1 を有効にして、ブートストラップを最小限に保ちます。リクエストコールバック内で読み込まれたコードは、次のリクエストで OPcache によってリフレッシュされます。外側のスコープで一度だけ読み込まれたコードはリフレッシュされません。注意点の完全な一覧については OPcache と JIT → 開発時の設定 を参照してください。

トラブルシューティング

リクエストがハングして完了しない

ブートストラップスクリプトで oxphp_worker() が一度も呼び出されないと、リクエストはディスパッチされず、すべてのリクエストが無期限に待機します。スクリプトが通常のコードパスで無条件に oxphp_worker() を呼び出していることを確認してください。

リクエスト間で状態がリークする

oxphp_worker() コールバック内で定義された変数は PHP のガベージコレクターによってクリーンアップされますが、外側のスコープで定義された静的プロパティやグローバル変数は維持されます。あるリクエストのデータが別のリクエストに現れる場合は、呼び出しをまたいで状態を蓄積している静的プロパティやグローバル変数がないか確認してください。

対処法: 各リクエストコールバックの先頭で静的状態を明示的にリセットするか、リクエストごとの状態を静的変数に格納しないようにします。

ワーカーが即座にリサイクルされる(メモリ制限)

ワーカーのメモリ制限は、PHP が報告するメモリ使用量を用いて各リクエストの後にチェックされます。ブートストラップフェーズで大量のメモリを割り当てる場合(たとえば大きなキャッシュを読み込む場合)、初期のメモリフットプリントがすでに制限に近い可能性があります。

対処法: WORKER_MAX_MEMORY_MIB を増やすか、大きな割り当てを最初のリクエストまで遅延させます。

ワーカーが即座にリサイクルされる(エラー制限)

3 回連続してハンドラーが失敗するとリサイクルが発生します。リクエストコールバック内で発生している例外や致命的エラーがないか、アプリケーションのログを確認してください。

確認: アクセスログまたは構造化ログ出力でエラーを探します。

bash
docker logs <container> 2>&1 | grep '"level":"error"'
アイドル後にデータベース接続が切断される

データベースサーバーがアイドル接続を閉じる場合、次のリクエストでの再接続の試みが失敗する可能性があります。再接続を処理するコネクションプールを使用するか、例外をキャッチして手動で再接続してください。

Docker の例

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "8080:80" volumes: - ./src:/var/www/html environment: - DOCUMENT_ROOT=/var/www/html/public - WORKER_MODE_ENABLED=true - ENTRY_FILE=/var/www/html/worker.php - WORKER_MAX_MEMORY_MIB=128

PHP API

ワーカーのイントロスペクションとワーカーのエントリーポイントは、OxPHP\Server\Worker クラスを通じて公開されます。

php
<?php $worker = OxPHP\Server\Worker::current(); $worker->serve(function () { handleRequest(); });

レガシーの自由関数oxphp_is_workeroxphp_worker_idoxphp_worker)は引き続き利用可能で、同じ内部状態を経由します。新しいコードではクラス API を優先してください。

このクラスは、グレースフルな自己リサイクル、可観測性、ヘルスチェックに役立つランタイムのイントロスペクションも公開します。

Method Returns
Worker::isWorkerMode(): bool サーバーがワーカーモードで実行されているかどうか
$worker->id(): int スレッドごとの安定したワーカーID
$worker->startTime(): float このワーカーが起動した Unix タイムスタンプ
$worker->requestCount(): int このワーカーが処理したリクエスト数
$worker->memoryUsage(): int このワーカーの現在の memory_get_usage(true)
$worker->rss(): int 現在の resident set size(バイト単位、Linux/macOS)
$worker->maxMemoryBytes(): int リサイクルのしきい値 — WORKER_MAX_MEMORY_MIB × 1 MiB、無制限の場合は 0
$worker->isExitScheduled(): bool scheduleExit() が呼び出されたかどうか
$worker->exitReason(): ?string 実行中は null。ワーカーが停止する際は "scheduled""max_memory""error" のいずれか

完全なシグネチャと実践的な例については OxPHP\Server\Worker を参照してください。

PHP の例

ワーカーモードの検出

現在のプロセスがワーカーモードで実行されているかどうかを確認するには、OxPHP\Server\Worker::isWorkerMode() を使用します。これは、従来のモードとワーカーモードの両方で動作するコードを書く際に役立ちます。

php
<?php if (OxPHP\Server\Worker::isWorkerMode()) { // Reuse a persistent connection $redis = new Redis(); $redis->pconnect('redis', 6379); } else { // Traditional mode: connect per request $redis = new Redis(); $redis->connect('redis', 6379); }

Symfony のワーカースクリプト

worker.php
<?php use App\Kernel; require __DIR__ . '/../vendor/autoload.php'; $kernel = new Kernel('prod', false); $kernel->boot(); oxphp_worker(function () use ($kernel) { $request = Symfony\Component\HttpFoundation\Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response); }); $kernel->shutdown();

ベストプラクティス

  • WORKER_MAX_MEMORY_MIB を設定します(たとえば 128)。これにより、リークしているワーカーがホストを消費し尽くす代わりに自動的にリサイクルされます。さらにアプリケーション駆動のリサイクルのために Worker::scheduleExit() と組み合わせます。
  • リクエストごとの状態を静的プロパティやグローバル変数に格納しないようにします。 これらはリクエスト間で維持されるため、あるリクエストの残留状態が別のリクエストにリークする可能性があります。
  • ソフトリセットを早い段階で検証します。 開発用フラグの下でハンドラーに Worker::current()->scheduleExit() を追加し、アプリケーションをエンドツーエンドで動かしてみます。これにより、長寿命のワーカーに移行する前に状態リークのバグを捕捉できます。
  • データベースのアイドルタイムアウトに対処します。 データベースドライバーがアイドル期間の後に切断する場合は、例外をキャッチして再接続するか、再接続を自動的に処理するコネクションプールを使用します。
  • 外側のスコープを最小限に保ちます。 本当に維持する必要があるもの、つまりオートローダー、設定、共有サービスだけをブートストラップします。リクエスト固有のセットアップはコールバックに遅延させます。

関連項目

  • ルーティング — ワーカーモードが URL ルーティングにどのように組み込まれるか
  • 早期レスポンス — レスポンスを即座に送信し、バックグラウンド処理を続行する
  • PHP 関数oxphp_worker()oxphp_is_worker()、その他の組み込み関数の完全なリファレンス
  • 設定リファレンス — 環境変数の完全な一覧