Laravel + PPM(PHP-PM) + ECS/FargateのWeb APIを本番運用した話

Laravel + PPM(PHP-PM) + ECS/FargateのWeb APIを本番運用した話

author potpro(ぼとぷろ)
2020/07/16

Laravel + PPM(PHP-PM) + ECS/FargateのWebアプリを本番運用した話

久々にPHPの記事です。そうだ自分はPHPerだった(最近は怪しいけど)。

割と攻めた構成で本番運用まで出来て、なおかつ安定しているくらいまで実現できたということで記念に記事を書く。

なお今回、ECS/Fargateの設定なんかは前に書いたことがあるのであまり詳しく書きません。

LaravelでISUCON突破したいみたいな人にも役に立つんじゃないかな。実際出来るかはわかりませんが。

PPM(PHP-PM)とは?

まず、PHP-PMを知らない人がほとんどだと思われます。一般的に使われているであろうPHP-FPMの間違いではないです。

phppm

php-pm/php-pm - PHP Process Manager

PHP-PMはPHPアプリケーションのためのスーパーチャージャーかつロードバランサーと言っています。 具体的に言うと、ReactPHPというOSSに基づいたプロセスマネージャーとなってます。

ReactPHPはNode.jsのようなイベント駆動ノンブロッキングIOをPHPで実現するための低レベルなAPIで、これを使用してSymfonyのHTTPKernelフレームワークを使用するようなアプリケーションで、ノンブロッキングIOを実現します。

PHP-PMはReactPHP単体と違って、これはlaravelやSymfonyフレームワークのソースコード自体に一切手を入れなくても動作するという点がやりやすく、素晴らしいです。

PHP-FPM含め、通常のPHPはブロッキングでなおかつシングルスレッドのため、ブロッキングによる処理の同時実行性とスループットが下がります。ここをノンブロッキングで処理できるようにすると、PHPのようなWebアプリケーションの場合、高いパフォーマンス向上につながるというわけです。

PHP-PMのデメリットとして、laravelやSymfony自体をReactPHPでノンブロッキングIOを実現したため、アプリケーション内でevent-loopを使用することができない、という問題がありますが、特殊なアプリケーションで無ければ大丈夫だと思います。

後は公式に書いてある点で一番気になるところですが、

Memory leaks, memory leaks and memory leaks. You will also find leaks in your application. :) But no big issue since workers restart automatically.

ですが、これに関しては本番運用で挙動を調べて、現状は問題ないかなという感じでした。後で詳しく説明します。

パフォーマンス

これを採用した理由の一番は、やはりパフォーマンスの向上目的です。驚くことに公式はPHP-FPMと比べて最大15倍のパフォーマンス向上と言っております。

15倍はちょっと言いすぎな気がしますが、公式のReadmeにある、laravel5-exampleを使用したパフォーマンスのグラフには大体3~4倍のパフォーマンスが表示されています。とはいえこれだけでも十分すごいと思います。

実際のパフォーマンスに関しても、後で詳しく述べています。

Dockerfile

公式にDocker Imageが用意されています。

php-pm-docker

このイメージはnginxとppmが入ったイメージでそのまま本番に使っても問題ないように作られているので、これをカスタマイズして使用するのが一番かなと思います。

phppm/nginxは、PHP7.3とnginxの最新版が入っているので、この点でも問題なく使用できます。 また、LinuxはAlpineなので、そのままapk addなどのコマンドが使用できます。 本番は、こんな感じでカスタマイズして使用しています。

FROM phppm/nginx:2.0.3

ENV LANG ja_JP.UTF-8

# JSTに変更
RUN apk add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

RUN apk add --no-cache --virtual .build-deps vim git g++ make autoconf ca-certificates wget curl

# 使用するモジュール追加
RUN apk --no-cache add php7-bcmath php7-pecl-memcached

ENV COMPOSER_VERSION 1.10.8

RUN curl -L -O https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar && \
    chmod +x composer.phar && mv composer.phar /usr/local/bin/composer && \
    composer self-update

COPY . /var/www

WORKDIR /var/www

RUN composer install

CMD ["--bootstrap=laravel","--static-directory=public/", "--app-env=prod" ,"--debug=0", "--logging=0", "--workers=5"]

redisだったりmemcachedを使用する場合は、apk addを使用すればインストール可能です。

PPMの設定

DockerfileのCMDの行で指定することで、起動設定を変更できます。 本番で使用する場合は、この設定を少し変更したほうが良いです。

  • --app-env=prod は、指定すると本番環境に最適化されます。追加しておきましょう。
  • --debug=0 も、デバック情報をログに吐き出さないための設定です。
  • --logging=0 は、アクセスログを表示するかの設定です。今回ALB側でアクセスログは取れるので、無しとします。
  • --workers=5は、次の項目にて説明。

PPMの設定(Worker)

PPMは、PHP-FPMと同じようにworkerを設定できます。default設定は8台です。

公式のREADMEには、

通常、CPUコア数の10%が適切です。例:8つの実際のコア(ハイパースレッディングを除く)がある場合、--workers=9を使用します。

と書いてありますが、Fargateを使っている場合実コアが見えませんし、かなり特殊と思われます。

今回はECS/FargateのvCPU 0.5 / Mem 1GBを1つのコンテナ(タスク)として使用し、最大のパフォーマンスが出るようなWorkerの設定を測定しました。

パフォーマンスの測定

実際に問題ないかを確かめるために、パフォーマンスの測定を行いました。

vCPU 0.5 / Mem 1GB 1タスクでの処理上限(1req/sずつ上げていって、タイムアウトが多発するまでの地点)

  • 1 worker = 55req/s 
  • 2 worker = 70req/s
  • 5 worker = 110req/s
  • 8 worker = 115req/s
  • 10 worker = 115req/s
  • 12 worker = 110req/s
  • 20 worker = 110req/s

と、結果的には5 workerで頭打ちのような挙動をしたので、5 workerを採用。 ちなみに、FPMと同じようにWorkerを増やせば増やすほどメモリ使用率も上がりました。

なので雑に多すぎる数をするのは良くないかと思うので、頭打ちになった値で採用するといいかもしれません。

また安定性に関しても、処理できるリクエスト数を超えてエラー多発するまで、エラー率は0%で全て200を返せており、プロダクション投入しても問題ないであろうという確証にもなりました。

実際、これで本番リリース時 に問題なく捌けていて、かなりいい感じです。  

メモリリークの実際の挙動

Memory leaks, memory leaks and memory leaks. You will also find leaks in your application. :) But no big issue since workers restart automatically.

先ほども指摘しましたが、意訳すると「ものすげえメモリリークするけどWorkerが自動的に再起動するから大した問題にはならないよ:)」とのラフすぎてちょっと不安になる文章があります。

これは実際にどのように再起動するか書かれていませんが、max-requestsという項目があり、1 workerにつきこのリクエスト数を処理したらWorkerが再起動する仕組みとなっています。初期値は__1000__です。

当然1000リクエストを処理するまでずっとメモリリークするという挙動なわけなので、1000リクエスト後にはメモリが足らなくなってしまうような挙動をするのであれば、この値を減らす必要があります(それかメモリを増やすか)。

また、1 workerで試したときにWorkerが再起動中で処理ができるWorkerがなくなり、処理が詰まっているような挙動をしていたので、2 worker以上にはしておくべきかと思います。Workerの再起動には少し時間が掛かるようです。

今回負荷テスト・本番投入時には、まあそんなに複雑なことをしていないというのもありほぼメモリ使用量は変わらず10%程度でしたので、初期値のままで問題なく動いております。

本番リリース時

いろいろと計測していたため、特に本番等も想定通りの結果でした。

実際のアプリケーションはそこまでまだ機能のないWeb Rest APIだったということもあると思いますが、処理数的にもかなり高水準を捌けています。

前にGo言語で開発したAPIとそこまで変わらない水準の処理性能を出しています。ここに関してはやってる処理も違うので、あんまり比較にならないですし、PHPとGoを比較すると雲泥の差レベルなので、ここまでやってもGoの方がパフォーマンス高いですけど。

本番のレスポンスタイム

実際のレスポンスタイムを貼っておきます(ALB/1時間での値)。

resphppm

平均レスポンスタイム: 8 ms

なんと処理速度は10ms以下という、そこまで難しい処理をしていないとはいえ、正直PHP+laravelと思えないくらいの値を出していると思います。

PHP 7.3でかつOpcacheを使っているからそれを含めて早くなっている、という話でもありますが、PHPで平均1桁ミリ秒台のレスポンスタイムを叩き出すなんて昔じゃ考えられないですね・・・

memcachedからデータを取得していて、それがボトルネックになっているので、laravel自体の処理は1ms程度で処理できていると思われます。すごい。

これを見るとPHP-PMが爆速なPHPロードバランサーというのがよくわかり、15倍速くなるっていうのは誇張でもないってのがわかりますね。

laravelのアプリケーションはPHPであれば確実に増加傾向にあると思いますので、是非検討してもらいたいです。