設定リファレンス
OxPHP はすべて環境変数で設定します。管理すべき設定ファイルは存在せず、すべての設定項目にデフォルト値があるため、設定ゼロのデプロイがそのまま動作します。
ブール値
ブール値として指定された変数は、大文字・小文字を区別せず前後の空白を取り除いたうえで、決まった正規の値の集合を受け付けます。
- 真値:
on,true,1,yes - 偽値:
off,false,0,no
その集合に含まれない空でない値、たとえば ture のようなタイプミスは、起動時に該当する変数名を示すエラーで即座に失敗します。これにより、フラグが誤った向きに静かに切り替わってしまう前に、トラフィックが流れ始める前の段階で設定ミスを検出できます。
未設定の変数や空の代入(FOO=)は、ドキュメントに記載されたデフォルト値にフォールバックします。空を未設定として扱うのは意図的な挙動です。Docker Compose / Kubernetes の FOO=${FOO} のような変数展開は、ホスト側の変数が存在しないときに FOO= を生成しますが、それによってサーバーの起動が拒否されるべきではないからです。
サーバー
| 変数 | デフォルト | 説明 |
|---|---|---|
LISTEN_ADDR |
0.0.0.0:80 |
メイン HTTP サーバーのアドレスとポート |
DOCUMENT_ROOT |
/var/www/html/public |
ファイルと PHP スクリプトを配信するルートディレクトリ |
ENTRY_FILE |
(未設定) | 単一の正規エントリースクリプト。未設定 = 直接ファイルマッピング。*.php = フロントコントローラー。.php 以外 = 静的フォールバック(SPA)。WORKER_MODE_ENABLED=true の場合 = ワーカーのブートストラップ。DOCUMENT_ROOT を基準に解決されます(相対パスと .. は許可、絶対パスはそのまま使用)。ルーティングを参照 |
WORKER_MODE_ENABLED |
false |
永続的なワーカーモードを有効化します。ENTRY_FILE が .php スクリプトを指している必要があります。ブール値 — ブール値を参照 |
MAX_CONNECTIONS |
10000 |
同時 TCP 接続の最大数。デフォルトの QUEUE_MAX_WAITING の上限(その半分)でもあるため、不正な形式の値は静かなフォールバックではなく起動エラーになります。これを下げても QUEUE_CAPACITY のデフォルトは動きません。そちらはワーカー数だけから決まるためです — PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING より大きく保ってください。accept ループのための余裕を確保するを参照 |
TOKIO_WORKERS |
CPU / 2(最小 1) | 非同期 I/O スレッド。1 = シングルスレッド、N > 1 = スレッド数固定、未設定 = 自動(CPU / 2、最小 1) |
PHP ワーカー
| 変数 | デフォルト | 説明 |
|---|---|---|
EXECUTOR |
sapi |
PHP エグゼキューターのバックエンド。sapi は PHP の実行、stub は PHP なしでのベンチマーク用 |
PHP_WORKERS |
CPU / 2(最小 1) | ワーカープールのサイズ。N = 固定プール、MIN:MAX = 動的スケーリング、0 = 自動 |
PHP_WORKERS_IDLE_SECONDS |
30 |
動的ワーカーが破棄されるまでアイドル状態を保つ秒数(動的モードのみ)。ワーカーは処理中のものを何も持たない瞬間に破棄されるため、処理中のリクエストが途中で切られることはありません — また、終わらないリクエスト(開いたままのストリームなど)を抱えたワーカーはそのような瞬間に決して到達せず、まったく破棄されません |
QUEUE_CAPACITY |
初期ワーカー数 × 128 | PHP キューで保留できるリクエストの最大数。満杯のキューに到着したリクエストはスロットの空きを待ちます(QUEUE_WAIT_TIMEOUT_MS を参照)。動的プール(MIN:MAX)の場合、初期ワーカー数 = 最小値。0 = 自動 |
QUEUE_WAIT_TIMEOUT_MS |
1000 |
リクエストが 529 で拒否されるまでに PHP ワーカーを待てる時間。到着時に刻印される単一の期限で、リクエストが直面しうる両方の待機 — キューのスロット待ちと、キュー内でのワーカー待ち — をカバーします。期限が過ぎた後にワーカーが到達したリクエストは、実行されるのではなく取り出し時点で拒否されるため、予算は受け入れまでの前半だけでなく待機全体を制限します — その後ハンドラーがどれだけ実行されるかは制限しません。同時に待機できるのは最大 QUEUE_MAX_WAITING 件で、それを超えたリクエストは即座に拒否されます。0 = キューが満杯なら即座に拒否。アプリケーションが HTTP 経由でこの同じサーバーを呼び返す場合(内側の呼び出しは外側の呼び出しがワーカーを解放するまで受け入れられず、待機が無駄になります)や、前段のロードバランサーがより短い独自のタイムアウトを持つ場合は、これを下げるか 0 に設定してください。待機の途中で接続を閉じたクライアントは、HTTP/1.1 でも HTTP/2 でも、即座にその場所を返します。したがって、タイムアウトして接続を閉じるバランサーが、既に放棄した試行で待機セットを埋めることはありません。サーバーに見えないのは、接続を閉じずに待つのをやめたクライアントです — そのクライアントは受け入れられるか予算が尽きるまで場所を保持し続け、先にワーカーが空けばそのスクリプトは誰のためでもなく実行されます — したがって、予算を前段にあるもののタイムアウトより短く保つことには、依然として価値があります |
QUEUE_MAX_WAITING |
初期ワーカー数 × 128、上限は MAX_CONNECTIONS / 2 |
キュースロットを待って同時に待機できるリクエストの最大数。これを超えたリクエストは待機せず即座に拒否されます。各待機者は、待機している間ずっと接続と完全にバッファ済みのリクエストボディを保持するため、これは報われる見込みのある待機ではなく、保持されるリソースに対する制限です。デフォルトに適用される MAX_CONNECTIONS / 2 の上限が制限するのはバックログのこの部分だけです。キュー内および実行中のリクエストも同じように接続を保持するため、サーバーが受け入れと拒否のために実際に確保している余裕は、MAX_CONNECTIONS に対する PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING から導かれます — 後述を参照。サイズは自身のハンドラーレイテンシから決めてください — こちらも後述。0 = 自動、1 未満にはなりません |
QUEUE_MAX_WAITING_BYTES |
67108864(64 MiB) |
待機中のリクエストが合計で保持できるリクエストボディの最大バイト数。ボディが合計をこれより先へ押し上げるようなリクエストは、待機せず即座に 529 で拒否されます。ボディを持たないリクエストがこれを理由に拒否されることはありません。QUEUE_MAX_WAITING は同じセットをリクエスト数で制限しますが、それはサイズについて何も語りません — ボディはリクエストがキューに到達する前に完全にバッファされるため、数だけで止めるなら待機セットは QUEUE_MAX_WAITING × 10 MiB を抱えることができてしまいます。大きなボディのバーストを切り捨てるのではなく吸収すべきアップロード中心のアプリケーションでは引き上げ、メモリ上限のあるコンテナでは引き下げてください。0 = 自動 |
待機セットのサイズを決める
有意義に待機できるリクエストの数は、プールがどれだけ速くそれらを処理し終えるかから導かれます。W 個のワーカーとハンドラーの所要時間 T ミリ秒なら、プールは 1 ミリ秒あたり W / T 件のリクエストを受け入れられるため、B ミリ秒の予算はおよそ W × B / T 件の待機者を入れられます。それを超えたものはすべて、予算をまるごと待った挙げ句に拒否され、その間ずっと接続とバッファ済みのリクエストボディを保持します。
デフォルト値はこれを計算できません — サービスレートは起動時には分からないからです — ので、意図的に寛大になっています。これは速いハンドラーには合っています。プールが深いバックログを予算内で余裕をもって掃けるからです。一方、遅いハンドラーにははるかに大きすぎます。8 ワーカー、デフォルトの 1 秒予算、200 ミリ秒のハンドラーでは、時間内に受け入れられるのは約 40 件だけなのに、デフォルトは最大 1024 件を待機させます。
QUEUE_MAX_WAITING=40これを W × B / T の近くに設定すると、余剰分は 1 秒後の 529 ではなく即時の 529 になります。QUEUE_WAIT_TIMEOUT_MS を短くしても、もう一方の項によって同じことが達成できます。両者はトレードオフの関係にあり、遅いハンドラーでは通常、予算を小さくする方が良いレバーです。クライアントが拒否を待つ時間も制限するからです。
このセットはバイト単位でも重ねて制限されます。各待機者は待機している間ずっとリクエストボディをメモリに保持するため、リクエスト数で数える上限は、保持されるメモリをトラフィック任せにします。同じ 1024 件の待機者でも、ボディのない GET ではコストはゼロ、アップロードではギガバイト単位です。QUEUE_MAX_WAITING_BYTES はその合計を直接制限します — これを超えると、ボディを持つリクエストは待機させられるのではなく即座に拒否され、ボディのないリクエストは通常どおり待機を続けます。バーストを切り捨てるのではなく吸収すべきアップロード中心のアプリケーションはより大きな値を、メモリ上限が固いコンテナはより小さな値を求めます。どちらの上限による拒否も oxphp_admission_refused_total で別々にカウントされる(数に対しては waiting_full、メモリに対しては waiting_bytes)ため、メトリクスがどのノブに手を伸ばすべきかを教えてくれます。
accept ループのための余裕を確保する
リクエストは、到着した瞬間から応答されるまで、その間に何をしていようと、接続 — そして MAX_CONNECTIONS のパーミットの 1 つ — を保持します。キュースロットはワーカーがリクエストを取り出した瞬間、スクリプトが実行される前に解放されるため、これは 3 つの別々の集団をカバーします。
- ワーカー内で実行中: 少なくとも
PHP_WORKERS件。ワーカーモードでは 1 つのスレッドがファイバーを多重化するため、それ以上になります。 - キュー内: 最大
QUEUE_CAPACITY件。 - 受け入れ待ちで待機中: 最大
QUEUE_MAX_WAITING件。
MAX_CONNECTIONS に由来する上限を持つのは 3 番目だけです。PHP_WORKERS + QUEUE_CAPACITY + QUEUE_MAX_WAITING は MAX_CONNECTIONS を下回るように保ってください。これを超えると、障害モードはより悪い方へ変わります。accept ループは接続の処理を始める前に接続パーミットを取得するため、PHP のパスがすべてを保持してしまうと accept ループは停止し、その時点で到着したクライアントは 529 の代わりにまったく応答を得られません — ロードバランサーはこれを死んだインスタンスと見分けられず、一方 INTERNAL_ADDR 上のヘルスプローブはどちらの制限も経由しないため、グリーンのままです。
サーバーは起動時にこれをチェックし、合計が予算に達すると、すべての値を挙げて警告します。oxphp config --check も同じものを報告しますが、判定結果や終了コードは変わりません。警告の解消は十分条件ではなく必要条件として扱ってください。アイドル状態の keep-alive 接続、静的ファイルのリクエスト、進行中のハンドシェイクもパーミットを保持しますが、いずれも起動時には数えられないからです。
この警告への通例の入り口は MAX_CONNECTIONS を下げることです。QUEUE_CAPACITY のデフォルトはワーカー数から決まり、それに追従して下がらないためです — 7 ワーカーと MAX_CONNECTIONS=1000 では、予算 1000 に対して 7 + 896 + 500 になります。どのノブに手を伸ばすかに注意してください。QUEUE_MAX_WAITING をデフォルトのままにして MAX_CONNECTIONS だけを引き上げても警告は解消されません。そのデフォルトは MAX_CONNECTIONS の半分であり、一緒に上がっていくからです — 7 ワーカーでは合計は 1799 に落ち着き、条件は MAX_CONNECTIONS=1800 以上でようやく解消されます。QUEUE_CAPACITY を下げるか、QUEUE_MAX_WAITING を明示的に設定したうえで予算を引き上げてください。
動的プール(MIN:MAX)では、実行中の項は最小値 — キューのデフォルトの算出元と同じ数 — なので、最大まで成長したプールは合計が示すより多くを保持します。
この比較は接続数を数えますが、予算はリクエストによって消費されます。そのため HTTP/2 中心のデプロイメントは、正当にこの合計を上回ることがあります。1 つの接続は最大 H2_MAX_CONCURRENT_STREAMS 件のリクエストを運ぶため、同じバックログが接続のごく一部によって保持されるからです。そこではこの警告は想定内です。標準の予算のままの大きなプールも警告に達します — 自動サイズの 39 ワーカーのプールは 10 000 に対して 39 + 4992 + 4992 になります — こちらは無視するのではなく、対処する価値があります。
静的ワーカーと動的ワーカー
固定プールにするには、PHP_WORKERS に単一の数値を設定します。
PHP_WORKERS=8 # Fixed 8 workers
PHP_WORKERS=0 # Auto-detect: CPU / 2 (min 1)自動スケーリングにするには、PHP_WORKERS に MIN:MAX を設定します。
PHP_WORKERS=2:16 # Scale between 2 and 16 workers
PHP_WORKERS=4:0 # 4 minimum, auto-detect maximum (CPU × 2)
PHP_WORKERS=0:16 # auto-detect minimum (CPU / 4, min 1), 16 maximum動的モードでは、OxPHP はすべてのワーカーがビジー状態のときにワーカーをスケールアップし、ワーカーが PHP_WORKERS_IDLE_SECONDS より長くアイドル状態だったときにスケールダウンします。
ワーカーモード
| 変数 | デフォルト | 説明 |
|---|---|---|
WORKER_MAX_MEMORY_MIB |
0 |
ワーカーがリサイクルされるまでの、ワーカーごとの最大メモリ(MiB)。0 = 無制限 |
WORKER_MODE_ENABLED=true を設定し、ENTRY_FILE をワーカーのブートストラップスクリプト(例: ENTRY_FILE=worker.php や ENTRY_FILE=../worker.php)に向けます。すると PHP プロセスはリクエストをまたいで生き続け、ブートストラップの状態(オートローダー、データベース接続)をメモリ上に保持します。ワーカーは WORKER_MAX_MEMORY_MIB を超えると自動的にリサイクルされ、またアプリケーションが Worker::scheduleExit() を呼び出したときにはオンデマンドでリサイクルされます。以前のリリースにあった WORKER_MAX_REQUESTS のつまみは非推奨となり無視されます。どちらも設定しないか、Worker::scheduleExit() へ移行してください。
非推奨: INDEX_FILE と WORKER_FILE
レガシーの INDEX_FILE と WORKER_FILE 変数は、後方互換性のために今も解析されます。設定されると起動時に WARN ログ行を出力し、新しいモデルへマッピングされます。
| レガシー | 現在の同等設定 |
|---|---|
INDEX_FILE=index.php |
ENTRY_FILE=index.php |
INDEX_FILE=index.html |
ENTRY_FILE=index.html |
WORKER_FILE=/path/worker.php |
WORKER_MODE_ENABLED=true ENTRY_FILE=/path/worker.php |
古い設定と新しい設定の両方が指定された場合は、ENTRY_FILE / WORKER_MODE_ENABLED が優先されます。移行はご都合のよいタイミングで行ってください。非推奨の形式は将来のリリースで削除される予定です。
SAPI / PHP
| 変数 | デフォルト | 説明 |
|---|---|---|
SUPERGLOBALS_ENABLED |
true |
スクリプトの実行前に PHP のスーパーグローバル($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER, php://input)を設定します。偽値を設定すると、この設定処理をスキップします — その場合、リクエストデータはオブジェクト API(oxphp_http_request())経由でのみ利用できます。オブジェクト API を直接利用し、リクエストごとにスーパーグローバルを構築するコストを避けたいアプリケーションに便利です |
タイムアウト
| 変数 | デフォルト | 説明 |
|---|---|---|
HEADER_TIMEOUT_SECONDS |
5 |
接続後に HTTP ヘッダーを受信するまでの最大秒数(Slowloris 対策) |
DRAIN_TIMEOUT_SECONDS |
25 |
グレースフルシャットダウン中に処理中の接続を待つ最大秒数 |
PHP の実行時間は、OxPHP の環境変数ではなく、PHP 自身の max_execution_time ini ディレクティブ(および実行時の set_time_limit())によって制限されます。
レート制限
| 変数 | デフォルト | 説明 |
|---|---|---|
RATE_LIMIT |
0(オフ) |
時間ウィンドウあたり、IP ごとの最大リクエスト数。0 はレート制限を無効化します |
RATE_WINDOW_SECONDS |
60 |
レート制限のウィンドウ長(秒) |
セキュリティ
| 変数 | デフォルト | 説明 |
|---|---|---|
FRAME_OPTIONS |
SAMEORIGIN |
クリックジャッキング対策。SAMEORIGIN は同一オリジンのページによるフレーム化のみを許可、DENY はすべてのフレーム化をブロック、off は無効化(フレーム化を独自の CSP で管理する場合に使用)。それ以外の値は起動時の警告とともにデフォルトの SAMEORIGIN にフォールバックします。すべてのレスポンスに X-Frame-Options と Content-Security-Policy: frame-ancestors の両方を設定します。発行されるヘッダー値、サーバーヘッダーがアプリケーション設定のヘッダーにどう譲るか、値の選び方の指針については、下のクリックジャッキング対策を参照してください |
TRUSTED_PROXIES |
(未設定) | 信頼済みリバースプロキシのネットワーク(カンマ区切りの CIDR、または private)。設定すると、OxPHP は Forwarded(RFC 7239)または X-Forwarded-For ヘッダーから、最も右側の非信頼アドレスを採用するアルゴリズムで実際のクライアント IP を抽出します。また X-Forwarded-Proto と X-Forwarded-Host を処理して $_SERVER['HTTPS']、REQUEST_SCHEME、SERVER_NAME、SERVER_PORT に反映します。未設定 = 機能は無効 |
PHP_DENY_PATHS |
(未設定) | .php ファイルを直接 URI 経由で決して実行してはならない、カンマ区切りの glob パターン(例: /uploads/**,/cache/**,/admin/legacy.php)。パターンはディレクトリ全体または単一のファイルを対象にできます。直接マッピングのモード(Traditional と SPA)で適用されます。Framework と Worker モードでは、任意の .php ファイルを直接実行することがないため、起動時に警告を出して無視されます。ディレクトリインデックスの解決(/uploads/ → uploads/index.php)を通じて到達するスクリプトもカバーします。直接の .php URI については、マッチングがディスク I/O の前に行われるため、拒否されたパスはファイルの有無にかかわらず同じレスポンスを返します(存在を推測できるオラクルになりません)。レガシー名の PHP_DENY_DIRS は非推奨のエイリアスとして受け付けられ、起動時に WARN を出力します。PHP 実行拒否リストを参照 |
PHP_DENY_FALLBACK |
404 |
PHP_DENY_PATHS にマッチしたときに返す内容。400–599 の HTTP ステータス(ERROR_PAGES_DIR と組み合わせてカスタム HTML を返す)、または DOCUMENT_ROOT 内の PHP フォールバックスクリプトを指す / で始まる URI パスのいずれか。フォールバックスクリプトは $_SERVER で OXPHP_DENIED_PATH と OXPHP_DENIED_PATTERN を受け取ります。起動時に検証されます。スクリプトは存在し、DOCUMENT_ROOT 内に正規化され、かつそれ自身が PHP_DENY_PATHS にマッチしてはなりません(ループ防止) |
SYMLINK_ALLOW_PATHS |
(未設定) | シンボリックリンクが DOCUMENT_ROOT の外へ抜け出すことを許可する、カンマ区切りの絶対パスのリスト。各項目はディスク上にすでに存在している必要があります。相対パスや存在しないパスは起動を中止させます。未設定 = シンボリックリンクの抜け出しは一切許可しません。シンボリックリンク許可パスを参照 |
特殊値 private は、すべての RFC-1918 プライベートネットワーク、ループバック、およびリンクローカルアドレス(IPv4 と IPv6)に展開されます: 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8、169.254.0.0/16、::1/128、fc00::/7、fe80::/10。
クリックジャッキング対策
クリックジャッキングは、敵対的なページがあなたのサイトを不可視の <iframe> に埋め込み、ユーザーに見えないものをクリックさせる攻撃です。「はい、アカウントを削除します」ボタン、ワンクリック購入、OAuth の「認可」プロンプトなどです。防御は、誰が(もし誰かいるなら)あなたのページをフレーム化してよいかをブラウザに伝えることです。これを制御するのが FRAME_OPTIONS です。
フレーム化を支配するヘッダーは 2 つあります — レガシーの X-Frame-Options(すべてのブラウザが理解します)と、モダンな Content-Security-Policy: frame-ancestors(両方が存在する場合はこちらが優先されます)です — ので、OxPHP は両方を発行し、ポリシーは古いブラウザでも新しいブラウザでも等しく保たれます。単一の FRAME_OPTIONS 値は、対応するペアにマッピングされます。
FRAME_OPTIONS |
X-Frame-Options |
Content-Security-Policy |
誰がページをフレーム化できるか |
|---|---|---|---|
SAMEORIGIN(デフォルト) |
SAMEORIGIN |
frame-ancestors 'self' |
同一オリジンのページのみ |
DENY |
DENY |
frame-ancestors 'none' |
誰も不可。自分自身のページでさえ不可 |
off |
(送信されない) | (送信されない) | 誰でも — サーバーからのフレーム化制限なし |
値の選び方。 デフォルトは SAMEORIGIN です。クリックジャッキングが実際に依存しているクロスオリジンのフレーム化をブロックしつつ、自分のページ同士の埋め込み — 多くのアプリが正当に行っていること(管理画面のプレビュー、ダッシュボードのウィジェット、同一オリジンでホストされる決済コンポーネント)— は引き続き許可します。サイト上の何ひとつ、自分自身によってさえフレーム化されることを意図していない場合は、最も厳格な姿勢として DENY を選んでください。off を選ぶのは、アプリケーションが設定する完全な Content-Security-Policy によって自分でフレーム化を管理する場合だけです — 後述を参照。
外部オリジンによるフレーム化。 どちらの X-Frame-Options 値も、特定の許可オリジンを名指しすることはできません(ALLOW-FROM は標準から削除されました)。指名したサードパーティにページのフレーム化を許可するには、FRAME_OPTIONS=off を設定し、アプリケーションに明示的な frame-ancestors リストを持つ独自の Content-Security-Policy を発行させてください。例: header("Content-Security-Policy: frame-ancestors 'self' https://partner.example.com");
アプリケーションのヘッダーが優先されます。 サーバーのヘッダーはフォールバックであり、レスポンスがそのようなヘッダーを持たない場合にのみ適用されます。PHP の header() で独自の X-Frame-Options や Content-Security-Policy を設定するアプリケーションは、それをそのまま保持します。フレーム化に関する 2 つのヘッダーは 1 つのポリシーとして扱われるため、サーバーがアプリケーションと矛盾することは決してありません。
- アプリが
X-Frame-Optionsを設定している場合、OxPHP は自身のframe-ancestorsフォールバックをスキップします(サーバーの CSP はモダンブラウザでアプリの選択を上書きしてしまうためです)。 - アプリが
frame-ancestorsディレクティブを含むContent-Security-Policyを設定している場合、OxPHP は自身のX-Frame-Optionsフォールバックをスキップします(より厳しいサーバーのX-Frame-Optionsは、CSP を無視するレガシーブラウザで過剰にブロックしてしまうためです)。
同じ優先順位は、OxPHP がすべてのレスポンスに nosniff として設定する X-Content-Type-Options にも適用されます。アプリケーションが設定した値はそのまま保持されます。何かの効果を持つ値は nosniff だけであることに注意してください — アプリケーションがそれ以外の値で上書きすると、MIME スニッフィング保護は静かに無効になります。
TLS
| 変数 | デフォルト | 説明 |
|---|---|---|
TLS_CERT |
(未設定) | PEM 形式でエンコードされた TLS 証明書のパス。TLS を有効化するには TLS_CERT と TLS_KEY の両方を設定する必要があります |
TLS_KEY |
(未設定) | PEM 形式でエンコードされた TLS 秘密鍵のパス |
TLS_MIN_VERSION |
1.2 |
受け付ける最低の TLS プロトコルバージョン: 1.2 または 1.3。TLS が有効でない場合でも、起動時に(および oxphp config --check によって)検証されます — 非 UTF-8 バイトを含め、それ以外の値は致命的な起動エラーになります。空の値は未設定として扱われます |
HTTP/2
| 変数 | デフォルト | 説明 |
|---|---|---|
H2_MAX_CONCURRENT_STREAMS |
PHP_WORKERS_MAX × 4(最小 32) |
HTTP/2 接続あたりで同時に開けるストリームの最大数 |
H2_MAX_PENDING_RESET |
20 |
接続が閉じられるまでにキューへ積まれる RST_STREAM フレームの最大数(Rapid Reset 対策) |
H2_MAX_HEADER_LIST_BYTES |
65536 |
リクエストあたりのデコード後ヘッダーの最大合計バイト数 |
H2_KEEPALIVE_INTERVAL_SECS |
20 |
HTTP/2 PING フレームの間隔(秒)。0 で無効化 |
H2_KEEPALIVE_TIMEOUT_SECS |
10 |
接続を閉じる前に PING の応答を待つ秒数 |
静的ファイル
| 変数 | デフォルト | 説明 |
|---|---|---|
STATIC_MAX_AGE |
30d |
静的ファイルの Cache-Control: max-age。30s、5m、2h、30d、1w、1y、秒数のみ(3600)、またはヘッダーを無効化する off を受け付けます。非推奨の STATIC_CACHE_TTL を置き換えます。 |
STATIC_REVALIDATE |
off |
ブール値 — ブール値を参照。真値を設定すると、メモリ内のコンテンツキャッシュで mtime による再検証を有効化します。ファイルの更新時刻はファイルごとに(リクエストごとではなく)最大でも 3 秒に 1 回だけ再チェックされ、古いエントリは自動的に破棄されるため、変更は 3 秒以内に反映されます。非推奨の STATIC_CACHE(off が逆の意味だった)を置き換えます。 |
COMPRESSION_LEVEL |
4 |
Brotli 圧縮の品質(0–11)。0 で圧縮を無効化 |
ロギング
| 変数 | デフォルト | 説明 |
|---|---|---|
LOG_LEVEL |
info |
ログの詳細度: trace、debug、info、warn、error |
ACCESS_LOG |
(未設定) | リクエストごとのアクセスログ: all = すべてのリクエスト、error = 4xx/5xx のみ、未設定 = オフ |
ACCESS_LOG は all または error を受け付けます。アクセスログを完全に無効化するには未設定のままにします。
可観測性
| 変数 | デフォルト | 説明 |
|---|---|---|
INTERNAL_ADDR |
(未設定) | 内部サーバー(/health、/metrics、/config)のアドレス。未設定のときは内部サーバーを起動しません。ポートのみの値(:9090 や 9090)は 127.0.0.1 にバインドします。ホスト外へ公開するには、明示的に 0.0.0.0:9090 をバインドしてください |
INTERNAL_ALLOW_IPS |
(未設定) | 内部サーバー向けの、カンマ区切りの CIDR/IP 許可リスト。リスト外のピアは /metrics、/config、およびプラグインパスで 403 を受け取ります。ヘルスプローブ(/health、/healthz、/readyz、/startupz とそれらの長い形式)は引き続きアクセス可能です。未設定/空 = すべて許可。ループバックは暗黙には含まれません — localhost からのアクセスを維持するには 127.0.0.1/32 を列挙してください。不正な形式のリストは起動を中止させます |
ERROR_PAGES_DIR |
(未設定) | {status}.html という名前のカスタムエラーページ(例: 404.html、503.html)を含むディレクトリ |
MAX_QUERY_BODY |
524288 |
内部クエリエンドポイント向けのリクエストボディの最大サイズ(バイト)(512 KiB) |
TRACE_CONTEXT |
false |
ブール値 — ブール値を参照。真値のとき、W3C Trace Context の伝播を有効化します。traceparent/tracestate ヘッダーを読み取り、$_SERVER を介して PHP に転送します |
OpenTelemetry
| 変数 | デフォルト | 説明 |
|---|---|---|
OTEL_ENABLED |
false |
OpenTelemetry のスパンエクスポートを有効化します。自動的に TRACE_CONTEXT=true を設定します。ブール値 — ブール値を参照 |
OTEL_EXPORTER_OTLP_PROTOCOL |
grpc |
エクスポートプロトコル: grpc または http/protobuf |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4317(gRPC)または http://localhost:4318(HTTP) |
OTLP コレクターのエンドポイント |
OTEL_EXPORTER_OTLP_TIMEOUT |
10000 |
エクスポートのタイムアウト(ミリ秒) |
OTEL_EXPORTER_OTLP_HEADERS |
(未設定) | 認証ヘッダー: key=value,key2=value2 |
OTEL_SERVICE_NAME |
oxphp |
エクスポートされるスパンでのサービス名 |
OTEL_SERVICE_VERSION |
(未設定) | サービスバージョンの属性 |
OTEL_RESOURCE_ATTRIBUTES |
(未設定) | 追加のリソース属性: env=prod,region=us-east-1 |
OTEL_TRACES_SAMPLER |
parentbased_traceidratio |
サンプリング戦略: always_on、always_off、traceidratio、parentbased_always_on、parentbased_always_off、parentbased_traceidratio |
OTEL_TRACES_SAMPLER_ARG |
1.0 |
比率ベースのサンプラー向けのサンプリング比率(0.0–1.0) |
範囲外または不正な OTEL_TRACES_SAMPLER_ARG の値は [0.0, 1.0] にクランプされ、warn レベルでログに記録されます。未知の OTEL_TRACES_SAMPLER の値は parentbased_traceidratio にフォールバックし、ログに記録されます。
APM
| 変数 | デフォルト | 説明 |
|---|---|---|
OTEL_APM_ENABLED |
false |
APM を有効化します: 自動インストルメンテーション、エラーキャプチャ、および PHP トレーシング SDK。OTEL_ENABLED=true が必要です。ブール値 — ブール値を参照 |
OTEL_APM_SLOW_QUERY_MS |
100 |
スロークエリのしきい値(ミリ秒)。これを超えるデータベースクエリには oxphp.db.slow=true スパン属性が付きます |
OTEL_APM_DB_CAPTURE_PARAMS_ENABLED |
false |
バインドパラメータを db.params スパン属性に記録します。パラメータに機密データが含まれる可能性がある場合は、本番環境で無効にしてください。ブール値 — ブール値を参照 |
OTEL_APM_STACKTRACE_MAX_BYTES |
8192 |
exception.stacktrace 属性の最大サイズ(バイト)。上限を超えると、スタックトレースは末尾から切り詰められ …(truncated) マーカーが付きます。0 で切り詰めを無効化 |
OTEL_APM_MESSAGE_MAX_BYTES |
4096 |
exception.message 属性の最大サイズ(バイト)(デフォルトは New Relic の属性ごとの値の上限に合わせています)。上限を超えると、メッセージは末尾から切り詰められ …(truncated) マーカーが付きます。0 で切り詰めを無効化 |
APM が有効なとき、OxPHP は 34 個の内部 PHP 関数(PDO、mysqli、cURL、Redis、Memcached、ファイル I/O)を自動的にフックして子スパンを作成します。oxphp_apm_*() PHP 関数は、APM が有効かどうかにかかわらず登録されます — 無効なときは安全な no-op になります。
非同期ワーカー
| 変数 | デフォルト | 説明 |
|---|---|---|
ASYNC_WORKERS |
0(無効) |
専用の非同期ワーカースレッドの数。0 のとき、非同期関数(oxphp_async など)は登録されますが、呼び出すと OxPHP\Async\AsyncException をスローします。バックグラウンドタスクの実行を有効にするには正の値を設定します |
ASYNC_QUEUE_CAPACITY |
ASYNC_WORKERS × 64 |
非同期キューで保留できるタスクの最大数。0 = 自動(ワーカー数 × 64) |
ASYNC_MAX_FIBERS |
256 |
ワーカーごとの、同時実行する非同期タスクファイバーの上限。プロセス全体での処理中(キュー待ち + 実行中)の上限は ASYNC_MAX_FIBERS × ASYNC_WORKERS です。これを超えるディスパッチは即座に OxPHP\Async\AsyncException で拒否されるため、ファンアウトの合成がデッドロックすることはありません |
非同期ワーカープールは、PHP からディスパッチされた撃ちっぱなし(fire-and-forget)のバックグラウンドタスクを処理します。これは PHP ワーカープールとは別物で、標準的なリクエスト処理には必要ありません。
これら 3 つの変数のいずれかに不正な形式の値(例: ASYNC_WORKERS=8x)があると起動エラーになります — デフォルトにフォールバックすると、プールが静かに無効化されたり誤設定されたりするおそれがあるためです。ちょうど空の値は未設定として扱われます。
ランタイムフック
| 変数 | デフォルト | 説明 |
|---|---|---|
RUNTIME_HOOKS |
(無効) | ブロッキングする PHP 組み込み関数を、ファイバーを中断する実装に置き換えるオプトイン機能。1/true/all はすべてのフックカテゴリを有効化します。カンマ区切りのリストは特定のカテゴリを有効化します(例: RUNTIME_HOOKS=sleep,streams) |
| カテゴリ | フックする対象 |
|---|---|
sleep |
ネイティブの sleep() と usleep() が、oxphp_sleep()/oxphp_usleep() とまったく同じように現在のファイバーを中断します |
streams |
2 つの待機が、ワーカースレッドを釘付けにする代わりに現在のファイバーを中断します。tcp:// ソケットストリーム上のブロッキング読み取りと、stream_select() です。読み取りは 1 つのソケットでブロックするクライアント — fsockopen()、stream_socket_client()、HTTP ストリームラッパー、mysqlnd(PDO_MySQL、mysqli)、phpredis — をカバーし、stream_select() は複数を同時に待つループをカバーします。どちらの場合もコード変更は不要です。それ以外の方法で待つクライアントは影響を受けません(後述) |
フックは、ワーカーモードのリクエストファイバーと非同期タスクファイバーの内側で効果を発揮します。ファイバーの外側(traditional/framework/SPA のリクエストコンテキスト、CLI)では、引数バリデーションエラーを含め、元のネイティブな挙動が保たれます。sleep フックを有効にすると、sleep() を呼び出すサードパーティのコードはワーカースレッドを釘付けにしなくなります — コード変更は不要です。フックされたスリープの最中に非同期タスクをキャンセルすると OxPHP\Async\AsyncException で巻き戻され、フックされた sleep() は常に 0 を返します(ネイティブ組み込みのシグナル割り込み時の戻り値は発生しません)。
streams フックがカバーする範囲
streams フックが協調的にするのは、PHP ソケットストリーム上のブロッキング読み取りと、stream_select() 内の待機です。これを当てにする前に、あなたのクライアントがこの 2 つのいずれかの方法で待っていることを確認してください。よくあるクライアントのいくつかはそうではなく、それらにとっては何も変わりません。
- ext/curl。
curl_exec()とcurl_multi_*は PHP ストリームの下でソケットと直接会話するため、フックの目に触れることはありません。これは curl 上に構築されたすべての HTTP クライアントに当てはまります — デフォルトハンドラーの Guzzle もその 1 つです。curl クライアントは代わりにストリームラッパーハンドラーへ向けることができ、そちらは PHP ストリームを経由します。 socket_select()。 これは ext/sockets という、生のディスクリプタ上の別の API であり、フックされません。stream_select()はフックされます。同じことはstream_socket_accept()内の待機にも当てはまり、こちらもフックされません。socket_export_stream()から得たストリーム。 これらは PHP に閉じた別の ops テーブルを持っており、拡張からはパッチできません。unix://、udp://、udg://ストリーム。 同じ理由です。その ops テーブルは PHP に閉じています。- 暗号が有効になった後の
ssl://とtls://。stream_socket_enable_crypto()が成功する前は、SSL ストリームの読み取りはプレーンなソケット読み取りに委譲されるため中断します。ハンドシェイク以降は中断しません。 - 接続の確立と DNS 解決。 接続を開いたまま保持するクライアントはクエリのたびに恩恵を受けますが、接続のセットアップは恩恵を受けません。
localhost経由で到達する MySQL。 MySQL クライアントはlocalhostを「unix ソケットを使え」と読みますが、それはtcp://ストリームではありません — 代わりに DSN に127.0.0.1と書いてください。これは PDO_MySQL と mysqli の両方に当てはまり、見落としやすい点です。すべては動き続け、ただ恩恵がないだけだからです。- 書き込み。 待つのは読み取りだけです。読み取りの準備完了状態は、1 つのファイバーがディスクリプタを所有し他の何もそれを排出しない限り持続しますが、送信バッファの空きはピアによって与えられたり取り消されたりするため、書き込み可能で起こされたファイバーは、書き込みが実行される頃には窓が再び閉じているのに気づくことがあり、その後 PHP はフックの有無に関係なくタイムアウトいっぱいまでスレッドをブロックします。したがって、ソケットバッファを満たす書き込みは、フックなしのときとまったく同じように振る舞います。実際にはこのコストは小さいものです。時間がかかるのは応答を待つことであって、クエリをカーネルへ渡すことではないからです。(書き込みが監視されるのは 1 つのことのためだけです。その接続が現在どのファイバーの交換を運んでいるか — 下の共有接続に関する注記を参照してください。他のファイバーが使っていない接続 — 通常の場合はすべての接続がそうです — では、書き込みは手つかずのネイティブパスを実行します。)
この範囲内では、フックはネイティブの契約を保ちます。ソケットタイムアウト(stream_set_timeout()、default_socket_timeout)は変わらず適用され、タイムアウトした読み取りは引き続き stream_get_meta_data() を通じて timed_out を報告し、ストリームの同一性には手が付けられないため、socket_import_stream() などは動き続けます。タイムアウトには 1 つの制限があります。期限はスケジューラのティックごとに 1 回しか調べられないため、発火は早くても次のティックです — 最良でワーカーモードで 100 マイクロ秒、非同期プールで 50 マイクロ秒、負荷がかかればそれ以上です。ティックはワーカーが実行しているものが続く限り続くからです。非同期プールは何も起きていないときにはそれより長く休止しますが、保持している期限を越えて休止することはありません。読み取りまたは書き込みの期限は、休止をちょうどその上に着地するよう短縮するため、長く待つことでタイムアウトが遅くなることはありません。default_socket_timeout が 60 秒であれば、これはほとんどのデプロイメントで観測できるものではありません。
stream_select() は、その 3 つの配列が名指しするディスクリプタを待ってから、タイムアウトをゼロに設定して呼び出しを PHP に渡すという形でフックされます。したがって、観測できるものはすべて依然として PHP が決めます。戻り値のカウント、準備完了のストリームだけに配列を書き換えること、警告、そして引数エラーです。配列の中に、フックが肩代わりしないものが含まれている場合はいつでも、待機はスキップされ、呼び出しはフックなしのときとまったく同じように実行されます。既にデータがバッファされている読み取りストリーム(これには stream_select() はディスクリプタを見ずにバッファから答えます)、ディスクリプタをまったく持たないストリーム、生きたストリームでない要素(たとえば配列に残された閉じられたストリーム — PHP はこれに待機ではなくエラーで答えます)、カーネルが準備完了を監視してくれないディスクリプタ(通常のファイルがよくあるケースで、尋ねた瞬間に準備完了として数えられます)、あるいは FD_SETSIZE 以上のディスクリプタ(これは PHP 自身の select() が門前払いします)です。最後のものは形式的な話ではなく現実の上限です。ビジーなワーカーは 1024 を超えるオープンディスクリプタを保持しえますし、そのうちの 1 つを名指しする stream_select() は、フックの有無にかかわらず同じように失敗します。
並行ファイバー間で共有される接続
並行ファイバー間で共有される 1 つの接続は、OxPHP がガードするクライアントにとって安全ですが、何も得られません。 これはエッジケースではなく、ワーカーモードアプリケーションの通常の形です。WordPress、Laravel、Symfony は、ワーカーの起動時にデータベースとキャッシュのクライアントを一度だけ開き、同じものをすべてのリクエストに渡します。データアクセス層を書き直さない限り、ファイバーごとに 1 接続を求めることはできません。
クライアントプロトコルは交換の連続です — コマンドを書き、答えを読む — が、接続上には 1 つの交換がどこで終わるかを示すものは何もありません。読み取りで待機しているファイバーは交換の真っ最中で待機しているのであり、そこに 2 つ目のファイバーのコマンドが着地するとプロトコルは壊れます。この 2 つのクライアントはそこで異なる形で失敗し、どちらもうまくは失敗しません。mysqlnd は接続状態を追跡しており、何も送信する前にコマンドを拒否します。一方 phpredis にはそのようなチェックがなく、2 つのファイバーは互いの応答を読み合います — あるリクエストのデータが、エラーを一切上げずに別のリクエストへ返されるのです。
そのためファイバーは、接続を使用する前にそれをクレームします。これが行われなければならない 2 つのレベルの両方でです。バイト列の順序を保つソケット ops と、PDO および mysqli のクライアントエントリポイントです。mysqlnd の拒否はあらゆる I/O の前に起こるため、ソケット側では間に合わずそれを防げないからです。同じ接続に到達した別のファイバーは、クライアントレベルではそれが手放されるのを待ちます。そこで保持されているのは接続の同一性だけだからです。ソケットレベルでは待たずに、ソケットタイムアウトが拒否されるのと同じやり方で拒否されます。他人のストリーム上の操作の内側で中断されたファイバーは、所有者が解放するかもしれないポインタを保持していることになるからです。phpredis もまったく同じ理由で、メソッドごとにクライアントレベルでガードされています。クライアントレベルがクレームするのは接続そのものであって、それを保持する PHP オブジェクトではありません。したがって、複数の PDO オブジェクトを通じて到達される永続接続は 1 つとして数えられ、PDO::connect() — PDO ではなくドライバー自身のサブクラスを返します — で開かれた接続も他と同じようにカバーされます。
したがって、このフックの利得は、ファイバーが自分自身のために開く接続に属するものだと読んでください。独自の HTTP やデータベース呼び出しを行う非同期タスク、独自のクライアントを開くリクエストです。共有接続が得るのは、待っている間のワーカースレッドの返却であり、他のリクエストはその接続上にない仕事を実行できます。共有接続自身の交換は、フックがオフのときとまったく同じように、順番に 1 つずつ実行されます。知っておく価値のある境界が 4 つあります。
- 一度クエリしたファイバーは、その接続をリクエストの終わりまで保持します。 リクエストの終わりが、交換の終わりを確実に過ぎた最初の瞬間だからです。他の何かで待機している間も保持し続けるため、クエリした後に、同じ接続を必要とする自分自身の仕事を待つリクエストは、自分自身を待っていることになります。両者は先へ進む代わりに、下の制限時間で解けます。
- 待機は常に制限されます。
max_execution_timeとdefault_socket_timeoutの小さい方によってです。どちらも設定しなければその制限は 30 秒です。サーバー SAPI はエンジンのデフォルト — 前者は 30、後者は 60 — を取るからです。default_socket_timeoutの 60 になるのはmax_execution_timeが 0 の場合だけです。max_execution_timeはリクエストが現在持っている値として読まれます。set_time_limit()は、リクエストが自分はどれだけ実行してよいかを表明する手段だからです。default_socket_timeoutはプロセスが起動したときの値として読まれます。これはソケット操作のデフォルト期限であり、あるリクエストが自分の呼び出しのためにそれを狭めた — ライブラリがよくやることで、後に残していきがちなものです — としても、同じワーカー上で後続するリクエストのためのこの制限を短くしてはならないからです。制限を超えると、呼び出しはガードなしの挙動にフォールバックし、理由をサーバーログに記します。PDO と mysqli の場合はクライアントに渡され、交換の途中で発行されたコマンドに対するクライアント自身の拒否が、アプリケーションが既に処理しているエラーになります。一方 phpredis にはそのような拒否がなく、代わりに他人の応答を読んでしまうため、RedisExceptionを送出して何も送信しません。ソケットレベルの競合には独自の制限がありません。決して待たないからです。操作はタイムアウトと同じやり方で即座に失敗するため、stream_get_meta_data()はtimed_outを報告します。したがって、互いが待つものを互いに保持し合う 2 つのファイバーは、永遠に待ち合う代わりにその制限で解けます。 - いくつかのケースは意図的にカバーされないまま残されています。 リクエストをまたいで保持されるステートメントや結果オブジェクト。これにはクレームされた呼び出しが先行しません。以前のリクエストで prepare されたステートメント上の
PDOStatement::execute()は、クレームがまったくないときと同じように振る舞います。別のファイバーが交換の途中にある永続接続上に 2 つ目のハンドルを構築すること。PDO はプールされた接続を渡す前にそれが生きているかを確認しますが、そのチェックは交換の途中では失敗し、PDO は接続を破棄することで応じます。生のソケット上に手書きされたプロトコル — コマンドを書き、別の何かで中断し、後で応答を読む — 。ソケットレベルが拒否するのは、保持者が応答そのもので待機している間だけであり、その 2 点の間に別のファイバーが接続を乗っ取れるからです。上の 3 つのクライアントは、手書きプロトコルには相当物のないクライアントレベルのクレームによってここでカバーされています。そして、ファイバーの外でまったく接続に到達するもの、たとえばリクエスト間にエンジンのサイクルコレクタが実行するデストラクタ。これはどのクレームからも見えません。 - 別のリクエストが読み取りで待機している共有接続を閉じると、そのリクエストは終了します。
500と、何が起きたかを記したログ行つきでです。クレームは 2 つの交換を隔てておくことはできますが、接続を生かしておくことはできず、待機中のリクエストの応答は、もはや存在しない接続の上にあります — したがって、解放済みメモリが今保持しているものを返すのではなく、リクエストを終わらせるのが誠実な答えです。どの呼び出しがこれを行えるかはクライアントに依存し、待つのはそのうち 1 つだけです。mysqli::close()とmysqli_close()はクレームされる呼び出しなので、保持者を待ち、その待機が尽きたら閉じます。Redis::close()もクレームされますが、閉じる代わりにRedisExceptionを送出します。それ以外はすべて即座に閉じます — 生のストリーム上のfclose()、そして重要なことに、close()メソッドをそもそも持たない PDO です。接続はハンドルへの最後の参照を落とすこと(unset($pdo)、再代入、スコープ外へ出すこと)で解放され、そのパスは、クレームが一切参照されないエンジン自身のオブジェクト破棄の内側で実行されます。したがって、共有 PDO ハンドルを再代入する再接続ヘルパーは、mysqliの同等物が得る制限付きの待機なしに、古い接続で待機していたリクエストを終了させます。リクエストは、自身の読み取り期限 — mysqlnd ではmysqlnd.net_read_timeoutで、初期設定では 1 日 — ではなく、クローズが起きた時点ですぐに告げられます。再接続を強制するために共有接続を閉じるアプリケーション(WordPress の$wpdb->check_connection()が典型です)は、その瞬間にそこで待機していたリクエストが、誤ったデータを返すのではなく失敗することを想定しておくべきです。
もう 1 つの境界があります。ユーザーランドのファイバースケジューラ(AMPHP、Revolt)の下でこのフックを動かすと、ブロッキング I/O にフォールバックします。そのようなスケジューラが開始したファイバーは独自のコンテキスト上で実行され、OxPHP のスケジューラはそれを再開できません。そのためフックはこれを検出し、どちらのスケジューラも壊さないようネイティブのパスを取ります。これは、ユーザーランドのスケジューラがリクエストの内側で開始するファイバーの話です。リクエスト自身のファイバーは OxPHP が駆動するものであり、ワーカーモードではそれは本物の Fiber です — リクエスト内の Fiber::getCurrent() はそれを返します — ので、並行リクエストを区別することだけが必要なライブラリは、フォールバックなしで動きます。同じ事実が、Revolt がワーカーモードのリクエスト内からイベントループの実行を拒否する理由でもあります。
有効化とコスト
1、true、all は、streams を含むすべてのカテゴリを有効にします。したがって、sleep フックのために既に RUNTIME_HOOKS=1 を設定しているデプロイメントは、自身では何も編集していないのに、アップグレードとともにソケットのフックを開始します。それが望みでない場合は、カテゴリを明示的に列挙してください(RUNTIME_HOOKS=sleep)。streams を有効にすると、起動時に PHP のストリーム ops テーブルの 1 エントリがメモリ上でパッチされます。ページは見つけたときの状態に戻されますが、元の保護状態を判定できないプラットフォームでは書き込み可能なままになります — わずかなハードニングの喪失であり、サーバーログに報告されます。
実測されたコスト: 他に何も待機していないワーカーではソケットの往復あたり約 3–5 マイクロ秒、64 個のファイバーがディスクリプタで待機している状態で約 5–6 マイクロ秒、200 個で約 7–11 マイクロ秒です。準備完了状態は、カーネルが待機と待機の間も保持するセットを通じて解決されるため、この数字が追跡するのは、いくつのディスクリプタが準備完了になったかであって、いくつが待っているかではありません。アイドル状態のワーカーは、固定間隔でスリープして次のティックで準備完了に気づくのではなく、それらのディスクリプタを待ちます。これは非常に重要です。盲目的なスリープには、代わりに往復あたりおよそ 2 ミリ秒のコストがかかっていました。
幅の広い stream_select() には、狭い場合にはないオーバーヘッドが伴います。呼び出しが名指しするすべてのディスクリプタが、待機の前に登録され、後に削除されるからです。待機そのものを除いて測定すると — すべてのディスクリプタが既に読み取り可能で、呼び出しは即座に戻り、オーバーヘッドだけが残ります — ディスクリプタ 1 個ではフックなしの 5 マイクロ秒に対して約 6 マイクロ秒、64 個では 9 に対して 56 マイクロ秒、200 個では 16–21 に対して 130–150 マイクロ秒です。おおよそディスクリプタあたり 0.65 マイクロ秒です。
これは同じ仕事の減速ではなく、呼び出しあたりの固定コストとして読んでください。実際に待つ stream_select() — select ループはそのために書かれるものです — の前ではこれは霞みます。1 ミリ秒の待機があれば、200 ディスクリプタの数字でさえ呼び出しの約 1 割にすぎず、それが買い戻すのはワーカースレッドです。フックなしの呼び出しは待機の間ずっとそれを占有します。例外となる形が 2 つあり、それらではこのカテゴリはオフのままにしておく方が良いでしょう。ほとんど待つことのない多数のディスクリプタにわたる呼び出し — 回収できるスレッド時間がないからです — と、リクエスト自体がイベントループである場合 — スレッドにはどのみち他に実行するものがないからです。1 つの接続を待つクライアント — mysqlnd、phpredis、HTTP ストリームラッパー — は表の最上部に位置し、そこではオーバーヘッドは 1 マイクロ秒前後です。
RUNTIME_HOOKS と oxphp_sleep() の比較
このフックはファーストパーティのプリミティブを置き換えるものではありません — 両者は異なるケースをカバーします。
- 自分で書くコードでは
oxphp_sleep()/oxphp_usleep()に手を伸ばしてください。これらは環境フラグなしで、ワーカーモードで無条件にファイバーを中断し、oxphp_sleep()は小数秒(oxphp_sleep(0.25))を受け付けます — ネイティブのsleep()(整数秒)には表現できない精度です。 - 編集できないコード — ネイティブの
sleep()/usleep()を直接呼び出すフレームワークやベンダーライブラリ — のためにはRUNTIME_HOOKS=sleepを有効にしてください。デフォルトでは無効で、ファイバーの内側でのみ適用され、ネイティブの契約を保ちます(フックされたsleep()は依然としてintを取り、0を返します)。
自分のハンドラーで RUNTIME_HOOKS に頼ると、その協調性がコードではなくデプロイメント設定に結び付けられます。そこでは明示的な oxphp_sleep() を優先してください。
共有状態
プロセス内の並行処理プリミティブ(OxPHP\Shared\Counter、Map、Channel、Mutex、Once、Pool、Atomic、Flag、Registry)。API の概要は共有状態を参照してください。
| 変数 | デフォルト | 説明 |
|---|---|---|
SHARED_ENABLED |
true |
ブール値 — ブール値を参照。OxPHP\Shared\* サブシステム全体のマスタースイッチ |
SHARED_MAX_ENTRIES |
100000 |
すべての Shared エントリを合わせたグローバルな上限。これを超える挿入は CapacityException で失敗します |
SHARED_MAX_BYTES |
1073741824(1 GiB) |
すべての Shared エントリにわたる推定メモリのグローバルな上限 |
SHARED_SOFT_LIMIT_RATIO |
0.7 |
使用量が SHARED_MAX_BYTES / SHARED_MAX_ENTRIES のこの割合を超えたときに、最も優先度の低い処理から切り捨てを開始します |
SHARED_METRICS_ENABLED |
true |
ブール値。oxphp_shared_* の Prometheus エクスポジションを切り替えます |
SHARED_INTROSPECTION_ENABLED |
true |
ブール値。内部サーバー上の /__ox_shared/* イントロスペクション API を切り替えます |
SHARED_INTROSPECTION_PREVIEW_ENABLED |
true |
ブール値。イントロスペクションのレスポンスでの値のプレビューを切り替えます(プレビューが機密データを漏らすおそれがある場合は無効にします) |
SHARED_CYCLE_DETECT_DEPTH |
16 |
循環チェック時の BFS の深さ。正当に深いグラフの場合は引き上げてください |
SHARED_CYCLE_DETECT_EDGES |
10000 |
循環チェック時に走査するエッジ数。正当に密なグラフの場合は引き上げてください |
SHARED_MAX_VALUE_SIZE |
1048576(1 MiB) |
値ごとのサイズ上限。これより大きい値の挿入は即座に失敗します |
SHARED_MAX_CHANNEL_BYTES |
67108864(64 MiB) |
チャネルごとの合計ペイロード上限 |
SHARED_POISON_STRICT |
false |
ブール値。真値のとき、Mutex/Once のクロージャ内でのパニックは、可能な限りの回復を試みる代わりに、プリミティブを恒久的に汚染(poison)します |
SHARED_LOCK_DIAGNOSTICS |
off |
ロック競合の診断: off、count、または trace |
SHARED_LOCK_POLL_INTERVAL_MS |
100 |
ロック診断のサンプラーが使用するポーリング間隔 |
SHARED_PREVIEW_STRING_LIMIT |
256 |
/__ox_shared/preview プレビューでの文字列ごとの切り詰め。バイト単位(文字境界で切られます) |
SHARED_PREVIEW_ARRAY_LIMIT |
20 |
/entry?id=… プレビューでサンプリングするエントリ数 |
プロファイリング
xhprof / speedscope のトレースを出力するサンプリングプロファイラー。出力形式とビューアー連携についてはプロファイリングを参照してください。
| 変数 | デフォルト | 説明 |
|---|---|---|
PROFILER_ENABLED |
false |
ブール値 — ブール値を参照。マスタースイッチ。他のすべての PROFILER_* 変数も起動時に解析されるため、タイプミスは即座に表面化します |
PROFILER_SAMPLE_RATE |
0.0 |
リクエストがサンプリングされる確率(0.0–1.0)。範囲外の値はクランプされます |
PROFILER_INTERNAL |
false |
ブール値。真値のとき、内部サーバー(/health、/metrics、プラグインエンドポイント)へのリクエストもサンプリングの対象になります |
PROFILER_AUTH_TOKEN |
(未設定) | オプションのベアラートークン。設定すると、oxphp_profiler_* PHP 関数はオンデマンドプロファイリングを有効にするために、このトークンを持つリクエストを要求します |
PROFILER_MAX_SPANS |
50000 |
リクエストごとのプロファイルスパンの上限。上限を超えるプロファイルは切り詰められます |
PROFILER_MAX_DEPTH |
256 |
サンプルごとにキャプチャする最大コールスタック深さ。65535 を上限にハードキャップされます |
PROFILER_OUTPUT_DIR |
/tmp/oxphp-profiles |
ディスク上のプロファイルファイル用のディレクトリ |
PROFILER_OUTPUT_FORMATS |
xhprof,speedscope |
ディスクに書き込む出力形式のカンマ区切りリスト |
PROFILER_DISK_MAX_PER_SEC |
10 |
1 秒あたりにディスクへ書き込むプロファイルファイル数のレート制限 |
PROFILER_RETENTION_COUNT |
100 |
PROFILER_OUTPUT_DIR に保持するプロファイルファイルの最大数。古いファイルは削除されます |
PROFILER_EXPORT_URL |
(未設定) | プロファイルを POST するリモートエンドポイント。設定すると、PROFILER_OUTPUT_FORMATS が空でない限りディスク書き込みも引き続き行われます |
PROFILER_EXPORT_FORMAT |
xhprof |
PROFILER_EXPORT_URL への POST に使うワイヤーフォーマット |
PROFILER_EXPORT_AUTH_TOKEN |
(未設定) | 各エクスポートリクエストとともに送られるオプションのベアラートークン |
PROFILER_EXPORT_XHGUI |
(自動検出) | ブール値。エクスポートペイロードを XHGui 互換でラップするよう強制します。未設定 = PROFILER_EXPORT_URL のパスが /run/import で終わるときに自動検出(ホストやクエリのヒントはマッチ対象外) |
PROFILER_EXPORT_BUGGREGATOR |
(自動検出) | ブール値。Buggregator のエンベロープを強制します。未設定 = PROFILER_EXPORT_URL のパスが /api/profiler/store で終わるときに自動検出。エンベロープは常に xhprof を出力するため、PROFILER_EXPORT_FORMAT はこの場合無視されます(xhprof 以外の値は警告を出しますが致命的ではありません)。PROFILER_EXPORT_XHGUI とは相互排他で、両方を有効にすると起動エラーになります |
PROFILER_EXPORT_APP_NAME |
(未設定) | プロジェクトのグルーピング用の Buggregator の app_name |
PROFILER_EXPORT_TAGS |
(未設定) | key=value,key2=value2 形式の Buggregator の tags。不正な形式のトークン、空のキー、重複するキーは起動エラーになります |
設定例
開発
LISTEN_ADDR=127.0.0.1:8080
DOCUMENT_ROOT=./public
LOG_LEVEL=debug
ACCESS_LOG=all
PHP_WORKERS=1
INTERNAL_ADDR=127.0.0.1:9090本番(Framework)
LISTEN_ADDR=0.0.0.0:80
DOCUMENT_ROOT=/var/www/html/public
ENTRY_FILE=index.php
PHP_WORKERS=8
QUEUE_CAPACITY=1024
LOG_LEVEL=warn
ACCESS_LOG=error
MAX_CONNECTIONS=10000
INTERNAL_ADDR=127.0.0.1:9090
RATE_LIMIT=100
RATE_WINDOW_SECONDS=60
TRUSTED_PROXIES=private
HEADER_TIMEOUT_SECONDS=5
DRAIN_TIMEOUT_SECONDS=25
COMPRESSION_LEVEL=4
STATIC_MAX_AGE=30d本番(ワーカーモード)
LISTEN_ADDR=0.0.0.0:80
DOCUMENT_ROOT=/var/www/html/public
WORKER_MODE_ENABLED=true
ENTRY_FILE=../worker.php
PHP_WORKERS=8
WORKER_MAX_MEMORY_MIB=128
QUEUE_CAPACITY=1024
LOG_LEVEL=warn
ACCESS_LOG=error
INTERNAL_ADDR=127.0.0.1:9090TLS
LISTEN_ADDR=0.0.0.0:443
TLS_CERT=/etc/ssl/oxphp/cert.pem
TLS_KEY=/etc/ssl/oxphp/key.pem
DOCUMENT_ROOT=/var/www/html/public
ENTRY_FILE=index.php稼働中の設定を確認する
内部サーバーが稼働しているとき、/config エンドポイントに問い合わせると、解決済みの設定を確認できます。
curl -s http://localhost:9090/config | jq .{
"listen_addr": "0.0.0.0:80",
"document_root": "/var/www/html/public",
"entry_file": "/var/www/html/public/index.php",
"log_level": "warn",
"executor_type": "sapi",
"php_workers": "8",
"tokio_workers": 4,
"queue_capacity": 1024,
"queue_wait_timeout_ms": 1000,
"queue_max_waiting": 1024,
"queue_max_waiting_bytes": 67108864,
"max_connections": 10000,
"drain_timeout_seconds": 30,
"header_timeout_seconds": 5,
"rate_limit": 100,
"rate_window_seconds": 60,
"tls_enabled": true,
"compression_level": 4,
"access_log": "all",
"max_query_body": 524288,
"worker_mode_enabled": false,
"worker_max_memory_mib": 0,
"static_max_age": 2592000,
"static_revalidate": false,
"async_workers": 0,
"async_queue_capacity": 0,
"async_max_fibers": 256,
"async_in_flight_cap": 0,
"trace_context": true,
"superglobals_enabled": true,
"trusted_proxies": false,
"plugins": {
"otel": {
"enabled": true,
"protocol": "grpc",
"service_name": "oxphp"
},
"apm": {
"enabled": true,
"slow_query_ms": 100,
"db_capture_params": false,
"hooks_registered": 34
}
}
}配信される /config レスポンスは、内部の Config 表現が保持しているいくつかのキーを取り除きます。TLS 証明書と鍵のパスは決して出力されず(tls_enabled が TLS が有効かどうかを示します)、internal_addr と error_pages_dir も削除されます — これらは攻撃者の助けとなり、メトリクススクレイパーには不要な、デプロイのトポロジーとファイルシステムのパスだからです。
関連項目
- ルーティング — ルーティングモードと
ENTRY_FILEの挙動 - ヘルスチェック — 内部サーバーのエンドポイント
- メトリクス — Prometheus 互換のメトリクスリファレンス
- グレースフルシャットダウン —
DRAIN_TIMEOUT_SECONDSがシャットダウンに与える影響 - TLS — TLS のセットアップと証明書の要件
- レート制限 — IP ごとのレート制限の詳細
- ワーカーモード — 永続的な PHP ワーカーのアーキテクチャ
- 圧縮 — Brotli 圧縮の詳細
- 静的ファイル — キャッシュとファイル配信
- 分散トレーシング & APM — OTel エクスポート、自動インストルメンテーション、PHP トレーシング SDK