---
title: Laravel OctaneはFrankenPHPをどう高速化しているのか？ソースコードから読み解く、高速化の仕組み
tags: 
author: [_ma_me_](https://convert.docswell.com/user/1313108)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/GJWGD9VK72.jpg?width=480
description: Laravel OctaneはFrankenPHPをどう高速化しているのか？ソースコードから読み解く、高速化の仕組み by _ma_me_
published: March 20, 26
canonical: https://convert.docswell.com/s/1313108/Z9N8R9-2026-03-20-223847
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/GJWGD9VK72.jpg)

Laravel OctaneはFrankenPHPを
どう高速化しているのか？
ソースコードから読み解く、高速化の仕組み
1


# Page. 2

![Page Image](https://bcdn.docswell.com/page/4EZLG94N73.jpg)

Laravel Octane（オクタン）とは
Laravelの常駐実行を実現する拡張パッケージ
主な価値: 起動済みアプリの再利用
対応ランタイム: Swoole / RoadRunner / FrankenPHP これらの簡単セットア
ップも提供する
2


# Page. 3

![Page Image](https://bcdn.docswell.com/page/Y76WYK397V.jpg)

対応ランタイムについて
ランタイム
概要
Swoole
PHP拡張。マルチプロセスで動作する非同期ランタイム
RoadRunner
Go製のPHPアプリケーションサーバー
FrankenPHP
Go製WebサーバーCaddy上で動くPHPランタイム
このスライドでは FrankenPHP を中心に解説します
3


# Page. 4

![Page Image](https://bcdn.docswell.com/page/G75MGPLD74.jpg)

Octaneの性能メリット
PHP-FPM と Octane の比較
Laravelを毎回起動するか
起動済み状態を再利用するか
結果として 毎回のブートコスト が変わる
4


# Page. 5

![Page Image](https://bcdn.docswell.com/page/9J29Y63MER.jpg)

Laravelのブート処理概要
これが毎回のブートコストにかかってくる
サービスコンテナの初期化
設定ファイル（.env / config/）の読み込み
サービスプロバイダの登録・起動（register / boot）
ルーティング定義の読み込み
ミドルウェアスタックの構築
ファサード・エイリアスのバインド
5


# Page. 6

![Page Image](https://bcdn.docswell.com/page/DEY4G91PJM.jpg)

PHP-FPM
Octane
毎回 bootで アプリを初期化
諸々の設定読込やルート解決も毎回
やり直す
初回の起動した Laravel アプリをワ
ーカーが保持
2回目以降は ブートを省ける
6


# Page. 7

![Page Image](https://bcdn.docswell.com/page/VJNYGL2M78.jpg)

速さの秘訣は起動済みアプリケーションのキャッシュ
起動済みアプリをどこに保持し、どう安全に使い回すか
php artisan octane:frankenphp
サーバー起動コマンドから追っていきます
7


# Page. 8

![Page Image](https://bcdn.docswell.com/page/YE9PW41WJ3.jpg)

自己紹介
発表者
ma@me
所属
最近の業務
不具合分析
不具合対応
品質改善業務をメインに色々
やってます。最近はNewRelic
が友達
8


# Page. 9

![Page Image](https://bcdn.docswell.com/page/GE8DNQ4RED.jpg)

閑話休題
改めて。起動済みのLaravelをどこに保持し、どう安全に使い回すか
php artisan octane:frankenphp
サーバー起動コマンドから追っていきます
9


# Page. 10

![Page Image](https://bcdn.docswell.com/page/LELMPX627R.jpg)

octane:frankenphpの起動フロー
Laravelの起動処理を受け持つindex.phpが差し代わる
PHP-FPM
OCTANE + FRANKENPHP
リクエスト
起動時（1回のみ）
↓ 毎回
↓
public/index.php
bin/frankenphp-worker.php
↓
↓
Laravel bootstrap
Laravel bootstrap → Worker::$app に保持
↓
リクエストごと（繰り返し）
レスポンス
frankenphp_handle_request() 受信
↓
Worker::handle() — clone → 処理 → flush()
↓
レスポンス → 次を待つ ↺
10


# Page. 11

![Page Image](https://bcdn.docswell.com/page/4JMY4L59JW.jpg)

起動時のコード解説
OCTANE + FRANKENPHP
起動時（1回のみ）
↓
bin/frankenphp-worker.php
↓
Laravel bootstrap → Worker::$app に保持
11


# Page. 12

![Page Image](https://bcdn.docswell.com/page/PJR9WK3979.jpg)

octane:frankenphp 実行時の処理
StartFrankenPhpCommand::handle() が順に実行される
public function handle(...): int
{
・・・
// 環境変数でワーカー数や最大リクエスト数を渡す
$server =
([&#039;frankenphp&#039;, &#039;run&#039;, &#039;-c&#039;, &#039;Caddyfile&#039;], env: [
&#039;CADDY_SERVER_WORKER_COUNT&#039; =&gt; $this-&gt;
(),
&#039;MAX_REQUESTS&#039;
=&gt; $this-&gt;
(&#039;max-requests&#039;),
// ここに環境変数が連なる
]);
new Process
workerCount
option
// Caddy と PHP Worker を起動して繋げる
$this-&gt;
($server, ...);
}
return
runServer
12


# Page. 13

![Page Image](https://bcdn.docswell.com/page/PEXQPL43JX.jpg)

ワーカースレッド起動後の処理（frankenphpworker.php）
// bin/frankenphp-worker.php
new Worker new ApplicationFactory($basePath), new FrankenPhpClient());
boot
$worker =
(
// Laravel を1回だけ起動して保持
$worker-&gt;
();
// リクエストループ
// clone → 処理 → flushの繰り返しs
(
・・・
(...) {
// アプリのclone・リクエスト処理・レスポンス送信
$worker-&gt;
($request, $context);
})
);
while
frankenphp_handle_request
handle
$worker-&gt;
terminate();
13


# Page. 14

![Page Image](https://bcdn.docswell.com/page/3EK9DLZNED.jpg)

Worker::boot() の実装
$worker-&gt;boot() の中身。起動済みアプリを $app プロパティに保持
class Worker implements WorkerContract
{
protected $app; // ここに保持される
// ここで$appに起動済みのアプリを保持する
(
$initialInstances = []):
{
$this-&gt;app = $app = $this-&gt;appFactory-&gt;
(
($initialInstances, [
::class =&gt; $this-&gt;client])
);
public function boot array
array_merge
$this-&gt;
}
void
createApplication
Client
dispatchEvent($app, new WorkerStarting($app));
}
14


# Page. 15

![Page Image](https://bcdn.docswell.com/page/L73WR3DZ75.jpg)

OCTANE + FRANKENPHP
リクエストごと（繰り返し）
frankenphp_handle_request() 受信
↓
Worker::handle() — clone → 処理 → flush()
↓
レスポンス → 次を待つ ↺
15


# Page. 16

![Page Image](https://bcdn.docswell.com/page/87DKY4P4JG.jpg)

リクエスト毎の使いまわし方 clone + flush
//Woker::handle() の実装から抜粋
public function handle(Request $request, RequestContext $context): void
{
// $this-&gt;app を clone して使い捨て用の作業コピー（sandbox）を作る
// → 処理中に状態が変わっても $this-&gt;app は汚れない
::
($sandbox =
$this-&gt;app);
CurrentApplication set
clone
$gateway = new ApplicationGateway($this-&gt;app, $sandbox);
try {
$response = $gateway-&gt;handle($request);
// ...
} finally {
$sandbox-&gt;flush(); // sandbox を後始末して捨てる
CurrentApplication::set($this-&gt;app); // 元のクリーンな状態に戻す
}
}
16


# Page. 17

![Page Image](https://bcdn.docswell.com/page/VJPK6MNVE8.jpg)

毎回後始末もされているので、クリーンで安全...？
17


# Page. 18

![Page Image](https://bcdn.docswell.com/page/2EVV39KREQ.jpg)

要注意ポイント
clone を見て警戒した方は正解
clone $this-&gt;app
$sandbox-&gt;flush();
$sandbox =
18


# Page. 19

![Page Image](https://bcdn.docswell.com/page/57GL3ZQ6EL.jpg)

落とし穴①：ステート汚染
clone はシャローコピーで、ネストしたオブジェクトは参照が共有される。
flush() が解放するのはサンドボックスのバインディングのみ
// サービスプロバイダーでシングルトン登録
$this-&gt;app-&gt;
(
::class, fn() =&gt;
singleton SomeService
new SomeService());
// リクエスト処理中に状態を書き換えると…
$service =
(
::class);
$service-&gt;data[] = $request-&gt;
(&#039;item&#039;); // ← $this-&gt;app 側にも影響する
app SomeService
input
staticで不定なRequestなどのユーザー入力値を扱う場合は要注意
19


# Page. 20

![Page Image](https://bcdn.docswell.com/page/4EQY5LK2JP.jpg)

落とし穴②：リクエスト跨ぎのメモリリーク
flush() はstatic プロパティ・グローバル変数をリセットしない
Worker が長寿命なほど消費メモリが増加する
// static プロパティへの蓄積
class EventCollector
{
public static array $log = [];
public static function record(string $event): void
{
self::$log[] = $event; // リクエストごとに積み上がる
}
}
対策：
MAX_REQUESTS（デフォルト 500）でワーカーを定期再起動
20


# Page. 21

![Page Image](https://bcdn.docswell.com/page/KJ4W9DPP71.jpg)

これが `octane:frankenphp` 実行から
起動済みアプリケーションを保持するまでの流れ
21


# Page. 22

![Page Image](https://bcdn.docswell.com/page/LE1YKZ9X7G.jpg)

FrankenPHPの仕組み
22


# Page. 23

![Page Image](https://bcdn.docswell.com/page/GEWGD9QKJ2.jpg)

Caddy（Go） と PHP Worker の繋ぎ方
実行
入口
Caddy / Go
HTTP リクエスト
を受ける
橋渡し
→
CGo
→
frankenphp_handle_request()
PHP Worker
Laravel を処理し
てレスポンスを返
す
受信は Go、アプリ実行は PHP Worker、その接続点が CGo /
frankenphp_handle_request()
23


# Page. 24

![Page Image](https://bcdn.docswell.com/page/47ZLG9WNJ3.jpg)

FrankenPHP上でのPHPはZend Thread Safety
（ZTS）で動作する
マルチスレッドな Go のスレッド上で
PHP Worker を動かすため、ZTS（Zend Thread Safety）版 で動作する
ここが PHP-FPM や Swoole との差異
ZTS 版ではOctane提供のキャッシュ機能に制限事項が生まれる
24


# Page. 25

![Page Image](https://bcdn.docswell.com/page/YJ6WYKG9JV.jpg)

前提：ZTS ではスレッド間のメモリが分離される
WORKER #2 （スレッド）
WORKER #1 （スレッド）
PHP HEAP （ ZMM ）
PHP HEAP （ ZMM ）
OctaneArrayStore
[ &#039;site-config&#039; → {...} ]
参照不可
OctaneArrayStore
[
] ← Worker #1 の値は見
えない
ZTS は各スレッドに独立したメモリプール（TSRM）を持つ
OctaneArrayStore の格納先は PHP のネイティブな配列（スレッドローカ
ル）
25


# Page. 26

![Page Image](https://bcdn.docswell.com/page/GJ5MGPVDJ4.jpg)

Cache::store(&#039;octane&#039;) の範囲制限
Octane が開発者向けに提供するオプション機能
リクエスト越しに繰返し使う値をワーカー内でキャッシュするための仕組み
// 例: DBから取る設定値をワーカーメモリにキャッシュ
$config =
::
(&#039;octane&#039;)-&gt;
(&#039;site-config&#039;, 60, fn() =&gt;
::
()
);
Cache store
SiteConfig first
remember
26


# Page. 27

![Page Image](https://bcdn.docswell.com/page/9E29Y6XM7R.jpg)

Cache::store(&#039;octane&#039;) の保存先
ZTS のスレッドローカルメモリの制約により、Cache::store(&#039;octane&#039;) は
スレッドローカルな値になる
Cache::store(&#039;octane&#039;)-&gt;put(...)
↓
Swoole
FrankenPHP
↓
↓
OctaneStore
OctaneArrayStore
↓
↓
Swoole\Table
PHP array
プロセス間共有メモリ
スレッドローカル
27


# Page. 28

![Page Image](https://bcdn.docswell.com/page/D7Y4G98PEM.jpg)

ワーカー間共有のもう1つの手段：Octane Tables
Swoole の Swoole\Table を Octane から扱いやすくしたもの
ワーカー間で共有できる固定サイズのメモリテーブル
config/octane.php で事前定義して使う
// config/octane.php
&#039;tables&#039; =&gt; [
&#039;example:1000&#039; =&gt; [
&#039;name&#039; =&gt; [&#039;type&#039; =&gt;
&#039;votes&#039; =&gt; [&#039;type&#039; =&gt;
],
],
Table::TYPE_STRING, &#039;size&#039; =&gt; 1024],
Table::TYPE_INT],
// 利用時
::
$row =
Octane table(&#039;example&#039;)-&gt;set(&#039;row-1&#039;, [&#039;name&#039; =&gt; &#039;Alice&#039;, &#039;votes&#039; =&gt; 5]);
Octane::table(&#039;example&#039;)-&gt;get(&#039;row-1&#039;);
28


# Page. 29

![Page Image](https://bcdn.docswell.com/page/VENYGLQMJ8.jpg)

Octane Tables と FrankenPHP
Swoole
Octane
Tables
共有の仕組
み
✅ 利用可能
Swoole\Table （プロセス間共有メ
モリ）
FrankenPHP
✗ 利用不可
ZTS のスレッドローカルメモリのため
実現不可
Swoole 使用時、Worker間で任意のデータを共有できる
FrankenPHP 使用時、任意のデータをWorker間で共有したい場合
APCu（ZTSで同プロセス内共有）や Redis などを利用する
29


# Page. 30

![Page Image](https://bcdn.docswell.com/page/Y79PW4DWE3.jpg)

⭕️ Octaneのキャッシュ機能がフル活用できない
❌ だからFrankenPHPが遅い
FrankenPHPめっちゃ早いよ！
（宣伝）どこまで違う？！PHP実行環境パフォーマンス対決 - mod_php vs
php-fpm vs Swoole vs FrankenPHP
https://www.docswell.com/s/1313108/ZGGREJ-2025-07-17-000927
30


# Page. 31

![Page Image](https://bcdn.docswell.com/page/G78DNQ3R7D.jpg)

そもそも Octane はプロセスベース向
けに設計された
Octane はもともと Swoole・RoadRunner（プロセスモデル）向けに作られ
た
キャッシュ機構（Cache::store(&#039;octane&#039;) / Octane Tables）は
プロセス間共有メモリを前提に設計されている
FrankenPHP 対応は後から追加 → スレッドモデルのため恩恵を受けにくい
31


# Page. 32

![Page Image](https://bcdn.docswell.com/page/L7LMPXV2JR.jpg)

比較まとめ：Swoole vs
FrankenPHP
Octaneのキャッシュ機能で差異あり
Swoole
FrankenPHP
実行モデル
マルチプロセス
ZTS マルチスレッド（Go +
CGo）
Cache::store(&#039;octane&#039;) の実体
OctaneStore
（ Swoole\Table ）
OctaneArrayStore （PHP
Cache::store(&#039;octane&#039;) のワーカ
○ できる
Octane Tables
✅ 利用可能
ワーカー間共有の手段
Swoole\Table / Octane
ー間共有
Tables
array）
✗ できない（スレッドローカ
ル）
✗ 利用不可
APCu（ZTSで同プロセス内
共有）/ Redis
32


# Page. 33

![Page Image](https://bcdn.docswell.com/page/4EMY4LG9EW.jpg)

パフォーマンス計測
33


# Page. 34

![Page Image](https://bcdn.docswell.com/page/PER9WKZ9J9.jpg)

計測環境・条件
環境
計測条件
メモリ
16 GB
boot 遅延
スタック
Laravel Octane
(FrankenPHP) +
Docker
200ms（両環境共通・意図
的に付加）
warm テス
ト
ab -n 1000 -c 20
ワーカー数 /
MAX_REQUESTS
4 / 500
cold テス
ト
ab -n 20 -c 1
計測ツール
ApacheBench 2.3
-n
総リクエスト数
-c
同時並列リクエスト数
34


# Page. 35

![Page Image](https://bcdn.docswell.com/page/P7XQPL93EX.jpg)

計測指標の見方
指
標
意味
p50
全リクエストの50%が収まるレスポンスタイム。典型的な応答速度
p95
全リクエストの95%が収まるレスポンスタイム。外れ値を除いた「ほぼ最悪」のケ
ース
平
均
全リクエストの平均レスポンスタイム
35


# Page. 36

![Page Image](https://bcdn.docswell.com/page/37K9DLPN7D.jpg)

計測結果（ルート: /）
PHP-FPM
+ Nginx
Octane
(FrankenPHP)
初回起動含む
Octane
(FrankenPHP)
起動済み
p50
218 ms
2 ms
7 ms
p95
449 ms
58 ms
15 ms
平均
230 ms
5 ms
10 ms
36


# Page. 37

![Page Image](https://bcdn.docswell.com/page/LJ3WR3XZJ5.jpg)

導入に有利な条件
ボトルネックにLaravelブートが含まれる
キャッシュを活かせば性能要件を満たせる見込みがある
要検討
ボトルネックがDBや外部APIなど、Laravelブート以外にある場合
キャッシュを活かしても改善見込みがない
37


# Page. 38

![Page Image](https://bcdn.docswell.com/page/8JDKY424EG.jpg)

まとめ
Octaneは要件が合えば強い
メインの価値は起動ブートの削減
導入判断は性能課題と運用前提の両方で決める
38


# Page. 39

![Page Image](https://bcdn.docswell.com/page/VEPK6M1V78.jpg)

ご清聴ありがとうございました
39


# Page. 40

![Page Image](https://bcdn.docswell.com/page/27VV39YR7Q.jpg)

採用情報
📄 採用情報
📝 note
40


