PHPのリクエスト処理は、1本ごとにフレームワークを起動して、応答したら全部捨てる形で成り立ってきました。Laravel Octaneはこの前提を外し、起動済みのアプリケーションをメモリに置いたまま次のリクエストへ回します。速くなるのは起動処理の分だけですが、そこが応答時間の過半を占める案件もあります。反面、捨てられていた状態が残るため、これまで動いていたコードが壊れる書き方も出てきました。この記事では、2026年8月29日時点の2.19.1と13.x公式ドキュメントで、仕組み・導入手順・状態リークの3類型・採否の線引きを確認します。
まとめ:Octaneが効く負荷条件と3つのサーバの選び分け
先に結論を置きます。版に依存する記述はlaravel/octane 2.19.1と13.x公式ドキュメントで確認しました。
- Octaneはアプリケーションを一度だけ起動してメモリに保持し、同じインスタンスへリクエストを流し続ける実行モデルです。ブートの回数がリクエスト数からワーカー数へ減ります
- 速くなる範囲は起動処理に限られます。遅いSQLやN+1、外部API待ちはそのまま残るため、まず計測してボトルネックの内訳を見てから判断してください
- 対応サーバはFrankenPHP・Swoole(Open Swoole含む)・RoadRunnerの3系統。並行タスク・tick・OctaneキャッシュはSwooleでのみ動きます
- 常駐ゆえの落とし穴は、コンテナ注入・リクエスト注入・設定注入の3類型と、staticプロパティへの追記によるメモリリークです
- ワーカー数の既定はCPUコア数で、1ワーカーがアプリ1本分のメモリを持ちます。搭載メモリからの逆算が要ります
- –max-requestsの既定500は、取りこぼしたリークを定期的に流す保険として残しておく設定です
- デプロイのたびにoctane:reloadを流さないと、メモリ上の旧コードが応答を返し続けます
Laravel Octaneとは:起動を1回に畳んでメモリへ常駐させる実行モデル
Octaneの理解は、何が速くなって何が変わらないのかを分けるところから始まります。ここが曖昧なまま入れると、期待した数字が出ないか、本番で不可解な不具合を踏みます。
PHP-FPMはリクエストごとに起動し、Octaneはワーカーごとに1回だけ起動する
PHP-FPM構成では、リクエストが届くたびにオートローダが走り、サービスプロバイダのregisterとbootが実行され、設定とルートが読み込まれます。応答を返した時点で状態は破棄され、次のリクエストはまた同じ道をたどります。この作り方には、状態が絶対に混ざらない安全性がありました。
Octaneはこの起動処理をワーカーの立ち上げ時に1回だけ実行し、以後は同じアプリケーションインスタンスへリクエストを渡し続けます。公式ドキュメントも、アプリを一度ブートしてメモリに保持する形と説明しています。サービスプロバイダのregisterとbootが動くのはワーカー起動時のみで、2本目以降のリクエストでは呼ばれません。フレームワーク側の状態についてはOctaneがリクエスト間でリセットしますが、アプリケーションが自分で作ったグローバルな状態までは面倒を見ない、という線引きになっています。
速くなるのは起動処理だけで、遅いSQLやN+1はそのまま残る
削れるのはブート時間です。応答時間の内訳が「ブート10ms+DB 200ms+外部API 300ms」の案件なら、Octaneで縮むのは先頭の10msだけになります。逆に「ブート40ms+処理10ms」のような軽量APIでは、削減幅が全体の8割近くを占める計算です。
導入前にやるべきは、この内訳を数字で押さえる作業です。開発環境ならクエリ本数と実行時間をLaravel Telescopeの使い方|設定と本番運用の判断基準の手順で拾い、本番の傾向はLaravelでPrometheus+Grafana監視を実装|メトリクス収集・可視化の方法でメトリクス化しておくと、導入後の効果を同じ物差しで比較できました。ブートが支配的でない案件に入れても、増えるのは運用の制約だけです。
対応サーバはFrankenPHPとSwooleとRoadRunnerの3系統
Octane自体はサーバを内蔵しません。実際にHTTPを受けてワーカーを回すのは、FrankenPHP・Swoole(およびOpen Swoole)・RoadRunnerのいずれかです。Octaneはこの3系統に共通のインターフェースを被せ、Laravel側から同じコマンドで扱えるようにする層と考えると位置づけがはっきりします。
導入形態も3者で違いました。FrankenPHPとRoadRunnerはGo製の実行バイナリで、PHPの拡張を足さずに動かせます。一方のSwooleはpeclで入れるPHP拡張のため、PHP本体のビルド環境に手が入る点が判断材料です。並行タスクやOctaneキャッシュを使うかどうかも、この選択で決まります。
導入手順:composerでの追加からsupervisorでの常駐化まで
手順そのものは短く、詰まるのは常駐化と前段のWebサーバです。開発環境で動いたあとに何が足りないのかを、先に見取り図として持っておきます。
composer requireとoctane:installで導入し、サーバを選ぶ
パッケージの追加と設定ファイルの生成は2コマンドで終わります。2.19.1のrequireはphp ^8.1.0で、Laravelは10.10.1以上の系列に対応します。手元のPHPが要件を満たすかどうかはLaravel 12の対応PHPバージョン|要件・サポート期限・11からの移行で確認してください。
composer require laravel/octane
php artisan octane:install
php artisan octane:start
octane:installを実行すると使用サーバを尋ねられ、設定ファイルが配置されます。FrankenPHPを選んだ場合はバイナリの取得までOctaneが引き受けます。RoadRunnerは初回起動時に取得を提案する形です。Swooleを選ぶ場合だけは、先にpeclで拡張を入れておく段取りが要ります。起動後の既定ポートは8000番です。
開発環境をDockerに寄せている案件では、Laravel Sailとは|sail upやsail downコマンドの使い方と導入手順の構成へ組み込みます。サービス定義のSUPERVISOR_PHP_COMMANDを差し替え、開発サーバの代わりにoctane:startを走らせる形です。RoadRunner併用時はspiral/roadrunner-cliとspiral/roadrunner-httpの追加、rr get-binaryによるバイナリ取得が別途要りました。
開発中は–watchを付けないとコードの変更が反映されない
常駐型の最初の戸惑いがここです。ルートを足してもコントローラを直しても、ブラウザを再読み込みした画面は変わりません。メモリ上のアプリが起動時のコードのままだからです。開発中は–watchを付けて起動し、ファイル変更を検知したら自動で再起動させます。
npm install --save-dev chokidar
php artisan octane:start --watch
–watchはNode環境とchokidarに依存します。監視対象は設定ファイルのwatch項目で調整できました。なお、この挙動を「毎回リロードすれば同じ」と考えて–max-requests=1で運用すると、リクエストごとに再起動が入るため応答時間はPHP-FPM相当まで戻ります。速度の利点を消して運用の複雑さだけが残る設定なので、本番では選びません。
本番ではSupervisorで常駐させ、Nginxから8000番へ渡す
本番のOctaneは、プロセスが落ちたら誰かが起こす仕組みとセットにします。公式のサンプル構成はSupervisorでoctane:startを監視し、autorestartを有効にしたうえで停止待ち時間を長めに取る形です。前段にはNginxかApacheを置き、画像やCSSはWebサーバが返し、それ以外を8000番のOctaneへプロキシします。TLSの終端も前段の役目です。
[program:octane]
command=php /home/forge/example.com/artisan octane:start --server=frankenphp --host=127.0.0.1 --port=8000
autostart=true
autorestart=true
stopwaitsecs=3600
HTTPS配信ではOCTANE_HTTPSをtrueにして、生成されるリンクのスキームを揃えます。この一段を忘れると、前段でTLSを終端している構成でhttpのURLが混ざりました。常駐サーバ・プロセス監視・再起動運用をまとめて引き受ける体制が社内に無い場合は、保守運用 / 内製化支援のような外部の運用設計とセットで検討する形になります。自前で組む前提なら、監視項目にワーカーのメモリ使用量を必ず入れてください。
状態リークの避け方:コンテナ・リクエスト・staticの3つの罠
Octane導入で実際に事故が起きるのはここです。テストは通り、開発環境でも再現せず、本番で数時間経ってから症状が出ます。公式ドキュメントが挙げる注意点は、注入の3類型とメモリリークです。
コンテナやリクエストをコンストラクタへ渡すと古い実体が居座る
シングルトンとして登録したクラスのコンストラクタへサービスコンテナを渡すと、ブート時のコンテナがそのオブジェクトに握られたまま残ります。あとから追加されたバインディングが見えず、解決に失敗する形で表面化しました。リクエストインスタンスを渡した場合はもっと危険で、ヘッダも入力値もクエリ文字列も、最初の1本目のリクエストのものを返し続けます。他人の入力が別の利用者の処理に混ざる、という不具合の作り方そのものです。
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app->make('request'));
});
回避策は2つあります。シングルトン登録をやめて都度解決に変えるか、実体の代わりに解決用のクロージャを渡し、呼び出しのたびに現在の実体を取りにいくかです。グローバルヘルパのrequestやconfig、Container::getInstanceは常に現在のものを返すため、こちらの利用は安全とされています。コントローラのメソッドやルートクロージャでリクエストを型指定する書き方も、従来どおりで構いません。
staticなプロパティへの追記はワーカーが落ちるまで解放されない
リクエストごとに配列へ値を足していくコードは、PHP-FPMなら応答と同時に消えていました。Octaneではワーカーが生きている限り積み上がり続けます。1件あたり数十バイトでも、秒間数百リクエストの環境では時間とともにメモリを食い潰す挙動になります。キャッシュのつもりで持たせたstatic配列、リクエストIDを溜めるロガー、遅延初期化のためのメモ化テーブルあたりが典型例です。
public function index(Request $request): array
{
Service::$data[] = Str::random(10);
return [];
}
対処は、溜めるものをOctaneの外へ出すことに尽きます。共有したい値はRedisや通常のキャッシュストアへ、リクエスト内だけで使う値はインスタンス変数へ移してください。開発中からメモリ推移を眺めておけば、右肩上がりの線が出た時点で原因を絞り込めます。
シングルトンの登録は解決タイミングで挙動が変わるため見直しが要る
設定リポジトリの注入も同じ構図です。ブート時のconfigインスタンスを保持したサービスは、その後に設定値が変わっても古い値を見続けます。ここまでの3類型に共通するのは、「ブート時に確定したもの」を「毎回変わるもの」として扱ってしまう誤りです。
既存アプリを移行するときは、サービスプロバイダのregisterとbootを一通り読み直し、singletonで登録しているバインディングのコンストラクタ引数を点検してください。コンテナ・リクエスト・設定・セッション・認証情報のいずれかが渡っていれば要注意です。移行時のキャッシュ操作をどう組むかはLaravelのキャッシュクリア|cache:clearとoptimize:clearの違いの手順と合わせて決めると、デプロイ手順の整理が一度で済みます。
サーバ別の機能差:並行タスクとtickとOctaneキャッシュの条件
3系統は「どれが速いか」ではなく「何が使えるか」で選びます。ベンチマークの差は構成と負荷特性で入れ替わりますが、機能の有無は仕様として固定されています。
並行タスクとtickとOctaneキャッシュはSwooleでのみ動く
Octaneの付加機能はSwoole(Open Swooleを含む)に紐づいています。並行タスクは複数のクロージャをタスクワーカーへ投げて同時に処理させる仕組みで、1回の呼び出しにつき1024件が上限です。tickは指定秒ごとにコールバックを回す仕組み、Octaneキャッシュはワーカー間で共有できる高速なストアで、いずれもSwooleテーブルが土台になっています。
[$users, $servers] = Octane::concurrently([
fn () => User::all(),
fn () => Server::all(),
]);
Octaneキャッシュとテーブルの中身は、サーバを再起動すると消えます。永続性が要る値の置き場ではありません。全ワーカーから読める短命な共有メモリと考えると、レート制限のカウンタや直近の集計値に向く道具です。
FrankenPHPはHTTP/3とearly hintsを単一バイナリで扱える
FrankenPHPはCaddyを土台にしたGo製のサーバで、early hintsやBrotli、Zstandardといった新しめの機能に対応します。Octaneでこれを選ぶとバイナリの取得と設置まで自動で進むため、導入の摩擦がいちばん小さい選択肢です。公式Dockerイメージも用意され、コンテナ前提の構成にそのまま乗ります。Caddyfileの指定で細かい振る舞いも差し替えられました。
RoadRunnerはGo製バイナリでワーカーを外側から管理する
RoadRunnerはGoで書かれたサーバが親となり、バイナリプロトコル経由でPHPワーカーへ処理を渡します。PHPプロセスは分離されたまま管理されるため、既存のモノリスをそのまま常駐化する場面で扱いやすい構造でした。PHP拡張を足さずに済む点はFrankenPHPと共通します。
| 観点 | FrankenPHP | Swoole | RoadRunner |
|---|---|---|---|
| 導入形態 | Go製バイナリ | PHP拡張(pecl) | Go製バイナリ |
| 並行タスク | 非対応 | 対応 | 非対応 |
| tickと間隔処理 | 非対応 | 対応 | 非対応 |
| Octaneキャッシュ | 非対応 | 対応 | 非対応 |
| 向く構成 | コンテナ配信 | 高スループットAPI | 既存モノリス |
運用設定:ワーカー数とmax-requestsとデプロイ時のreload
常駐型に移すと、設定値の意味が変わります。従来はプロセス数の話だったものが、メモリ設計と再起動計画の話になります。
ワーカー数の既定はCPUコア数で、メモリから逆算して決め直す
Octaneは既定でCPUコア数と同じ数のリクエストワーカーを起動します。1ワーカーがアプリ1本分のメモリを抱えるため、1ワーカー200MBのアプリを8ワーカーで動かせば1.6GBが常時占有される計算です。–workersで絞るか、サーバのメモリを積むかの二択になります。Swooleを使う場合は–task-workersでタスク用のワーカー数も別に指定します。
php artisan octane:start --workers=4 --task-workers=6 --max-requests=500
max-requestsの既定500はリークの保険として残しておく
–max-requestsは、指定した本数を処理したワーカーをgracefulに再起動させる設定です。既定値は500で、取り切れなかったメモリリークを定期的に洗い流す安全弁として働きます。値を大きくすれば再起動の頻度は下がりますが、リークの蓄積時間は伸びます。小さくすれば安全側に倒れる代わりに、再起動のたびにブート時間が戻ってきました。まずは既定のまま動かし、メモリ推移を見てから調整する順序が無難です。
実行時間の上限は設定ファイルのmax_execution_timeで、既定は30秒。0を入れると無制限になります。長時間のアップロードや重い集計を同期処理で受けている案件では、この値の見直しが要ります。変更後はサーバの再起動が必要でした。
デプロイのたびにoctane:reloadを流さないと旧コードが残る
常駐型で最も見落とされるのがここです。コードを配置しただけでは、メモリ上の古いアプリが応答を返し続けます。デプロイ手順の最後にoctane:reloadを置き、ワーカーをgracefulに入れ替えてください。停止はoctane:stop、稼働状況の確認はoctane:statusです。なおForgeでサイトを作る場合、Octaneとゼロダウンタイムデプロイは併用しない前提になります。条件はLaravel Forgeとは|自動化される範囲・料金・Cloudとの選択基準で整理しました。
php artisan octane:reload
php artisan octane:status
この一連の常駐管理を自前で持ちたくない場合、マネージドでOctaneを動かす選択肢もあります。判断材料はLaravel Cloudとは|構成・料金・ForgeとVaporの違いと採用条件にまとめてあるので、自社サーバでの常駐運用と並べて比較してください。
受託開発でLaravel Octaneを採る条件と見送る案件の線引き
ここからは判断の話です。Octaneは入れれば速くなる部品ではなく、アプリの書き方と運用体制に条件を課す実行モデルなので、条件が揃わない案件では見送るのが正解になります。
採るべきなのはAPIが秒間数百回叩かれ、起動が支配的になっている案件
採用条件は3つ揃ったときです。第一に、計測した応答時間の内訳でブートが3割以上を占めていること。第二に、同時接続が多く、リクエストあたりの処理が軽いこと。第三に、サーバをこちらで管理できて、Supervisorやコンテナのプロセス監視を運用に組み込めること。この3つが揃う典型例は、モバイルアプリのバックエンドAPI、社内システムの参照系エンドポイント、外部サービスからのWebhook受け口でした。
条件が揃えば効果は素直に出ます。ブートが40msの案件で毎秒300リクエストを捌いているなら、削れるのは秒あたり12秒分のCPU時間で、サーバ台数の削減として回収できる規模です。
見送るのは共有レンタルサーバと古い拡張に依存している既存案件
見送る判断も明確に置きます。まず、PHPプロセスの常駐が許されない共有レンタルサーバでは物理的に動きません。次に、管理画面中心で同時接続が数人という業務システムでは、削れるブート時間の絶対量が小さく、運用の複雑さのほうが上回ります。そして、グローバル変数やstaticなキャッシュを前提に組まれた歴史の長いコードベースでは、移行にかかる点検工数が効果を超えがちでした。
運用体制の面も見ておきます。デプロイのたびにreloadを流す、メモリ推移を監視する、リークが疑われたらワーカーを落とす。この手順を回せる担当が案件側にいないまま入れると、原因不明の不具合が半年後に噴き出します。
移行判断は負荷試験と静的解析でリーク箇所を洗ってから判断する
実際の移行は、段階を踏むと事故が減ります。開発環境で–watch付きで動かし、テストを通す。次にサービスプロバイダのsingleton登録を全件点検し、コンテナ・リクエスト・設定の注入を潰す。そのうえで、本番相当の負荷を数十分かけ続けてメモリ推移が横ばいになるかを見ます。ここで右肩上がりの線が出たら、–max-requestsで誤魔化さずに原因のコードを直してください。
切り替え当日は、PHP-FPM構成をすぐ戻せる状態で残しておくと判断が軽くなります。前段のNginxのプロキシ先を切り替えるだけで往復できる形なら、想定外の挙動でも数分で退避できます。
よくある質問
Laravel Octaneを入れると具体的にどれくらい速くなりますか?
削減されるのはブート時間の分だけなので、倍率はアプリごとに変わります。応答時間に占めるブートの割合を計測し、そこがゼロに近づくと考えるのが実務的な見積もりです。ブートが40msで総応答50msの軽量APIなら効果は大きく、DBに200ms使う画面では体感差が出ません。
SwooleとFrankenPHPとRoadRunnerはどれを選べばよいですか?
並行タスクやtick、Octaneキャッシュを使う予定があるならSwoole一択です。使わないなら、コンテナ配信が前提の構成ではFrankenPHP、既存のモノリスを常駐化する場面ではRoadRunnerが素直な選択でした。PHP拡張の追加を避けたいなら候補はこの2つです。
Octaneを入れたらキューワーカーは不要になりますか?
不要にはなりません。Octaneが速くするのはHTTPリクエストの処理経路で、非同期の仕事を後回しにする仕組みとは役割が別です。Swooleの並行タスクは同一リクエスト内で複数処理を同時に走らせるもので、失敗時の再試行や遅延実行は持ちません。メール送信や重いバッチは従来どおりキューへ流します。そのキュー側の実装と常駐の手順はLaravel Queueの使い方|ジョブ定義からworker常駐と失敗ジョブ運用までで扱っています。
既存のLaravelアプリをそのままOctaneへ載せられますか?
載る場合もありますが、無検証での切り替えは避けてください。確認すべきはサービスプロバイダのsingleton登録、staticプロパティへの追記、グローバル変数への書き込みの3点です。点検後に本番相当の負荷をかけ、メモリ推移が横ばいになるのを確かめてから切り替えます。
本番でOctaneが落ちたときはどう復旧しますか?
プロセス監視に任せるのが基本形です。Supervisorのautorestartを有効にしておけば、異常終了したワーカーは自動で立ち上がります。前段のWebサーバから旧構成へ切り戻せる経路を残しておけば、原因調査の時間も確保できました。復旧後はメモリ使用量とエラーログを突き合わせ、リークか外部要因かを切り分けます。
関連記事
- Laravel Cloudとは|構成・料金・ForgeとVaporの違いと採用条件:常駐運用を任せる選択肢です
- LaravelでPrometheus+Grafana監視を実装|メトリクス収集・可視化:導入前後の計測手段です
- Laravel Sailとは|sail upやsail downコマンドの使い方と導入手順:検証環境の土台です
- Laravelのキャッシュクリア|cache:clearとoptimize:clearの違い:デプロイ手順の整理に使います