コーディング試験サービスの高負荷を乗り越えるために行ったこと
目次
はじめに
こんにちは、株式会社ハイヤールーでソフトウェアエンジニアをしている@icchy_sanです。
みなさんは事前に高負荷が予想されるイベントに備えて、スケールアウトやスケールアップの対応、そして想定している RPS(Requests Per Second)に耐えられるかを検証するために負荷試験を行ったことはありますか?
先日 2 日間で約 4000 人ほどが受験を行うHireRoo(コーディング試験サービス)史上最大のイベントがあり、そのイベントを乗り切るために負荷試験やサーバーのスケールアウト・スケールインを実施しました。今回はおおまかな受験者数が分かっていたこともあり、それに合わせたサーバーの増強、そして検証のための負荷試験を行うことができました。
本記事では、イベント期間の 4000 人の受験に耐えるために実施したことと、イベントを通して感じた反省点・改善点を振り返ります。
想定読者としては、
- これから初めて負荷試験を行う人
- 大きなイベントでトラフィックが増える予定がありその対応をする人
- 負荷試験に興味がある人
です。
HireRoo のサービス構成
何をスケールアウト・スケールアップするのかが分かりやすいように、簡易的に構成を紹介します。
アーキテクチャについての詳細は「HireRoo のインフラ基盤を Cloud Run から Kubernetes へ移行しモノレポで管理し始めた話」にて記載していますが、改めて本記事でも簡易図を載せます。
次図から読み取れるように、HireRoo はマイクロサービスアーキテクチャを採用しており、それぞれのマイクロサービス(以下 MS)は、Google Cloud の GKE(VPC Native Cluster)上で稼働させています。そのため、Pod 内の各コンテナごとに必要な CPU や Memory のサイズを簡単に調整できます。
高負荷に耐えるために実施したこと
捌かなければならない RPS の予測
4000 人の受験ということで 1 日あたり 2000 人、ピークタイムには同時に 300 人程度が受験すると仮定し、以下のように算出しました。
- 試験時間は 2 時間で 4 問(コーディング形式は 3 問)
- 1 問あたり 30 分と仮定
- 過去のデータから 1 問あたりの平均のコード実行回数は 30 回(≒1 分に 1 回の実行)1 回の実行に 5 リクエストが並列で飛ぶ 300 人が 1 分に 1 回の実行
ここから考えられる RPS は、
(300 [人] * 10 [req]) / 60 [s] = 50 [rps]
となり、50 [rps] と算出できます。
しかし、実行の偏りや高負荷によるレイテンシーも考慮し、大きく余裕を持って 100 [rps] を目標値としました。
余裕をもたせることで、この間にスケールアップ・スケールアウトを実施する時間を持つことができるためです。
100 [rps] 耐えるようにサーバーの負荷試験と増強
捌かなければならない RPS の目標値を設定したことで、次に目標値である 100[rps]に耐えうるサーバーの増強に伴う検証を始めました。
前提として、HireRoo では複数の試験形式が提供されており、今回はその中でもコーディング形式とクイズ形式の 2 種類を出題するということが分かっていました。そのため、2 つの試験形式に関連する MS と、HireRoo を利用するうえで必要な MS(認証周りの MS や、Pub/Sub のイベントをハンドリングする MS など)の Pod のスケールアップ・スケールアウトを実施しました。
もちろん Kubernetes のHPAの設定はしていますが、100[rps]ともなるとスケールアウトに時間がかかり、レイテンシーが上がってしまい、ユーザー体験が損なわれてしまうリスクがあるため、事前に負荷試験とサーバーの増強を行いました。
負荷試験に際して「負荷試験 | ニコニコ生放送 Web フロントエンド Kubernetes 移行ハンドブック 2022」を参考にし、Datadog上でモニタリングしながら行いました。負荷試験のツールとしては、vegetaを採用し実際に受験者がコールする BFF のエンドポイントに対して負荷をかけていきました。
BFF のエンドポイントに対して負荷試験を行った理由としては、次のとおりです。
- 受験するために必要な BFF のエンドポイントを叩くことで関連する MS も同時に負荷試験ができる
- 受験をするうえで想定される呼ばれ方を再現できる
以上を踏まえて、単体の MS のエンドポイントごとに細かく負荷試験を実施するのではなく、受験者がコールする BFF のエンドポイントを今回の負荷試験対象として選びました。
負荷試験を実施するうえで、注意したこととして参考リンク先にも記載がある通り、負荷試験で利用するツールに渡すパラメータ(RPS)が正しいものだとしても実際にはリクエストを投げる元のネットワーク起因で 100[rps]投げることができなかったり、負荷試験ツール自体が目標としてる RPS をサポートしていなかったりすることもあるため、最初に正しい計測が可能かどうかの検証を行いました。正しいかどうかは、負荷をかける対象のエンドポイントに一定期間負荷をかけ、頭打ちする値が設定している RPS になっているかどうかで判断しました。次図は一定期間負荷をかけたときに各サーバーで捌きたい RPS が出ているかどうかを検証するために作成したグラフ(横軸:時間、縦軸 RPS)です。
実際にはツールの懸念に加えてメトリクスの集計バッチの間隔によるブレも生じるものの、集計間隔とリクエストの総数を確認して今回の負荷試験の範疇においては正しく計測できていることが確認できたため、vegeta を用いて負荷試験を続行しました。
その結果 BFF については Pod を 3 台から 15 台まで増やし、内部のコンテナに割り当てる Resource(CPU と Memory)も増強しました。
BFF の Pod に加えて、呼び出す MS も同様に
- 100[rps]実行しても Timeout が返ってこないこと
- レイテンシーに関しても許容される範疇に収まること
を確認し条件を満たすために必要な Pod 数・割当リソースを増強しました。
イベント期間のサポート体制
イベント期間のサポートは時間ごとにオンコールできるメンバーが交代交代で集まる形をとり、
より詳細を記載すると「受験者からの問い合わせ対応班」と、「問い合わせ内容の調査班」と 2 組に分かれて動くようにしました。
この体制によって受験者の対応をしながら、裏で調査を並列で行い、問い合わせしてきた方をなるべく待たせないように工夫しました。
もちろんなるべく問い合わせが来る前に対応することで、問い合わせの数も減らすことができるため、モニタリング基盤(アラートを含む)も事前に構築しました。
弊社には専任の SRE が存在しないため、各 MS の開発者がインシデントやアラート発生時に対応を行っています。
そのため、各 MS 開発者が問題を解決するために必要な情報になるべく迅速にたどり着けるような基盤を構築を行っており、基盤を構築するうえで利用している Observability のサービスとしてはDatadogです。Datadog を活用して Monitoring や APM を利用したアラートの Slack への通知だったり、アラート発生時にどの MS がボトルネックになっているかの確認だったりを迅速に行うことができるようになっています。
今回はその 2 つに加えて Datadog の Dashboard も活用してリクエスト数、リソースの使用率を監視し、リクエストが多くなってきた MS があれば、その MS のスケールアップ・スケールアウトを検討するといったことも行っていました。
これらすべてが Terraform により IaC 化されていることで、新規 MS を作成するタイミングで自動で構築されるため、今回のイベントでもすべての MS でぬけもれなく監視することができていました。
Observability が漏れなく構築されていたことで、受験者の書いたコードの評価が失敗した場合に鳴るアラートの原因となるエラーを迅速に究明し対応することができ、サービス上のバグによるインシデント発生時には、Google Meet を利用して同期的に原因調査を行うことで早期復旧を行うことができました。
イベント期間のリクエストの増減
イベント期間のリクエストの増減は次の棒グラフの通りで、普段のリクエスト数(Fri 21 あたりや Mon 24 以降)と比べると、言葉通り桁違いの量になっていることが読み取れます。
実際は 100[rps]来ることはなかったものの、100[rps]に近いリクエストはピーク時に観測されていたため、負荷試験によるスケールアップ・スケールアウトを実施していなければ大量の受験者から「試験が受けられない」という問い合わせで溢れかえっていた可能性がありましたが、最終的には過負荷によるインシデントを起こすことなく 2 日間を終えることができました。
今回のイベントを経ての反省点
サポートメンバーを当日にきめてしまった
本来であれば、サポートメンバーは事前にシフトを組んでおくことで当日誰がどの時間に対応しているのかを全員が把握できるため、そうするべきではあったが、決めておらず当日にサポートできるメンバーを確認するということをしてしまいました。幸いなことに誰かしらが空白の時間を作ることなく対応できる人がいたため、問題ありませんでしたが次回以降は必ずサポートメンバーのシフトを作成することをオペレーションに追加します。
シフトを組まない場合に発生する問題として次のことが考えられます。
- 誰が対応できるのか把握できない Slack でのメンション先が分からないインシデントが発生した際に対応が被ってしまい更に大きなインシデントにつながってしまう可能性がある
- 誰も対応していない時間が発生する可能性がある大きなインシデントが発生した場合にサービスの信頼性が下がる問い合わせが来たときに対応できず受験者の受験体験が悪くなる
少なくともシフトを組むことで上記の問題を回避することができるのであれば、小さな工数で大きなリスク回避になるため次回以降必ず実施するようにします。
インシデント発生時にインシデントタイムラインを作成していなかった
受験者に影響を与えるバグによるインシデントが発生した際に、同期的に対応を行うことで迅速に対応することができた一方で、今回の対応時にインシデントタイムライン(誰が何を行っているのか・行ったのかという情報をまとめたもの)を作成していませんでした。
インシデントタイムラインを作成することで、インシデントを管理する人が、情報整理を行ったり、他の人が情報をキャッチアップするためのログを残すことができたり、ポストモーテムを実施する際にも役に立ったりするため SRE の文脈ではとても重要なものです。実際にあとから入ってきたメンバーがキャッチアップしたタイミングでは口頭による共有を受けるしかありませんでした。
そこで、インシデントタイムライン作成含むインシデント管理については、すでに対策を講じており、インシデント発生時には Postmortem のテンプレートから Google Docs を作成し、調査ログを書きながら対応する運用に変更しました。そうすることで後から参加するメンバーが「どこまで」調査していて、「何が」分かっている状態なのかを理解する手助けになったり、Postmortem をチーム内で共有する際により詳細な情報を共有することができます。加えて、テンプレートにはインシデント対応に際してやるべきことを定義し、対応にのみ集中してしまい他のチームへの連絡を怠ってしまうということも防ぐようにしました。
まとめ
2 日で 4000 人が受験するという大きなイベントがあったが、アクセス集中による負荷が原因のインシデントを起こすことなく乗り切れたことは弊社にとって一つのステップアップにつながったことは間違いないです。
今回のイベントを通して、
- 負荷試験で確認しなければいけないところ
- 利用するツールが本当に正しい値を観測できているのか確認すること
を再認識することができ、なんとなく負荷試験ツール使って検証すれば問題ない”だろう”と考えていたところを見直すきっかけになったので個人的にこの経験はとても良い経験でした。
また、オンコール対応周りやインシデントタイムライン部分には改善の余地があることも分かったため、弊社の Value である Fail Fastを体現して、反省から学び次回以降はしっかりと Reliability を担保できるように仕組みを改善していこう、という気持ちになりました。明示的に SRE という役割はないですが、Reliability に関する部分の改善を今後も行っていこうと思います。