はじめに
こんにちは、バックエンドエンジニアのおおたわらです。
弊社のプロダクトの一部に、顧客企業の行いたい調査(例えば、企業が顧客のニーズを理解し、最適なサービスを提供をするためのユーザー向け調査)に応じてアンケートを作成し、回答を集めることができる「アンケート機能」があります。
この機能はコンシューマ向けの大規模な調査(具体的にはメールやプッシュ通知等で数十万〜数百万人にアンケート回答を依頼するような調査)での利用も想定しており、大量アクセスに対応できることが求められます。更に、集めた回答に対し分析を実施し、すぐに結果を確認できることも求められます。
このためのアーキテクチャを構築したので、紹介します。
求められる要件
アーキテクチャに求められる主な要件は以下です。
- 大規模調査における大量のリクエスト(具体的には秒間 1000〜回答を想定)を処理できるスケーラビリティ
- 回答サーバーは可用性が高いこと
- 停止すると、顧客の声を集める機会が失われてしまうだけでなく、アンケートを実施する顧客企業のブランドイメージ毀損に繋がる恐れがあるため
- 回答者のユーザー体験を損ねないよう、アンケート設定内容取得・回答の送信は低レイテンシであることが望ましい
- 回答データの損失は許容されない
- 受け付けた回答は分析機能のユーザーがほぼリアルタイムで確認できるのが望ましい
アーキテクチャ
構成図は以下のようになっています。
以下、各コンポーネントについて紹介していきます。
Cloud Load Balancing
回答サーバーの前段のロードバランサーとして使っています。
自動スケーリングにより大量アクセスにも柔軟に対応可能です。
Cloud Run
アンケートの設定情報(質問設定など)の返却と、回答を受け付けてバリデーション(必須の質問に回答があるか等)を行うサーバーです。
自動スケーリングにより、大規模調査における大量のリクエストにも対応できています。インスタンスの最大値や同時実行リクエスト数といった、自動スケーリングに関わる設定値は性能試験を行って適切な値を探りました。
アプリケーションは Rust で実装しています。言語の選定時にはあまり意識していませんでしたが、コンテナ起動が速いため(現状 1 秒かからない程度)、リクエスト急増時の迅速なスケールに一役買っています。
AlloyDB
アンケートの設定情報が保存されています。
可用性が求められるので、Cloud SQL に比べて SLA の高い AlloyDB を採用しました(注: Cloud SQL Enterprise Plus エディションであれば SLA が 99.99 % と AlloyDB と同等ですが、技術選定当時は登場前でした)。
AlloyDB は高パフォーマンスなトランザクション処理が強みではあるものの、高負荷時には後述する Memorystore for Redis からの情報取得を前提としています。
Memorystore for Redis
AlloyDB に保存されたアンケートの設定情報をキャッシュする目的で使っています。
高負荷下でも低レイテンシでデータを取得可能なため、回答者の体験を損ねません。
Pub/Sub
回答者からすると回答送信のレイテンシは低いことが望ましい一方、弊社のプロダクト上で回答が確認できるようになるまでの厳しいレイテンシ要件はありません。
そのため、Cloud Run で回答を受け付けたら Pub/Sub にパブリッシュしてレスポンスを返し、データストアへの保存は非同期に行う方法を取っています。パブリッシュのレイテンシは低く(実際に運用してみたところ、50 パーセンタイルで数十ミリ秒程度)、回答者の体験を損ねません。
また、Pub/Sub は信頼性とスケーラビリティに優れているので、大規模調査のトラフィックに対応しつつ回答データの損失が起きないことを保証できます。
Dataflow
Pub/Sub からサブスクライブした回答データを、顧客企業・アンケートごとに分かれた BigQuery のテーブルに振り分けています。
Dataflow には様々な自動スケーリングの機能がありますが、水平自動スケーリングのみを 設定しています。
(Dataflow については、過去にもこのブログで紹介しているので、よかったらご覧ください。)
BigQuery
分析で使いやすいよう、回答データは BigQuery に保存しています。
Storage Write API
保存は Storage Write API のストリーミング取り込み(at-least-once)により行っています。
Storage Write API の書き込みスループット上限はマルチリージョンでは 3 GB/秒、リージョンで 300 MB/秒と高いです(参考)。これが大量のアンケート回答を効率よく保存するのに非常に役立ちました。最初はこの方法ではなくバッチ読み込みの利用を検討していましたが、回答を一度 Dataflow でファイルアウトする必要があるためディスク I/O がボトルネックになってしまいました。実際に試してみたところ、高負荷下では非常に多くのインスタンスが必要となってしまい、運用は現実的ではなかったです。
また、ストリーミング取り込みであるため、受け付けた回答はほぼリアルタイムで参照できます。リアルタイム確認のユースケースのために BigQuery 以外のデータストアを構築しなくて済み、コストを抑えることができました。
データの保存方法
データセットはアクセス制御等の関係から顧客企業単位で分けています。また、テーブルはアンケートごとに分けています。これにより、アンケート単位での分析が行いやすい上、回答データ(個人情報を含むことがある)が不要になった際もシンプルな手順で削除が行えます。
Dataflow から送られた回答はまず回答テーブル(アーキテクチャ図で _raw と付いたテーブル)に JSON 型の 1 カラムに全質問分が入って保存されます。分析利用を考えると質問ごとに列が分かれた形式で保存したかったのですが、大規模調査を想定した負荷をかけて試験したところ Dataflow において回答データをパースし各列に分ける処理が性能のボトルネックになってしまったため見送りました。
後段で、質問ごとに列が分かれたビュー(アーキテクチャ図で _analysis と付いたビュー)にマテリアライズドビューを使って変換を行っています。マテリアライズドビューは BigQuery 側で自動で更新されるため、データ変換のためのジョブ管理やインフラが不要であり、エンジニアメンバーが限られている弊社にとっては開発工数や運用負荷を下げることができたのが良かったです。
おわりに
Google Cloud の強力なインフラを活用し、大規模調査に耐えながら回答を分析に活用できるアンケートシステムを構築することができました。
実際に何度か大規模調査を経験しましたが、サーバーダウンは発生せず運用できています。更に規模の大きい調査などでも活用していけるといいなと考えています。
エモーションテックでは顧客体験、従業員体験の改善をサポートし、世の中の体験を変えるプロダクトを開発しています。ご興味のある方はぜひ採用ページからご応募をお願いいたします。