使用環境
- Laravel8.x
- PHP 8.0
- Docker(Laravel Sailで構築した環境を使用しました)
ごあいさつ
この記事は2021年のQiita Advent Calendar PHP の4日目の記事です。
昨日は@yoshikyotoさんのPHP8.0,8.1に関する記事でした。最近PHPも8.1がリリースされてまだまだ進化を遂げていますね。
記事の目的
この記事ではLaravelで並列処理をするにあたって私が検討した選択肢を紹介することとともに、どのようにそれを実現したかをお示しすることで現在似たような実装を目指している方の助けとなることを目的とします。
前提
Laravelのキュー機能では様々な処理をジョブという関数に切り出しておいて、それらをバックグラウンドで非同期に実行することができます。キューについては公式ドキュメントを貼っておきます。
Laravel 8.x キュー
Laravelを触り出して半年そこそこの私は、既にキューを使って構築されているメッセージングサービスのプロジェクトでこんな要望を出されました。
社長「メッセージの配信速度って上げられないかなぁ」
問題のプロジェクトではメッセージを送信する際に外部のAPIを使用していました。そしてこのAPI(仮に送信APIと呼びます)を叩く処理をジョブに書いておき、それを必要なタイミングでキューへ発行することでメッセージ配信機能を実現していたのです。
送信APIの制約
送信APIは1秒間に5回までしかコールできないという制約がありました。いわゆるレートリミットですね。当然これを超えるとレートリミット超過ということでAPIコールが失敗してしまうので、配信速度を上げるとなれば秒間5コールぎりぎりをついていくほかありませんでした。以下の内容はこのレートリミットを踏まえて進みます。
メッセージの配信速度を上げるには...?
3つほど紹介します。結論だけ知りたい方は一番下までスキップしてください。
Golangで並列処理をする
若干Laravelに飽きていた私は新しい技術を使える機会ならばと並列処理を得意とするGolangの使用を考えました。処理機構のイメージとしてはこんな感じです。
詳しい人ならご存知だと思いますがcronやLaravelでそれを実装したタスクスケジューラは最も頻繁に実行しても1分に1回しか実行できません。しかし、問題のプロジェクトでは前述したレートリミットのぎりぎりをつくため1秒に1回実行しなければならなかったのでこの方法は使えませんでした。かなしみ。
Guzzle HTTPを使う
GuzzleHTTPとはPHPでHTTPコールを並列に行うためのライブラリです。少なくともLaravel8系ではデフォルトで入っています。技術調査にあたってはmercariの方が書かれたスライドが大変参考になったので貼り付けておきます。
Guzzle Promiseを使った 非同期処理によるAPIコールの高速化
要するに外部の送信APIを並列に叩きたいならrequestAsyncなりPoolなりを使ってAPIのエンドポイントにPOSTリクエストを必要なだけ送信すれば良いということになります。しかし、これも結局cronなりタスクスケジューラでレートリミットのぎりぎりをつく制御が必要になってしまう以上Golangの時と同じ理由で使えませんでした。かなしみ。しかし私のようなレートリミットの問題を抱えていない開発者の方には良い選択肢であると思います。PHPで完結するので。
キューのプロセスを複数起動する
3つの中では最も泥臭い方法ですが、結局これを採用しました。Laravelではジョブを発行する時に発行先のキューを任意に指定することができます。例えばqueue_Aとqueue_Bという2つのキューワーカプロセスを起動しておけば、キューの発行先にこれらのキュー名を指定してやることでジョブの発行先を出し分けることができるのです。
私の場合、従来はキューワーカプロセスを1つだけ起動していたのを2つに増やしたことで送信APIを並列にコールできるようになり、メッセージ配信速度を向上させることができました。
しかしまだレートリミットの問題が解決していません。結論から言えばキューワーカのプロセスを2つにしてベンチマークをとっても秒間5回を超えることはあまりなく、(平均4回でした)あったとしても瞬間的に超える分にはAPIコールは問題なく行えたので一応解決という形になりました。
ただし、本番環境ではキューのプロセスが落ちるリスクへの対策が必要になります。それはSupervisorを使って対応しました。その際に必要なsupervisor.confの書き方などについては記事の目的から外れるため割愛します。私が参考にした神記事を貼っておくのでご覧ください。