WINTICKET の Web 版(以降 WINTICKET Web)のテックリードを担当している @dora1998 です。

WINTICKET Web の GKE 脱却と Cloud Run の採用」にて、2022 年に取り組んだ Web バックエンドリアーキテクチャの背景や概要を紹介しました。続編となる本記事では、本番環境に対してリアーキテクチャを適用した際の移行プロセスについてお話しします。

リアーキテクチャに伴う移行概要

具体的な移行プロセスの紹介に入る前に、リアーキテクチャ前後の Web バックエンドの構成について触れておきます。以下の図は、前回の記事にある画像を編集したものですが、赤枠で囲った箇所が移行に伴って変わった箇所になります。

リアーキテクチャ前後のバックエンド構成図

前段にある CDN の Fastly から Cloud Load Balancing でリクエストを受けるところまでは、リアーキテクチャ前後で変更はありません。 Cloud Load Balancing で受信したリクエストはアプリケーションに渡されますが、この接続部分を Google Kubernetes Engine から Cloud Run へ移行するのが主な作業内容となります。

移行要件の整理

本番環境を移行するにあたって、まずゼロダウンタイムで移行できないかを考えました。調査を進めたところ、既存のバックエンドはそのままに、並行して新しいバックエンドを立てれば、CDN の Fastly からゼロダウンタイムで接続先の切り替えが行えることがわかりました。

また、いきなり全てのトラフィックを切り替えるのはリスクが高いため、徐々に切り替えるプロセスを検討しました。その結果、ミラーリングで実負荷に耐えられることを確認してから、Canary リリースで切り替えていくこととなりました。

ゼロダウンタイムで移行するための手順

ここからは、本番環境を移行完了するまでの実際の作業内容について紹介します。

1. 既存構成と共存する形で新しい構成を構築

まず、既存構成には触れず、新しい構成が並行して動く状態を構築しました。移行要件の整理で図に示したように、新バックエンドは旧バックエンドとは異なるドメインで接続できるように設定しています。

CI/CD ワークフローも新旧両方を並行して実行できるようにしたため、万が一切り戻しが発生しても旧バックエンドで引き続きリリース作業が行える状態を保つことができました。

2. 負荷試験の実施

負荷試験の目的と測定内容の検討

負荷試験でどのような測定が必要かを考えるには、まず負荷試験の目的を定めることが重要です。今回負荷試験を行った背景には、大きく 2 つの目的がありました。

まず 1 つ目は、予想されるリクエスト数からスケーリング設定が算出できるようになることです。リアーキテクチャ前の GKE 環境では、CPU 使用率に基づいて Pod の数を増減していました。しかし、Cloud Run ではインスタンスあたりの最大同時リクエスト数によってインスタンスを増減させるため、最適な設定値を探る必要があります。

2 つ目の目的は、リアーキテクチャ前後で大きくパフォーマンスが劣化していないと確認できることです。想定リクエスト数の負荷に対し、現行の GKE 環境と比較して大きな性能劣化はないか、Cloud Run のオートスケーリング特性がどうなっているかを確認しました。

以上の目的を踏まえ、まず 1 インスタンスで処理可能なスループットを測定することにしました。そして、負荷試験の結果をもとに最大同時リクエスト数の設定値を調整し、算出したスケーリング設定で目標 RPS が捌けることを確認しました。次に、具体的な計測方法について紹介していきます。

インスタンスあたりのスループットの測定

まず、Cloud Run の最大・最小インスタンスを 1 に設定し、リクエスト数に関わらず常に 1 インスタンスのみが立ち上がる設定としました。次に、測定対象としたページに対して徐々に負荷をかけました。

インスタンスあたりのスループットの限界を知るため、リクエストが捌ける間は RPS を上げていき、エラーレートが著しく上昇する境界を調査しました。

算出したスケーリング設定で目標 RPS が捌けることの確認

先ほど測定したスループットをもとに、インスタンスあたりの最大同時リクエスト数を仮置きし、今度はスケーリングできる状態で目標 RPS が捌けるか確認しました。

大まかな目安ではありますが、

(全インスタンス合計での最大同時リクエスト数) = (目標 RPS)

となるように、最大インスタンス数には

(目標 RPS) / (1インスタンスあたりの最大同時リクエスト数)

で求めた値を設定して測定を開始しました。

測定では目標 RPS の負荷をかけて、捌けない場合は CPU 使用率を確認して調整を行いました。具体的には、CPU 使用率が高い場合はインスタンスあたりの処理できる限界に達しているため、インスタンス数をさらに増やしました。一方、CPU 使用率にまだ余裕がある場合は、インスタンスあたりの最大同時リクエスト数を増やして調整を行いました。

3. ミラーリング

負荷試験を終えた後は、本番環境の負荷を利用して動作を確認するため、ミラーリングを行いました。

ミラーリングとは、ユーザーからのリクエストを新旧両方のバージョンに送り、レスポンスは旧バージョンから返すことで、ユーザーに影響を出さずに動作確認する手法です。ミラーリングはシャドーテストとも呼ばれ、Google Cloud のドキュメントでも取り上げられています。

なお、ミラーリング中はリクエストが二重に処理されるため、対象は副作用を起こさない冪等性のあるリクエストが前提となります。今回は GET リクエストに限定して実施し、加えて外部連携のコールバック先 URL なども対象外としました。また、ユーザーに影響のある副作用の他に、行動ログなども二重に記録されないか事前に確認しました。

リアーキテクチャ以前のバックエンドはプロキシとして Envoy を使っていたため、Envoy から新しいバックエンドに対してミラーリングを行いました。ミラーリング時の設定は以下のようになります。

WINTICKET では、レーススケジュールの都合から 1 日の中でリクエストが少ない時間帯と多い時間帯が存在します。一部のグレードレース期間などを除いて、日ごとのアクセス数に大きな変化はないため、1 日程度ミラーリングを実施して動作を確認したのち、新旧バックエンドの切り替え作業に移りました。

4. Dark Canary で疎通を確認

一般ユーザー向けに切り替えを行う前に、まずは Dark Canary でリリースを行い、 HTTP リクエストに特定のヘッダが含まれる場合のみ、新バックエンドに接続されるように設定しました。社内の開発メンバーで接続テストを行い、Fastly 経由で問題なく接続できることが確認できました。

以下の図では、通常のリクエストは origin.example.com に繋ぎつつ、HTTP リクエストに X-Backend-Version: v2 が含まれていた場合は、origin-v2.example.com に繋いでいます。

Dark Canary 設定時の Fastly の管理画面のスクリーンショット

5. Canary リリース

次に、Fastly で新バックエンドにトラフィックを流す割合が 5%、20%、50%、80%、最後は 100% となるように段階的にロールアウトしていきました。以下の図では、新バックエンドに 5% のトラフィックを流すように設定しています。

Canary リリース中の Fastly の管理画面のスクリーンショット

振り返り

バックエンドの移行作業を無事に終えることができ、実作業の中で得られた知見も多くありました。その中からいくつか主要なものを紹介します。

適切な統計情報に基づく負荷試験シナリオの作成

当初、負荷試験の対象として、トップページやレース詳細ページと呼ばれる、サービスの中でも主要とされるページを対象にしていました。しかし、オリジンサーバーのアクセス解析を行うと、トップページやレース詳細ページは、CDN にキャッシュされるので見込みよりアクセスは少ないことがわかりました。

WINTICKET Web では、ほとんどのページを CDN にキャッシュさせていますが、ユーザー固有の情報が含まれるマイページ配下はキャッシュ対象外としています。そのため、オリジンサーバーに到達するリクエストのうち多くの割合をマイページ配下が占めていましたが、その中でもチャージページへのアクセスが大半でした。

調査を進めたところ、チャージページは Web 版のユーザーのみならず、アプリからも WebView 経由でよく開かれるページだったため、想定よりアクセスが多かったことが判明しました。

既に稼働しているサービスの場合、負荷試験のシナリオは必ず適切な統計情報を参照して作る必要があると改めて実感しました。

負荷試験を実行する環境の選定

当初、数十 RPS 程度で試すので問題ないと思い、ローカルマシン 1 台から負荷試験を実施していました。ただ、想定よりも小規模のリクエストでもエラーレートが高く、原因調査を行いました。

Cloud Run や Cloud Load Balancing にエラーやメトリクス上の怪しい兆候なども見つからず、とりあえず Compute Engine のインスタンス上から実行したところ、当初想定していたような結果となりました。

やはり負荷試験は規模に関わらず、常にローカルマシンとは異なる環境から実施するべきでした。

ミラーリング実施中の関連サーバーに対する負荷考慮

ミラーリングを行うとリクエストが二重に処理されるため、Web バックエンドから接続している関連システムへの負荷も上昇します。WINTICKET Web では、SSR 時に API サーバーにアクセスしていたため、API サーバーへのリクエスト数も 2 倍になることが想定されました。

そのため、今回は実施前に API サーバーの開発メンバーに想定負荷を共有し、ミラーリング実施期間中のスケールアウトを依頼しました。

まとめ

本記事では、WINTICKET Web のバックエンド構成をゼロダウンタイムで移行するための負荷試験やミラーリング、Canary リリースについて紹介しました。

ゼロダウンタイムで切り戻しも容易な形で移行できたことで、事業影響を抑えられただけでなく、移行作業を行う作業者の精神的負荷も下げることができました。

また、負荷試験を通じて、既存アプリケーションが抱えるパフォーマンス上の問題も発見できたので、こちらは別途改善に取り組んでいきたいです。

次回は、リアーキテクチャ後のリリースに関連した様々なオペレーションを支える Release Manager について紹介予定です。お楽しみに!