#はじめに
Laravel Octane は、FrankenPHP、Open Swoole、Swoole、RoadRunner などの高性能アプリケーションサーバーを使ってアプリケーションを提供し、パフォーマンスを大幅に向上させます。Octane はアプリケーションを一度起動してメモリに保持し、その後は超高速でリクエストを処理します。
#インストール
Octane は Composer パッケージマネージャーを使ってインストールできます:
composer require laravel/octane
Octane をインストールした後、octane:install Artisan コマンドを実行すると、Octane の設定ファイルがアプリケーションにインストールされます:
php artisan octane:install
#サーバーの前提条件
Laravel Octane は PHP 8.1+ が必要です。
#FrankenPHP
FrankenPHP の Octane 統合はベータ版であり、本番環境での使用は注意が必要です。
FrankenPHP は Go 言語で書かれた PHP アプリケーションサーバーで、early hints や Zstandard 圧縮などの最新のウェブ機能をサポートします。Octane をインストールしてサーバーに FrankenPHP を選択すると、Octane が自動的に FrankenPHP のバイナリをダウンロードしてインストールします。
#Laravel Sail 経由の FrankenPHP
Laravel Sail を使ってアプリケーションを開発する場合は、以下のコマンドで Octane と FrankenPHP をインストールしてください:
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane
次に、octane:install Artisan コマンドを使って FrankenPHP バイナリをインストールします:
./vendor/bin/sail artisan octane:install --server=frankenphp
最後に、アプリケーションの docker-compose.yml ファイル内の laravel.test サービス定義に SUPERVISOR_PHP_COMMAND 環境変数を追加してください。この環境変数には、Sail が PHP 開発サーバーの代わりに Octane を使ってアプリケーションを提供するためのコマンドが含まれます:
services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=frankenphp --host=0.0.0.0 --admin-port=2019 --port=80"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data
HTTPS、HTTP/2、HTTP/3 を有効にするには、代わりに以下の変更を適用してください:
services:
laravel.test:
ports:
- '${APP_PORT:-80}:80'
- '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
- '443:443'
- '443:443/udp'
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --host=localhost --port=443 --admin-port=2019 --https"
XDG_CONFIG_HOME: /var/www/html/config
XDG_DATA_HOME: /var/www/html/data
通常、FrankenPHP Sail アプリケーションには https://localhost でアクセスします。https://127.0.0.1 を使う場合は追加設定が必要であり、推奨されていません。
#Docker 経由の FrankenPHP
FrankenPHP の公式 Docker イメージを使うと、パフォーマンスが向上し、静的インストールには含まれない拡張機能も利用できます。また、公式 Docker イメージは Windows など FrankenPHP がネイティブにサポートしていないプラットフォームでも動作します。公式 Docker イメージはローカル開発と本番環境の両方に適しています。
以下の Dockerfile を FrankenPHP を使った Laravel アプリケーションのコンテナ化の出発点として利用できます:
FROM dunglas/frankenphp
RUN install-php-extensions \
pcntl
# 他の PHP 拡張をここに追加...
COPY . /app
ENTRYPOINT ["php", "artisan", "octane:frankenphp"]
開発中は、以下の Docker Compose ファイルを使ってアプリケーションを実行できます:
# compose.yaml
services:
frankenphp:
build:
context: .
entrypoint: php artisan octane:frankenphp --max-requests=1
ports:
- "8000:8000"
volumes:
- .:/app
FrankenPHP を Docker で実行する方法の詳細は、公式 FrankenPHP ドキュメント を参照してください。
#RoadRunner
RoadRunner は Go でビルドされた RoadRunner バイナリによって動作します。RoadRunner ベースの Octane サーバーを初めて起動すると、Octane が RoadRunner バイナリのダウンロードとインストールを提案します。
#Laravel Sail 経由の RoadRunner
Laravel Sail を使ってアプリケーションを開発する場合は、以下のコマンドで Octane と RoadRunner をインストールしてください:
./vendor/bin/sail up
./vendor/bin/sail composer require laravel/octane spiral/roadrunner-cli spiral/roadrunner-http
次に、Sail シェルを起動し、rr 実行ファイルを使って最新の Linux 用 RoadRunner バイナリを取得します:
./vendor/bin/sail shell
# Sail シェル内で...
./vendor/bin/rr get-binary
最後に、アプリケーションの docker-compose.yml ファイル内の laravel.test サービス定義に SUPERVISOR_PHP_COMMAND 環境変数を追加してください。この環境変数には、Sail が PHP 開発サーバーの代わりに Octane を使ってアプリケーションを提供するためのコマンドが含まれます:
services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=roadrunner --host=0.0.0.0 --rpc-port=6001 --port=80"
最後に、rr バイナリに実行権限を付与し、Sail イメージをビルドしてください:
chmod +x ./rr
./vendor/bin/sail build --no-cache
#Swoole
Laravel Octane アプリケーションを Swoole アプリケーションサーバーで提供する場合は、Swoole PHP 拡張をインストールする必要があります。通常は PECL を使ってインストールします:
pecl install swoole
#Open Swoole
Laravel Octane アプリケーションを Open Swoole アプリケーションサーバーで提供する場合は、Open Swoole PHP 拡張をインストールする必要があります。通常は PECL を使ってインストールします:
pecl install openswoole
Laravel Octane を Open Swoole で使うと、同時実行タスク、ティック、インターバルなど、Swoole が提供する機能を利用できます。
#Laravel Sail 経由の Swoole
Sail 経由で Octane アプリケーションを提供する前に、Laravel Sail の最新バージョンを使用し、アプリケーションのルートディレクトリで ./vendor/bin/sail build --no-cache を実行してください。
または、Laravel の公式 Docker ベース開発環境である Laravel Sail を使って Swoole ベースの Octane アプリケーションを開発できます。Laravel Sail にはデフォルトで Swoole 拡張が含まれていますが、docker-compose.yml ファイルの調整は必要です。
まず、アプリケーションの docker-compose.yml ファイル内の laravel.test サービス定義に SUPERVISOR_PHP_COMMAND 環境変数を追加してください。この環境変数には、Sail が PHP 開発サーバーの代わりに Octane を使ってアプリケーションを提供するためのコマンドが含まれます:
services:
laravel.test:
environment:
SUPERVISOR_PHP_COMMAND: "/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan octane:start --server=swoole --host=0.0.0.0 --port=80"
最後に、Sail イメージをビルドしてください:
./vendor/bin/sail build --no-cache
#Swoole の設定
Swoole は octane 設定ファイルに追加できるいくつかの設定オプションをサポートしています。これらは通常変更する必要がないため、デフォルトの設定ファイルには含まれていません:
'swoole' => [
'options' => [
'log_file' => storage_path('logs/swoole_http.log'),
'package_max_length' => 10 * 1024 * 1024,
],
],
#アプリケーションの提供
Octane サーバーは octane:start Artisan コマンドで起動できます。デフォルトでは、このコマンドはアプリケーションの octane 設定ファイルの server オプションで指定されたサーバーを使用します:
php artisan octane:start
デフォルトでは、Octane はポート 8000 でサーバーを起動するため、ブラウザから http://localhost:8000 でアプリケーションにアクセスできます。
#HTTPS 経由でのアプリケーション提供
デフォルトでは、Octane 経由で実行されるアプリケーションは http:// で始まるリンクを生成します。アプリケーションの config/octane.php 設定ファイル内で使用される環境変数 OCTANE_HTTPS は、アプリケーションを HTTPS 経由で配信する場合に true に設定できます。この設定値が true に設定されていると、Octane は Laravel に対して生成されるすべてのリンクの先頭に https:// を付けるよう指示します:
'https' => env('OCTANE_HTTPS', false),
#Nginx 経由でのアプリケーション提供
自分でサーバー設定を管理する準備ができていない、または堅牢な Laravel Octane アプリケーションを動かすために必要な各種サービスの設定に不安がある場合は、Laravel Forge を検討してください。
本番環境では、Nginx や Apache などの従来のウェブサーバーの背後で Octane アプリケーションを提供すべきです。これにより、ウェブサーバーが画像やスタイルシートなどの静的アセットを提供し、SSL 証明書の終了処理を管理できます。
以下の Nginx 設定例では、Nginx がサイトの静的アセットを提供し、ポート 8000 で動作する Octane サーバーにリクエストをプロキシします:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name domain.com;
server_tokens off;
root /home/forge/domain.com/public;
index index.php;
charset utf-8;
location /index.php {
try_files /not_exists @octane;
}
location / {
try_files $uri $uri/ @octane;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
access_log off;
error_log /var/log/nginx/domain.com-error.log error;
error_page 404 /index.php;
location @octane {
set $suffix "";
if ($uri = /index.php) {
set $suffix ?$query_string;
}
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8000$suffix;
}
}
#ファイル変更の監視
Octane サーバーが起動時にアプリケーションを一度メモリに読み込むため、アプリケーションのファイルを変更してもブラウザをリロードしても反映されません。例えば、routes/web.php に追加したルート定義はサーバーを再起動するまで反映されません。利便性のため、--watch フラグを使うと、アプリケーション内のファイル変更時に Octane が自動的にサーバーを再起動します:
php artisan octane:start --watch
この機能を使う前に、ローカル開発環境に Node がインストールされていることを確認してください。また、プロジェクトに Chokidar ファイル監視ライブラリをインストールしてください。
npm install --save-dev chokidar
監視対象のディレクトリやファイルは、アプリケーションの config/octane.php 設定ファイルの watch オプションで設定できます。
#ワーカー数の指定
デフォルトでは、Octane はマシンの CPU コア数ごとにアプリケーションリクエストワーカーを起動します。これらのワーカーが HTTP リクエストを処理します。octane:start コマンドの --workers オプションで起動するワーカー数を手動で指定できます:
php artisan octane:start --workers=4
Swoole アプリケーションサーバーを使う場合は、起動する "task workers" の数も指定できます:
php artisan octane:start --workers=4 --task-workers=6
#最大リクエスト数の指定
メモリリークを防ぐため、Octane はワーカーが 500 リクエストを処理した後に優雅に再起動します。この数値は --max-requests オプションで調整できます:
php artisan octane:start --max-requests=250
#ワーカーのリロード
octane:reload コマンドで Octane サーバーのアプリケーションワーカーを優雅に再起動できます。通常はデプロイ後に実行し、新しいコードをメモリに読み込んで以降のリクエストに反映させます:
php artisan octane:reload
#サーバーの停止
octane:stop Artisan コマンドで Octane サーバーを停止できます:
php artisan octane:stop
#サーバーの状態確認
octane:status Artisan コマンドで Octane サーバーの現在の状態を確認できます:
php artisan octane:status
#依存性注入と Octane
Octane はアプリケーションを一度起動してメモリに保持しながらリクエストを処理するため、アプリケーション構築時にいくつか注意点があります。例えば、サービスプロバイダーの register と boot メソッドはリクエストワーカーの初回起動時に一度だけ実行されます。以降のリクエストでは同じアプリケーションインスタンスが再利用されます。
このため、アプリケーションサービスコンテナやリクエストをオブジェクトのコンストラクタに注入する場合は特に注意が必要です。そうすると、そのオブジェクトは次のリクエストで古いコンテナやリクエストを保持してしまう可能性があります。
Octane はリクエスト間でファーストパーティのフレームワーク状態を自動的にリセットしますが、アプリケーションが作成するグローバル状態のリセット方法は常に把握していません。したがって、Octane に適した形でアプリケーションを構築する方法を理解しておく必要があります。以下では、Octane 使用時に問題を引き起こしやすい一般的なケースを説明します。
#コンテナの注入
一般的に、アプリケーションサービスコンテナやHTTPリクエストのインスタンスを他のオブジェクトのコンストラクタに注入するのは避けるべきです。例えば、以下のバインディングは、シングルトンとしてバインドされたオブジェクトにアプリケーションサービスコンテナ全体を注入しています。
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* アプリケーションサービスを登録します。
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app);
});
}
この例では、Service インスタンスがアプリケーションのブートプロセス中に解決されると、コンテナがサービスに注入され、その同じコンテナが後続のリクエストでも Service インスタンスに保持されます。これは特定のアプリケーションでは問題にならないかもしれませんが、ブートサイクルの後半や後続のリクエストで追加されたバインディングがコンテナに存在しないという予期しない問題を引き起こす可能性があります。
回避策としては、バインディングをシングルトンとして登録するのをやめるか、常に現在のコンテナインスタンスを解決するコンテナリゾルバーのクロージャをサービスに注入する方法があります。
use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;
$this->app->bind(Service::class, function (Application $app) {
return new Service($app);
});
$this->app->singleton(Service::class, function () {
return new Service(fn () => Container::getInstance());
});
グローバルの app ヘルパーと Container::getInstance() メソッドは常に最新のアプリケーションコンテナを返します。
#リクエストの注入
一般的に、アプリケーションサービスコンテナやHTTPリクエストのインスタンスを他のオブジェクトのコンストラクタに注入するのは避けるべきです。例えば、以下のバインディングは、シングルトンとしてバインドされたオブジェクトにリクエストインスタンス全体を注入しています。
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* アプリケーションサービスを登録します。
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app['request']);
});
}
この例では、Service インスタンスがアプリケーションのブートプロセス中に解決されると、HTTPリクエストがサービスに注入され、その同じリクエストが後続のリクエストでも Service インスタンスに保持されます。そのため、すべてのヘッダー、入力、クエリ文字列データを含むリクエストデータが不正確になります。
回避策としては、バインディングをシングルトンとして登録するのをやめるか、常に現在のリクエストインスタンスを解決するリクエストリゾルバーのクロージャをサービスに注入する方法があります。あるいは、最も推奨される方法は、オブジェクトが必要とする特定のリクエスト情報を実行時にオブジェクトのメソッドに渡すことです。
use App\Service;
use Illuminate\Contracts\Foundation\Application;
$this->app->bind(Service::class, function (Application $app) {
return new Service($app['request']);
});
$this->app->singleton(Service::class, function (Application $app) {
return new Service(fn () => $app['request']);
});
// または...
$service->method($request->input('name'));
グローバルの request ヘルパーは、アプリケーションが現在処理しているリクエストを常に返すため、アプリケーション内で安全に使用できます。
コントローラーのメソッドやルートクロージャで Illuminate\Http\Request インスタンスを型宣言するのは許容されます。
#設定リポジトリの注入
一般的に、設定リポジトリのインスタンスを他のオブジェクトのコンストラクタに注入するのは避けるべきです。例えば、以下のバインディングは、シングルトンとしてバインドされたオブジェクトに設定リポジトリを注入しています。
use App\Service;
use Illuminate\Contracts\Foundation\Application;
/**
* アプリケーションサービスを登録します。
*/
public function register(): void
{
$this->app->singleton(Service::class, function (Application $app) {
return new Service($app->make('config'));
});
}
この例では、リクエスト間で設定値が変更されても、そのサービスは元のリポジトリインスタンスに依存しているため、新しい値にアクセスできません。
回避策としては、バインディングをシングルトンとして登録するのをやめるか、設定リポジトリを解決するリゾルバーのクロージャをクラスに注入する方法があります。
use App\Service;
use Illuminate\Container\Container;
use Illuminate\Contracts\Foundation\Application;
$this->app->bind(Service::class, function (Application $app) {
return new Service($app->make('config'));
});
$this->app->singleton(Service::class, function () {
return new Service(fn () => Container::getInstance()->make('config'));
});
グローバルの config は常に最新の設定リポジトリを返すため、アプリケーション内で安全に使用できます。
#メモリリークの管理
Octaneはリクエスト間でアプリケーションをメモリに保持するため、静的に管理されている配列にデータを追加するとメモリリークが発生します。例えば、以下のコントローラーは、アプリケーションへの各リクエストで静的な $data 配列にデータが追加され続けるため、メモリリークが発生します。
use App\Service;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
/**
* 受信リクエストを処理します。
*/
public function index(Request $request): array
{
Service::$data[] = Str::random(10);
return [
// ...
];
}
アプリケーションを構築する際は、このようなメモリリークを避けるよう特に注意してください。ローカル開発中にアプリケーションのメモリ使用量を監視し、新たなメモリリークを導入していないか確認することを推奨します。
#同時実行タスク
この機能は Swoole が必要です。
Swooleを使用すると、軽量なバックグラウンドタスクで操作を同時実行できます。Octaneの concurrently メソッドを使ってこれを実現できます。このメソッドはPHPの配列分解と組み合わせて、各操作の結果を取得できます。
use App\Models\User;
use App\Models\Server;
use Laravel\Octane\Facades\Octane;
[$users, $servers] = Octane::concurrently([
fn () => User::all(),
fn () => Server::all(),
]);
Octaneで処理される同時実行タスクはSwooleの「タスクワーカー」を利用し、着信リクエストとは全く別のプロセスで実行されます。同時実行タスクを処理できるワーカー数は、octane:start コマンドの --task-workers オプションで決まります。
php artisan octane:start --workers=4 --task-workers=6
concurrently メソッドを呼び出す際は、Swooleのタスクシステムの制限により1024タスクを超えないようにしてください。
#ティックとインターバル
この機能は Swoole が必要です。
Swooleを使用すると、指定した秒数ごとに実行される「ティック」操作を登録できます。tick メソッドでティックコールバックを登録します。tick メソッドの第1引数はティッカーの名前を表す文字列、第2引数は指定した間隔で呼び出されるコール可能な関数です。
この例では、10秒ごとに呼び出されるクロージャを登録します。通常、tick メソッドはアプリケーションのサービスプロバイダーの boot メソッド内で呼び出します。
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10);
immediate メソッドを使うと、Octaneサーバーの起動時にティックコールバックを即座に呼び出し、その後はN秒ごとに呼び出すよう指示できます。
Octane::tick('simple-ticker', fn () => ray('Ticking...'))
->seconds(10)
->immediate();
#Octaneキャッシュ
この機能は Swoole が必要です。
Swooleを使用すると、1秒間に最大200万回の読み書きが可能なOctaneキャッシュドライバーを利用できます。このため、極めて高速な読み書きが必要なアプリケーションに最適なキャッシュドライバーです。
このキャッシュドライバーはSwooleテーブルを利用しています。キャッシュに保存されたすべてのデータはサーバー上のすべてのワーカーからアクセス可能ですが、サーバーが再起動されるとキャッシュデータは消去されます。
Cache::store('octane')->put('framework', 'Laravel', 30);
Octaneキャッシュに許可される最大エントリ数は、アプリケーションの octane 設定ファイルで定義できます。
#キャッシュのインターバル
Laravelの通常のキャッシュメソッドに加え、Octaneキャッシュドライバーはインターバルベースのキャッシュを提供します。これらのキャッシュは指定した間隔で自動的に更新され、アプリケーションのサービスプロバイダーの boot メソッド内で登録する必要があります。例えば、以下のキャッシュは5秒ごとに更新されます。
use Illuminate\Support\Str;
Cache::store('octane')->interval('random', function () {
return Str::random(10);
}, seconds: 5);
#テーブル
この機能は Swoole が必要です。
Swooleを使用すると、任意のSwooleテーブルを定義して操作できます。Swooleテーブルは非常に高いパフォーマンスを持ち、サーバー上のすべてのワーカーからデータにアクセス可能です。ただし、サーバーが再起動されるとデータは失われます。
テーブルはアプリケーションの octane 設定ファイルの tables 設定配列内で定義します。最大1000行を許容する例のテーブルがすでに設定されています。文字列カラムの最大サイズは、以下のようにカラムタイプの後にサイズを指定して設定できます。
'tables' => [
'example:1000' => [
'name' => 'string:1000',
'votes' => 'int',
],
],
テーブルにアクセスするには、Octane::table メソッドを使用します。
use Laravel\Octane\Facades\Octane;
Octane::table('example')->set('uuid', [
'name' => 'Nuno Maduro',
'votes' => 1000,
]);
return Octane::table('example')->get('uuid');
Swooleテーブルでサポートされているカラムタイプは、string、int、float です。