ABEMA の Cloud Platform チームの山本 ( @_tetsuya28 ) です。先日開催された、学生向け 3 days インターンである ABEMA Gwroth Tech Vol.3 において運営として参画し、インターン中に利用した負荷試験基盤などの設計・実装を担当させていただきました。
この記事では、この負荷試験基盤についてのアーキテクチャや実際に構築した際のポイントなどについて紹介します。
ABEMA Growth Tech Vol.3 の概要説明
まずはじめに、今回のインターンの ABEMA Growth Tech Vol.3 とは ABEMA での視聴データを元に機械学習を用いたレコメンド機能を API として構築、改善を行うインターンです。実際に ABEMA で利用されている Go や Kubernetes を用いた環境を利用したりなど現場レベルの開発を体験できるインターンとなっています。
ポータルの説明
続いて、この記事で説明するポータルについての概要についてです。
インターン期間中に参加者が利用するポータルサイトです。このポータルサイトでは、ユーザ情報の登録・確認、負荷試験の実行、ランキング結果・エラーの確認ができます。
3 日間のインターンで合計 986 回の負荷試験が実行されました。ここからは負荷試験基盤を含めたポータルについて紹介していきます。
ポータルインフラの概要説明
基本設計方針について
ABEMA Growth Tech は今年で 3 回目の実施となりますが、今回のようにコンペ形式で開催するのは初の試みとなっています。そのため 0 からの設計が必要となります。また、実際に業務で利用するものではないため、使ってみたいサービスや技術を検証する場としても利用したいという思いもありました。そこで、以下の基本設計方針を定めた上で開発を行うこととしました。
- 使ってみたい / 気になる技術を使ってみる
- データはイベントでやりとり
- 能動的にイベントを取得しにいく Worker は実装しない
最終的に設計されたシステムの全体像は以下のようになりました。個別のコンポーネントの詳細については次の章で説明します。
個別コンポーネントの説明
この章では各コンポーネントの役割などを説明します。
- Jim
ポータルのフロントエンド ( Next.JS on Cloud Run ) とバックエンド ( Go on Cloud Run ) をまとめたコンポーネントです。
ポータルのフロントエンドは運営メンバーと学生からのみのアクセスを受け付けるため IAP 認証を追加しています。
また、ポータルのバックエンドもパブリックからのアクセスを受け付けないように、フロントエンド・バックエンド共に VPC 内部にデプロイを行なっています。
Cloud Run を VPC 内部にデプロイを行うために VPC Connector を利用しています。 Terraform を利用している場合、以下のようにgoogle_cloud_run_service
にmetadata
を付与することで VPC Connector 経由での通信を行うことができます。
metadata {
annotations = {
"run.googleapis.com/ingress" = "internal"
"run.googleapis.com/vpc-access-connector" = google_vpc_access_connector.default.name # VPC Connector 名
"run.googleapis.com/vpc-access-egress" = "all-traffic"
}
}
- Harper
Harper では Spanner への負荷試験のキュー追加をトリガーに Spanner Change Data Capture を設定し、 Dataflow 経由で変更されたレコードを GCS に吐き出しています。
Dataflow から GCS へ吐き出す部分は テンプレート が用意されているのでそちらを利用しています。 ( Dataflow から PubSub へも実行できそうだったのですが、テンプレートが存在していなかったため今回は時間都合上利用しませんでした。 )
Dataflow から GCS へ吐き出されたレコードは PubSub 経由で Cloud Run で処理を行います。
Harper の Cloud Run では同一参加者から負荷試験の実行数を制御するため Memorystore ( Redis ) を用いて、 TTL を設定したユーザごとの Key の有無を検証することで実行数の制御を行なっています。
特定ユーザの Key が Memorystore に存在した場合 Cloud Run からエラーを返却することで PubSub のリトライキューに積み、再度リトライを行う仕組みを実現しています。
- Heaven
今回の仕組みには負荷試験は Gatling を利用しています。また、 Gatling を GKE 上で稼働させるために Gatling Operator を利用しています。 Heaven では Harper から送られてきた負荷試験の実行キューを元に Gatling の Custom Resource を生成し、 GKE 上にデプロイを行なっています。
- Macallan
Macallan では Gatling Operator から生成され GCS へアップロードされたファイルを元に Spanner へとデータを書き込みます。
- Scotch
Cloud Run 上で稼働する ML のスコア計算を行うマイクロサービスです。 Macallan の Cloud Run 内の処理中に呼ばれます。
- Irish
定期的な負荷試験をトリガーするコンポーネントです。 Cloud Scheduler での cron 式を元に Irish の Cloud Run を起動し、全チームに対して負荷試験の実行キューを生成します。
イベント駆動アーキテクチャ
今回のポータルは負荷試験の実行、結果などをイベントとして受け渡しを行うことで成り立っています。本章は、ポータルの開発におけるイベント駆動設計の所感についてです。
メリット
- 失敗時のリトライを PubSub に任せられることによるリリース高速化
ポータル開発は開発環境などは作成せずに本番環境のみでの開発を行っていました。また、インターン開催中も機能追加や修正などをかなりの頻度で行っていました。もちろん、インターン開催中は任意のタイミングで負荷試験が実行されるためシステム全体を停止することはできません。しかし、今回のポータルではコンポーネントの繋ぎに PubSub を挟むことでリリースにより、不具合が混入していた場合でも適切なエラーハンドリングを行うことで PubSub による自動リトライが行われます。これにより、不具合を解消することで結果的にはシステム全体のデータの生合成が取れるような構成にすることができました。
難しさ
- PubSub / Eventarc などの At least once
イベントの送信を行うため、コンポーネントの繋ぎに PubSub / Eventarc を利用しています。開発中にデバッグを行っていると Eventarc の後ろ側でデータの処理を行っている CloudRun に同じイベントが複数回飛んでくる事象を確認しました。クラウド慣れしている人にとっては当然の考慮点とはなりますが、クラウドのイベント駆動開発では、多くのイベント送信サービスは 1 回以上の送信 ( At least once ) となっています。 ( 最近は PubSub を含め、 Exactly once をサポートするオプションも実装されています。 )
当初、そこまで複数同一イベントが送信されることはないだろうと実装をしていませんでした。しかし、同一イベントが送信されることでエラーを返してしまい、その結果 PubSub にメッセージが残り続けてしまう状態になったため、 At least once のイベント送信に対応する処理を追加しました。
- 特定の機能のデバッグの難しさ
今回のポータルではイベント駆動での開発を行ってきたため、特定のコンポーネントを動かすためには基本的にイベントを発行する必要がありました。また、今回のシステム全体の唯一のイベント発生ポイントは負荷試験実行キューの追加となります。そのため、特定のコンポーネントの動作確認を行うためにシステム全体を稼働させる必要がありました。これにより最初の繋ぎ込みを行うためにシステム全体の実装を終わらせる必要がありました。その結果、動作確認できるタイミングがインターン直前になったり、不具合が出た際にどこのコンポーネントがおかしいのかを把握するのにかなり時間を取られました。今回は全てのイベントに ID を割り当て、成否を問わずあらゆる処理の過程をログで出力することで Cloud Logging で全て可視化される状態を作りましたが、実際のサービスで全てをログ出力させるわけにはいかないため、他の解決策を考える必要があります。
Gatling レポートの利用方法について
Gatling Operator にはレポート結果をクラウドストレージに吐き出すオプションが存在します。今回はこちらを利用し、負荷試験結果を GCS に送信しています。 GCS に送信されたファイルの中から js/global_stats.json の中身をパースすることで負荷試験結果を取得し、データベースへの書き込みに利用しています。また、 レポートとして出力される req_request-**.html
には負荷試験中に発生したエラー内容が記載されているため、 HTML をパースしエラー内容をデータベースへ登録しています。これにより参加者はポータルから、以下のようになぜ負荷試験が失敗しているかの原因を確認することができるようになっています。
Gatling の負荷試験データのレスポンスチェックについて
Gatling では負荷試験を行うだけではなく、 API からのレスポンス内容を検証し、レコメンド結果をファイルに出力しています。このファイルを GCS へ転送し、レコメンド結果の推薦精度の計算を行うコンポーネントである Scotch で精度を計算しています。この仕組みを実現するために Gatling で負荷試験を行った際のレスポンス内容に関してもファイル出力を行い、出力されたファイルを GCS へ転送しています。推薦制度の計算には正常に返却されたレスポンスの結果のみを使用したいため、HTTP ステータスコードやレスポンスボディの validation チェック、タイムアウトになるかどうかを判断し、正しいレスポンス結果だけをファイルに書き込むようにしました。この方法を用いることで不正なレスポンスを取り除くことができ、正しいレコメンド結果のみデータに含めることができました。これにより吐き出されたファイルを Gatling Operator のレポート転送用のサイドカーに、レポートと同時に GCS に転送してもらうことで後続のコンポーネントに対して負荷試験のレスポンス内容をデータとして渡しています。以下のように、レスポンス内容のファイルを GCS へと転送する処理を追加しています。
感想
インターンまで時間がない中、かなり無茶な実装を手伝ってくれた @u_chi_ha_ra_ をはじめとして運営メンバーには非常に助けられました。また、今回は初めての試みで、インターン中にもいくつか不具合を出してしまいましたが、次に似たようなインターンやイベントを行う際の基礎的な部分に関しては再利用できる形で作成することができたのでまた機会があればさらにブラッシュアップしたものを提供できるようにしたいと思います。
今回作成したシステムについてもっと深く知りたいなどあれば Twitter の DM などでも質問を受け付けているのでお気軽にご連絡ください。