爆速でGo!

GoGo!

Feature Flagを用いたA/B TestingツールをGCPで設計する

先日、AbemaTVが開催する「AbemaTV x A/B Testing」にてFeature Flagを用いたA/B Testingツールのインフラアーキテクチャ設計を行いました。
その時の学びをまとめていきます。

要件

Feature Flagを用いてA/Bテストできれば良いのですが、以下は守る必要がありました。

  • Uiの表示速度に影響しない
  • データに対して柔軟に素早く集計・分析できるようにする
  • スケーラブル

アーキテクチャ構成図

f:id:NakaWatch:20180312110231p:plain

僕達の最終的なアウトプットはこのようになりました。
色々書いてありますが、大事なのは以下の2つです。

  • 構成図下部のclientアプリがフォアグラウンドに切り替わったタイミングでfeature flagを受け取る。
  • test対象のclientは定期的にaction logをlogging service(構成図右下)にpostする。

Feature Flag取得時のキャッシュ戦略

f:id:NakaWatch:20180312113110p:plain

上図の通り、とりあえずUIを表示し非同期で取得し次回起動時に反映するという方式をとりました。
こうすることで、「UIの表示速度に影響しない」という要件を満たします。
しかしデメリットもあります。
それはFeature Flag取得までにラグがあることです。
すぐにFlagを切り替えたい時に、二回のアプリ起動が必要になります。
そのためアプローチとして以下の措置をとりました。

  1. プッシュ通知でアップデートフラグをローカルに立てる
  2. クライアントが次回アクティブになった時に強制的に反映(ロードが生じる)

わざわざローカルにフラグを立てるのは、プッシュ通知時に一斉にアップデートするというモデルはスケーラブルではないからです。

スケーラブル戦略

最も瞬間的にアクセスが集中する部分はflag取得前のtestユーザーチェックだと考えました。
(↓緑で囲われてる部分)

f:id:NakaWatch:20180314093944p:plain

そこで、MySQLの前段にRedisを置くことを決断しました。
set型の集合にuser_idをkeyにして、testユーザーチェック等の処理に必要な情報を置いておきます。
こうすることで、O(1)でのreadが可能になります。
また、Redis Clusterを使用することで複数ノードに自動的に分散することができます。 永続化とクラスタリングにはデータ更新時に整合性の問題がありますが、今回はtest終了時まで更新はないので問題ありません。

各micro serviceの仕事

lab receive service(構成図左下)

  • Labからセグメント情報もらう
  • ユーザーがアンケート等で直接入力した値と比較し、いい感じに挿入

logging service(右下)

  • clientから生のaction logを受け取る
  • redisからuserが参加中のtest情報を受け取り、BigQueryに挿入

aggregate service(右上)

  • バッチでBigQueryに対してクエリを実行
  • 管理画面からの呼び出しによって人為的にクエリ実行

cache service(左上)

  • テスト実行者が設定した情報をもとに、前段に置く値を決定する

フィードバック

構成図右部のlog集計部分をLambda Architectureに従って設計するべきだとのフィードバックを受けました。

f:id:NakaWatch:20180314094938p:plain

Lambda Architecture

Lambda Architectureとは、以下3つのレイヤーから構成される設計指針です。

バッチレイヤー:ログをバッチで集計するので正確だけど、リアルタイムではない
サービスレイヤー:バッチレイヤーの集計結果を提供する
スピードレイヤー:データはリアルタイムだが、データは少し不正確の可能性ある

スピードレイヤーの集計で得た最新の値とバッチレイヤーの集計によって得た確定した値をマージすることで、素早く推定することが出来るというモデルらしいです。
まだあまり理解していないので、詳しくは以下書籍を読んでみます。

www.oreilly.co.jp

まとめ

無知は罪だということを痛感しました。引き続きインプットの質を高めていこうと思います。