設定リファレンス

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 件を待機させます。

bash
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_WAITINGMAX_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 に単一の数値を設定します。

bash
PHP_WORKERS=8 # Fixed 8 workers PHP_WORKERS=0 # Auto-detect: CPU / 2 (min 1)

自動スケーリングにするには、PHP_WORKERSMIN:MAX を設定します。

bash
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.phpENTRY_FILE=../worker.php)に向けます。すると PHP プロセスはリクエストをまたいで生き続け、ブートストラップの状態(オートローダー、データベース接続)をメモリ上に保持します。ワーカーは WORKER_MAX_MEMORY_MIB を超えると自動的にリサイクルされ、またアプリケーションが Worker::scheduleExit() を呼び出したときにはオンデマンドでリサイクルされます。以前のリリースにあった WORKER_MAX_REQUESTS のつまみは非推奨となり無視されます。どちらも設定しないか、Worker::scheduleExit() へ移行してください。

非推奨: INDEX_FILEWORKER_FILE

レガシーの INDEX_FILEWORKER_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-OptionsContent-Security-Policy: frame-ancestors の両方を設定します。発行されるヘッダー値、サーバーヘッダーがアプリケーション設定のヘッダーにどう譲るか、値の選び方の指針については、下のクリックジャッキング対策を参照してください
TRUSTED_PROXIES (未設定) 信頼済みリバースプロキシのネットワーク(カンマ区切りの CIDR、または private)。設定すると、OxPHP は Forwarded(RFC 7239)または X-Forwarded-For ヘッダーから、最も右側の非信頼アドレスを採用するアルゴリズムで実際のクライアント IP を抽出します。また X-Forwarded-ProtoX-Forwarded-Host を処理して $_SERVER['HTTPS']REQUEST_SCHEMESERVER_NAMESERVER_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 にマッチしたときに返す内容。400599 の HTTP ステータス(ERROR_PAGES_DIR と組み合わせてカスタム HTML を返す)、または DOCUMENT_ROOT 内の PHP フォールバックスクリプトを指す / で始まる URI パスのいずれか。フォールバックスクリプトは $_SERVEROXPHP_DENIED_PATHOXPHP_DENIED_PATTERN を受け取ります。起動時に検証されます。スクリプトは存在し、DOCUMENT_ROOT 内に正規化され、かつそれ自身が PHP_DENY_PATHS にマッチしてはなりません(ループ防止)
SYMLINK_ALLOW_PATHS (未設定) シンボリックリンクが DOCUMENT_ROOT の外へ抜け出すことを許可する、カンマ区切りの絶対パスのリスト。各項目はディスク上にすでに存在している必要があります。相対パスや存在しないパスは起動を中止させます。未設定 = シンボリックリンクの抜け出しは一切許可しません。シンボリックリンク許可パスを参照

特殊値 private は、すべての RFC-1918 プライベートネットワーク、ループバック、およびリンクローカルアドレス(IPv4 と IPv6)に展開されます: 10.0.0.0/8172.16.0.0/12192.168.0.0/16127.0.0.0/8169.254.0.0/16::1/128fc00::/7fe80::/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-OptionsContent-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_CERTTLS_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-age30s5m2h30d1w1y、秒数のみ(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 ログの詳細度: tracedebuginfowarnerror
ACCESS_LOG (未設定) リクエストごとのアクセスログ: all = すべてのリクエスト、error = 4xx/5xx のみ、未設定 = オフ
Note

ACCESS_LOGall または error を受け付けます。アクセスログを完全に無効化するには未設定のままにします。

可観測性

変数 デフォルト 説明
INTERNAL_ADDR (未設定) 内部サーバー(/health/metrics/config)のアドレス。未設定のときは内部サーバーを起動しません。ポートのみの値(:90909090)は 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.html503.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_onalways_offtraceidratioparentbased_always_onparentbased_always_offparentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG 1.0 比率ベースのサンプラー向けのサンプリング比率(0.0–1.0)
Note

範囲外または不正な 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 つあります。

  1. 一度クエリしたファイバーは、その接続をリクエストの終わりまで保持します。 リクエストの終わりが、交換の終わりを確実に過ぎた最初の瞬間だからです。他の何かで待機している間も保持し続けるため、クエリした後に、同じ接続を必要とする自分自身の仕事を待つリクエストは、自分自身を待っていることになります。両者は先へ進む代わりに、下の制限時間で解けます。
  2. 待機は常に制限されます。 max_execution_timedefault_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 つのファイバーは、永遠に待ち合う代わりにその制限で解けます。
  3. いくつかのケースは意図的にカバーされないまま残されています。 リクエストをまたいで保持されるステートメントや結果オブジェクト。これにはクレームされた呼び出しが先行しません。以前のリクエストで prepare されたステートメント上の PDOStatement::execute() は、クレームがまったくないときと同じように振る舞います。別のファイバーが交換の途中にある永続接続上に 2 つ目のハンドルを構築すること。PDO はプールされた接続を渡す前にそれが生きているかを確認しますが、そのチェックは交換の途中では失敗し、PDO は接続を破棄することで応じます。生のソケット上に手書きされたプロトコル — コマンドを書き、別の何かで中断し、後で応答を読む — 。ソケットレベルが拒否するのは、保持者が応答そのもので待機している間だけであり、その 2 点の間に別のファイバーが接続を乗っ取れるからです。上の 3 つのクライアントは、手書きプロトコルには相当物のないクライアントレベルのクレームによってここでカバーされています。そして、ファイバーの外でまったく接続に到達するもの、たとえばリクエスト間にエンジンのサイクルコレクタが実行するデストラクタ。これはどのクレームからも見えません。
  4. 別のリクエストが読み取りで待機している共有接続を閉じると、そのリクエストは終了します。 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 がワーカーモードのリクエスト内からイベントループの実行を拒否する理由でもあります。

有効化とコスト

1trueall は、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\CounterMapChannelMutexOncePoolAtomicFlagRegistry)。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 ロック競合の診断: offcount、または 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。不正な形式のトークン、空のキー、重複するキーは起動エラーになります

設定例

開発

bash
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)

bash
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

本番(ワーカーモード)

bash
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:9090

TLS

bash
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 エンドポイントに問い合わせると、解決済みの設定を確認できます。

bash
curl -s http://localhost:9090/config | jq .
json
{ "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 } } }
Note

配信される /config レスポンスは、内部の Config 表現が保持しているいくつかのキーを取り除きます。TLS 証明書と鍵のパスは決して出力されず(tls_enabled が TLS が有効かどうかを示します)、internal_addrerror_pages_dir も削除されます — これらは攻撃者の助けとなり、メトリクススクレイパーには不要な、デプロイのトポロジーとファイルシステムのパスだからです。

関連項目