RareJob Tech Blog

レアジョブテクノロジーズのエンジニア・デザイナーによる技術ブログです

RoadRunner + Slim Framework で高性能な PHP 実行環境の構築

はじめに

こんにちは、DevOps グループの中島です。
最近急に寒くなり、年の瀬を感じる時節になってきましたね。そういえば去年の年末は Herman Miller でセールをしていたので、駆け込みでイスを買ったのを思い出しました。

今回は性能が高いと評判の PHP アプリケーションサーバー RoadRunner を Slim Framework (Slim 4) で使うための設定手順と、簡単な性能の検証結果を共有したいと思います。

RoadRunner とは

RoadRunner は Go で書かれたオープンソースの高性能な PHP アプリケーションサーバ & プロセスマネージャで、プラグイン機構をサポートしています。

RoadRunner is an open-source (MIT licensed) high-performance PHP application server, process manager written in Go and powered with plugins ❤️. It supports running as a service with the ability to extend its functionality on a per-project basis with plugins.

nginx + php-fpm ではその構成上 HTTP サーバとアプリケーションサーバが分離されているため、リクエストを受け付けるたびに PHP 側でフレームワークの初期化を行う必要がありましたが、 RoadRunner では付属の HTTP サーバでリクエストを受け付けたあと、すでに初期化が走ったあとでデーモン的に動作している PHP のワーカーがリクエストを処理する構成となっているため、パフォーマンスの向上に繋がっています。

細かい特徴は ドキュメントZenn の記事 に譲ります。

セットアップ

まずは必要なツール類をインストールします。 OS は Ubuntu 24.04 LTS を利用しました。

PHP インストール

$ sudo apt update
$ sudo apt install php php-xml php-mbstring unzip

Composer インストール

$ curl -s https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer

RoadRunner インストール

$ wget https://github.com/roadrunner-server/roadrunner/releases/download/v2024.3.0/roadrunner-2024.3.0-linux-amd64.deb
$ sudo dpkg -i roadrunner-2024.3.0-linux-amd64.deb
$ rm roadrunner-2024.3.0-linux-amd64.deb

アプリケーション作成

次に、Slim のサンプルプロジェクトを作成します。

サンプルプロジェクト作成

$ composer create-project slim/slim-skeleton sample-app

ここまでで、サーバを起動してアクセスするとサンプルが動作することが確認できます。

$ php -S 0.0.0.0:8080 -t public
$ curl localhost:8080
Hello world!

RoadRunner 用設定

RoadRunner の組み込みに必要な設定をしていきます。

RoadRunner と関連ライブラリを依存関係に追加

$ cd sample-app
$ composer require spiral/roadrunner spiral/roadrunner-worker spiral/roadrunner-http nyholm/psr7

RoadRunner の設定ファイル作成

$ vi .rr.yaml

version: "3"

server:
  command: "php psr-worker.php"

http:
  address: 0.0.0.0:8080

logs:
  level: error

ワーカーの作成

ここから、PHP のワーカーである psr-worker.php を作成していきます。
RoadRunnerのガイド による実装は以下の通りです。

<?php

require __DIR__ . '/vendor/autoload.php';

use Nyholm\Psr7\Response;
use Nyholm\Psr7\Factory\Psr17Factory;

use Spiral\RoadRunner\Worker;
use Spiral\RoadRunner\Http\PSR7Worker;
$worker = Worker::create();

$factory = new Psr17Factory();

$psr7 = new PSR7Worker($worker, $factory, $factory, $factory);

while (true) {
    try {
        $request = $psr7->waitRequest();
        if ($request === null) {
            break;
        }
    } catch (\Throwable $e) {
        $psr7->respond(new Response(400));
        continue;
    }

    try {
        $psr7->respond(new Response(200, [], 'Hello RoadRunner!'));
    } catch (\Throwable $e) {
        $psr7->respond(new Response(500, [], 'Something Went Wrong!'));
        $psr7->getWorker()->error((string)$e);
    }
}

ただ、当然このままでは動作しません。Slim で利用するのですから、Slim のフレームワーク初期化処理やレスポンスの生成処理を加える必要があります。

Slim-Skeletonindex.php では、フレームワークの初期化が走ったあと、以下の部分でレスポンスを生成して出力しています。

<?php
...
// Run App & Emit Response
$response = $app->handle($request);
$responseEmitter = new ResponseEmitter();
$responseEmitter->emit($response);

このため、ワーカーでは上記のレスポンス生成処理を、リクエストを取得して処理するループの中に加えてやれば OK です。

<?php
...
while (true) {
    try {
        $request = $psr7->waitRequest();
        if ($request === null) {
            break;
        }
    } catch (\Throwable $e) {
        $psr7->respond(new Response(400));
        continue;
    }

    try {
        // コメントアウト
        // $psr7->respond(new Response(200, [], 'Hello RoadRunner!'));

        // Slim のレスポンス生成処理を追加
        $response = $app->handle($request);
        $psr7->respond($response);
    } catch (\Throwable $e) {
        $psr7->respond(new Response(500, [], 'Something Went Wrong!'));
        $psr7->getWorker()->error((string)$e);
    }
}

以下に完成したワーカーのコードを置いておきます。

性能検証

Apache Bench で性能を簡単に測定した結果を以下に示します。

条件

項目 内容
サーバのインスタンスタイプ c5.large
クライアントのインスタンスタイプ c5.xlarge
HTTP サーバとアプリケーションサーバ間の通信 Unix ソケット
php-fpm のプロセス数 100 固定
RoadRunner のワーカー数 100 固定
コンテンツ Hello World! を返すだけ
ログ出力 しない

結果

同時接続数 nginx + php-fpm RoadRunner
1 660.75 925.74
2 1059.36 1659.52
3 1241.54 2191.29
4 1283.96 2475.24
5 1308.92 2591.36
10 1363.07 2820.52
100 1373.17 3268.43
150 1353.76 3261.71

RoadRunner は nginx+php-fpm の約 2.5 倍リクエストを捌けていました。すごい。*1

終わりに

Ubuntu と Slim の構成 (とイスの話題) でピンときた方もいると思いますが、これは ISUCON を意識したものです。 社内で声をかけて今回はじめて参加することが出来たので、素振りの時に試した内容を記事にしました。 いろいろ学びがあり、何よりやってて楽しかったので来年以降も参加したいと思っています。

We're hiring!
弊社では、一緒に働いてくださるエンジニアを募集しています。
rarejob-tech.co.jp

*1:本来は業務ロジックが処理の多くを占めるため、ここまでの改善は見込めないと想定される一方で、 PHP のキャッシング機構も利用できるため、既存のプロダクトに適用してどれほど性能が向上できるかはそのプロダクトの性質によります。また、メモリリークが発生しないサードパーティのライブラリを選ぶなどのケアする課題はあるため、本番環境への採用は慎重に行う必要があります。