負荷制限を導入する時に考慮すること リクエストに優先順位をつける

長友健人氏:では、負荷制限を導入した時にどのようなことに考慮すればいいのかをお話しします。負荷制限の実装で考慮すべき事項は今回の発表の元となった記事にたくさん書かれているんですが、今回はそのうち5つをピックアップして紹介します。

1つ目は、「リクエストに優先順位をつけましょう」という内容です。「優先度の高くないリクエストはオフピーク時に処理しましょう」といっています。例えば、クライアントからのアクセスが大量にサーバーに来ていて、サーバーが過負荷な状態になっているとします。このタイミングで緊急度の高くないクローラーだったり、バッチからのリクエストを処理してしまうと、サーバーがさらなる過負荷に陥ってしまいます。

なので、ピーク時を避け、クライアントからのリクエストが少ない時にクローラーやバッチといった緊急度の高くない処理を回すことで、負荷を軽減できます。

ここで注意しなければいけないのが、ヘルスチェックのためのロードバランサーからのピンなどです。システムを維持するために大事なリクエストは優先度が高いです。サーバーに対して行われるリクエストを洗い出して、どれが優先度が高いのか、低いのかをあらかじめ決めておく必要があります。

負荷制限を導入する時に考慮すること 無駄な処理コストを使わない

次に、無駄な処理コストを使わないという内容です。これは一般的に言われていることですが、過負荷時にレスポンスの遅さから、ユーザーは頻繁にリトライするといわれています。Webサービスが遅い時にはとりあえずリトライして試してみるということですね。このリトライがサーバーにとっては良くない事態を引き起こします。

例えば、クライアントがリクエストして1つ目のリクエストの時に、レスポンスが来ないのでリトライするとします。で、2回目のリクエストを投げます。そして、サーバーが1個目のリクエストを処理します。

しかし、ここでクライアントは2回目のリクエストをしているので、1回目のリクエストに関しては、すでにListenしていないことになります。なので、サーバーは無駄な処理をしてしまったことになります。こういった事態を避けるために、優先度が低いと分類されるリクエストを削除するなどの対応が必要となります。

負荷制限を導入する時に考慮すること クロックの扱いに気をつける

次に、クロックの扱いに気をつけるという内容です。こちらも無駄な処理を防ぐという観点です。「クライアントタイムアウトに達してしまうぐらいサーバーの処理時間がかかってしまったら、途中で処理を中断してくださいよ」という内容です。

この実装を実現するために、クライアントはリクエストにタイムアウトの絶対時間を持たせる必要があります。また注意事項として、クロックをサーバー間で同期させることが重要です。

ここで、サーバー間のクロックが同期していない例を考えてみます。まず、クライアントが2秒後、23時55分12秒にタイムアウトしますという情報をリクエストに持たせて、Server1にリクエストを行います。ここでクライアントとServer1は同じクロックを持っています。そうすると、Server1はタイムアウトまであと2秒あることになります。正しくタイムアウトまでの時間を見積もって、Server1の処理を行うことができます。

次に、Server1からServer2にそのリクエストを投げるような処理があったとします。ここでServer1とServer2のクロックが同期されていなかった場合、Server2が正しいタイムアウトの時間を見積もれません。Server2が処理時間の長いものを処理した場合、クライアントがすでにタイムアウトしてしまっていて、無駄な処理をしてしまったという事態になり得ます。こういった事態を避けるために、クロックをサーバー間で同期させることが重要といえます。

負荷制限を導入する時に考慮すること キューの扱いに注意する

次に、「キューの扱いに注意しましょう」というお話です。過負荷の状態になるとサーバーがリクエストをすぐにさばくことができないので、いったんキューにためておくことがよくあると思います。

リクエストの数が増えていくと、キューされるものも多くなって、キューされる時間がどんどん長くなっていってしまいます。(スライドを示して)このスライドの図の例で、リクエスト1、リクエスト2、R1、R2、R3が、20分、15分、10分と非常に長い時間にわたってキューされているとします。

そうすると、クライアントはもうListenしていないので、レスポンスを返したとしても成功確率は非常に低いです。なので、キューに置かれる上限時間を定めてあげて、古すぎるものは削除するという処理を入れます。

そうすると、新しく入ってきた成功確率の高いリクエスト、このR4を優先してサーバーが処理できます。これによって無駄な処理をせず、成功率の高いリクエストだけを処理してクライアントに返すことができます。

もう1個記事の中で言われているのは、例えば、Webアプリケーションフレームワークを使った時に、内部の実装まで深く意識していないと、デフォルトでどこかでキューしていたりキャッシュをしているような状態があります。そうなると正しい時間が見積もれないから注意しなさいといったことも書かれています。

負荷制限を導入する時に考慮すること レイヤーごとに保護する

次に、レイヤーごとに保護するという内容です。「各レイヤーで負荷制限を実施して、サービスの状況を監視しましょう」といったことをいっています。(スライドを示して)こちらのスライドではAWSサービスのアイコンをいくつか置いていますが、これはAWSサービスで実現される一般的なサービスの実装の例です。

まず左から、AWS WAFは、Web アプリケーションファイアウォールのサービスです。そしてAmazon CloudFrontというのは、CDNのサービスです。そしてElastic Load Balancingというのはロードバランサーのサービスで、Amazon EC2はここではサーバーとして使っています。そして最後にAmazon RDSというデータベースのサービスになっています。

AWS WAFだと、リクエストのレート制限を設定できます。例えば、「5分間に、このIPからは何件のリクエストまで許可しますよ」という設定ができます。

Amazon CloudFrontでは、ディストリビューションあたりの単位時間のリクエスト数が制限されています。またAmazon RDSでは、MAXコネクションで最大接続数が管理されています。このように、各レイヤーで負荷制限を実施できます。

各レイヤーで負荷制限することで、どこで何が起きて負荷制限したのかをあとで追うことができます。つまり、前段となるサービスで後段のサービスのリソースを守ることができるメリットがあります。

負荷制限の実例

最後に、負荷制限の一例についてお話しします。今回は、両備システムズさまの新型コロナワクチン接種予約システムについてです。こちらの例は、予約困難なアクセス数をバーチャル待合室機能で対応した例になります。

バーチャル待合室機能というのは、例えば、予約システムにアクセスした時にシステムが過負荷の場合、待合室のページにリダイレクトされます。待合室のページでは、「あと何分待ってください」ということであったり、「あなたは何人目です」というような表示がされていて、順番が来たら予約システムに入れるようになっている実装になっています。

この例の開発要件の課題についてです。当時、ワクチンが日本にどれぐらい入ってくるかわからない状況だったので、アクセス数がワクチンの流通量に反映されるこのシステムは、アクセス数の予想が非常に困難でした。そして、アクセスの減少に従ってコストの面で規模を縮小させることができる柔軟性のあるアーキテクチャが必要でした。

これを実現させるためのアーキテクチャのポイントについては、負荷状況をCloudWatchでモニタリングして、Auto Scalingでシステムを柔軟に変更させたり、CloudFront Functionsで先着方式と抽選方式の混合方式によるバーチャル待合室機能を作成したりして、最大5分間で1,000万リクエストのアクセスでも動作する実績を残すこととなりました。

この例は、バーチャル待合室機能という負荷制限の仕組みによって、予測不可能であったワークロードを予測可能なワークロードに落とし込んだ例となっています。

負荷制限の実装には考慮すべきことがたくさんある

まとめとなります。負荷制限とは、リクエストのいくつかを脱落させてシステムを保護する手法です。システム全体を落とさずにサービスの継続を可能としていて、予測可能な一貫したパフォーマンスを維持します。負荷制限の実装には考慮すべきことがたくさんあります。

(スライドを示して)こちらに関しては、本発表と元の記事を参照してもらえればと思います。

これで私の発表は以上となります。