Laravel Octane: SwooleとFrankenPHPどっちがいい?
軽く負荷試験ツールで検証してみていますが、実際はDBと接続したアプリであったり、複雑なロジックが実行されるようなアプリケーションで実施する方が好ましいです。
HealthCheckEndpoint(一応DBへの接続はしている)のみで計測してどの程度差が出ているかの確認のみとなります。
また、M1Macの為VM上で起動するlimaを利用しているため、さらにボトルネック部分が多くなっております。
このような環境下でもどの程度変化するか、確認していきたいと思います。
負荷試験ツール
Macということで、
Siege
という物を利用してどのくらい速度に差が出るのかを確認してみようと思います。
$ brew install siege
Laravelのアプリについて
こちらのリポジトリをベースにOctane & OpenSwoole のDockerと、 FrankenPHP.Alpine で軽く比較してみようと思います。
検証比較にはならないかもしれないですが docker-compose でコンテナを建てて検証します。
version: '2'
services:
app:
build: .
environment:
DB_CONNECTION: pgsql
DB_HOST: db
DB_PORT: 5432
DB_DATABASE: db
DB_USERNAME: user
DB_PASSWORD: pass
depends_on:
- db
ports:
- 9000
db:
image: postgres:16.3-bullseye
environment:
POSTGRES_PASSWORD: pass
POSTGRES_USER: user
POSTGRES_DB: db
Swoole
DockerFileをそのまま持ってきています。バージョンの差異があるかもしれないので内容を載せておきます。
# Accepted values: 8.3 - 8.2
ARG PHP_VERSION=8.3
ARG COMPOSER_VERSION=latest
###########################################
# Build frontend assets with NPM
###########################################
ARG NODE_VERSION=20-alpine
FROM node:${NODE_VERSION} AS build
ENV ROOT=/var/www/html
WORKDIR ${ROOT}
RUN npm config set update-notifier false && npm set progress=false
COPY package*.json ./
RUN if [ -f $ROOT/package-lock.json ]; \
then \
npm ci --loglevel=error --no-audit; \
else \
npm install --loglevel=error --no-audit; \
fi
COPY . .
RUN npm run build
###########################################
FROM composer:${COMPOSER_VERSION} AS vendor
FROM php:${PHP_VERSION}-cli-bookworm AS base
LABEL maintainer="yusuke.aoki <yusuke.aoki@optim.co.jp>"
LABEL org.opencontainers.image.title="Laravel Octane Dockerfile - Hardware My Portal"
LABEL org.opencontainers.image.description="Production-ready Dockerfile for Laravel Octane"
LABEL org.opencontainers.image.source=https://github.com/exaco/laravel-octane-dockerfile
LABEL org.opencontainers.image.licenses=MIT
ARG WWWUSER=1000
ARG WWWGROUP=1000
ARG TZ=UTC
ENV DEBIAN_FRONTEND=noninteractive \
TERM=xterm-color \
WITH_HORIZON=false \
WITH_SCHEDULER=false \
OCTANE_SERVER=swoole \
USER=octane \
ROOT=/var/www/html \
COMPOSER_FUND=0 \
COMPOSER_MAX_PARALLEL_HTTP=24
WORKDIR ${ROOT}
SHELL ["/bin/bash", "-eou", "pipefail", "-c"]
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \
&& echo ${TZ} > /etc/timezone
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN apt-get update; \
apt-get upgrade -yqq; \
apt-get install -yqq --no-install-recommends --show-progress \
apt-utils \
curl \
wget \
git \
nano \
ncdu \
ca-certificates \
supervisor \
libsodium-dev \
# Install PHP extensions
&& install-php-extensions \
bz2 \
pcntl \
mbstring \
bcmath \
sockets \
pgsql \
pdo_pgsql \
opcache \
exif \
pdo_mysql \
zip \
intl \
gd \
redis \
rdkafka \
memcached \
igbinary \
ldap \
swoole \
&& apt-get -y autoremove \
&& apt-get clean \
&& docker-php-source delete \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& rm /var/log/lastlog /var/log/faillog
RUN wget -q "https://github.com/aptible/supercronic/releases/download/v0.2.29/supercronic-linux-amd64" \
-O /usr/bin/supercronic \
&& chmod +x /usr/bin/supercronic \
&& mkdir -p /etc/supercronic \
&& echo "*/1 * * * * php ${ROOT}/artisan schedule:run --no-interaction" > /etc/supercronic/laravel
RUN userdel --remove --force www-data \
&& groupadd --force -g ${WWWGROUP} ${USER} \
&& useradd -ms /bin/bash --no-log-init --no-user-group -g ${WWWGROUP} -u ${WWWUSER} ${USER}
RUN chown -R ${USER}:${USER} ${ROOT} /var/{log,run} \
&& chmod -R a+rw ${ROOT} /var/{log,run}
RUN cp ${PHP_INI_DIR}/php.ini-production ${PHP_INI_DIR}/php.ini
USER ${USER}
COPY /usr/bin/composer /usr/bin/composer
COPY composer.json composer.lock ./
RUN composer install \
--no-dev \
--no-interaction \
--no-autoloader \
--no-ansi \
--no-scripts \
--audit
COPY . .
COPY ${ROOT}/public public
RUN mkdir -p \
storage/framework/{sessions,views,cache,testing} \
storage/logs \
bootstrap/cache && chmod -R a+rw storage
COPY deployment/octane/Swoole/supervisord.swoole.conf /etc/supervisor/conf.d/
COPY deployment/supervisord.*.conf /etc/supervisor/conf.d/
COPY deployment/php.ini ${PHP_INI_DIR}/conf.d/99-octane.ini
COPY deployment/start-container /usr/local/bin/start-container
RUN composer install \
--classmap-authoritative \
--no-interaction \
--no-ansi \
--no-dev \
&& composer clear-cache \
&& php artisan storage:link
RUN chmod +x /usr/local/bin/start-container
RUN cat deployment/utilities.sh >> ~/.bashrc
EXPOSE 9000
ENTRYPOINT ["start-container"]
HEALTHCHECK CMD php artisan octane:status || exit 1
起動
docker-compose up -d
結果はまとめて記事後半に記載いたします。
FrankenPHP
では次にFrankenPHPで試して見ましょう。
先ずはプロジェクトをFrankenPHPに対応させる必要があるため、以下のコマンドで対応します。
(Laravel OctaneはSwooleでインストール済みとします)
$php artisan octane:install --server=frankenphp
FrankenPHP's Octane integration is in beta and should be used with caution in production. Do you wish to continue? (yes/no) [yes]:
> yes
Unable to locate FrankenPHP binary. Should Octane download the binary for your operating system? (yes/no) [yes]:
> yes
116094786/116094786 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%
WARN Please adjust the `OCTANE_SERVER` environment variable.
INFO Octane installed successfully.
ローカルで使う場合にswooleを使おうとすると危険なので環境変数も変えておきましょう。
.env
OCTANE_SERVER=frankenphp
FrankenPHPではGo言語のような使い方ができ、StandaloneBuildが出来るようなのでドキュメントに従ってバイナリを生成してみます。
バイナリのビルド... 遅い..!!!
こちらのドキュメントを参考にビルドを行ってみましたが、、、
3280.3s !!!!
しかも、何か修正を一つでも行ったらもう一度この時間待たないといけないという状態に...
どこで遅い。。?
#15 23.76 + upx --best dist/frankenphp-linux-x86_64
#15 23.78 Ultimate Packer for eXecutables
#15 23.78 Copyright (C) 1996 - 2024
どうやら、バイナリを圧縮する部分で遅くなっているようですね。
該当箇所を見つけました。
CIで毎回1時間拘束されるのは辛いので、OFFにして再度実行します。
以下のようにDockerFileの一部を書き換えます。
- RUN EMBED=dist/app/ ./build-static.sh
+ RUN NO_COMPRESS=true EMBED=dist/app/ ./build-static.sh
再度実行
#15 36.98 + cd ../..
#15 36.98 + '[' -d dist/app/ ]
#15 36.98 + truncate -s 0 app.tar
#15 36.99 + truncate -s 0 app_checksum.txt
#15 36.99 + type upx
#15 36.99 + '[' -z ]
#15 36.99 + '[' -z true ]
#15 36.99 + dist/frankenphp-linux-x86_64 version
#15 37.71 FrankenPHP v1.1.5 PHP Caddy v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=
#15 37.72 + '[' -n ]
#15 37.72 + '[' -n ]
#15 DONE 40.1s
無事にビルドが通りました。 40s なので許容範囲かなと思います。
以下のようなDockerFileとなりました。
FROM dunglas/frankenphp:static-builder as BUILD_STAGE
# Copy your app
WORKDIR /go/src/app/dist/app
COPY . .
# Remove the tests and other unneeded files to save space
# Alternatively, add these files to a .dockerignore file
RUN rm -Rf tests/
# Copy .env file
RUN cp .env.example .env
# Change APP_ENV and APP_DEBUG to be production ready
# RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
# Make other changes to your .env file if needed
# Install the dependencies
RUN composer install --ignore-platform-reqs --no-dev -a
# Build the static binary
WORKDIR /go/src/app/
RUN NO_COMPRESS=true EMBED=dist/app/ ./build-static.sh
FROM alpine:latest
COPY /go/src/app/dist/frankenphp-linux-x86_64 frankenphp
COPY ./deployment/frankenphp_static/start-container /bin/start-container
RUN chmod +x /bin/start-container
ENTRYPOINT [ "start-container" ]
start-containerではartisanコマンド系を叩いています。
#!/usr/bin/env sh
set -e
./frankenphp php-cli artisan storage:link; \
./frankenphp php-cli artisan optimize:clear; \
./frankenphp php-cli artisan event:cache; \
./frankenphp php-cli artisan config:cache; \
./frankenphp php-cli artisan route:cache;
if [ "$1" != "" ]; then
exec "$@"
else
exec ./frankenphp php-cli artisan octane:frankenphp --port=9000 --admin-port=2019 --max-requests=10000
fi
artisan octane:frankenphp --port=9000 --admin-port=2019
等のコマンドについては以下のドキュメントを参照
とりあえずalpineに詰めているので、これをサーバで実行し性能を見てみました。
性能試験
swoole
** SIEGE 4.1.6
** Preparing 5 concurrent users for battle.
The server is now under siege...
Lifting the server siege...
Transactions: 664 hits
Availability: 100.00 %
Elapsed time: 5.69 secs
Data transferred: 0.04 MB
Response time: 0.04 secs
Transaction rate: 116.70 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 4.96
Successful transactions: 664
Failed transactions: 0
Longest transaction: 0.16
Shortest transaction: 0.03
安定して動作しているように見えます。
FrankenPHP
** SIEGE 4.1.6
** Preparing 5 concurrent users for battle.
The server is now under siege...
Lifting the server siege...
Transactions: 153 hits
Availability: 93.87 %
Elapsed time: 5.47 secs
Data transferred: 0.01 MB
Response time: 0.05 secs
Transaction rate: 27.97 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 1.36
Successful transactions: 153
Failed transactions: 10
Longest transaction: 0.10
Shortest transaction: 0.03
????
かなり遅い.. というか途中からアクセスが遮断されている様子。
サーバを見てみると、Podが落ちていました。
MaxRequestに到達しているわけでも無く、ログは急に止まっている...
k8sを利用しているので、limitに引っかかってコケているのかもしれませんが、 そもそもswooleと同じlimitを設定しているのでこの時点で安定していないのであればFrankenPHPは少し辛そうという印象..
スペックをあげて挑戦して見るも同じ結果になりました。 別の要因がありそうです。
まとめ
ちゃんとした検証までやってないですが、FrankenPHPの採用は一旦諦めようかと思っています。
時間が少し空けば、もう少し踏み込んだ調査を実施して原因究明まで持っていきたいところですがSwooleで十分早く安定している状態のため
今のところは見送ろうと思います。
appendix: DockerImageサイズについて
- FrankenPHP: 70MB 程度
- Swoole: 270MB 程度
(これが安定したらFrankenPHPへの移行も視野に入れていきたい...)
Discussion