こんにちは。mikanでサーバーサイドエンジニアをしております。星(@hoshitocat)です。 最近ハマっているアニメは「不滅のあなたへ」です。先週はこれを観てうるっときてしまいました... OP曲の宇多田ヒカルさんの曲もよくて、おすすめです!
さて、mikanではGrowthのための施策やプロダクトの方向性を決める際にABテストを行っています。 これまでABテストの振り分けにはRemoteConfigを使っておりましたが、 最近振り分けロジックを変更したので、それについてご紹介したいと思います。 なぜRemoteConfigをやめたのか?みたいな話から、これから使おうと思っている方やすでに使っていて、課題感ある方に少しでも参考になればと思っております。
RemoteConfigを使った振り分け
RemoteConfigは、Firebaseのコンソールなどからあらかじめパラメータを設定しておいて、アプリ起動時などにその設定値を取得し、 それに応じてアプリの挙動などを制御することができるようなサービスになります。(ざっくり理解、詳細は公式ページをご参照ください。)
この設定値は、RemoteConfigで振り分けて返すことができ、以下のように50%のユーザには true
を返したいけど、残り50%は false
にしたいなどの分類が可能です。
これまでmikanでは、この機能を使ってABテストの振り分けを行っていました。
ABテストを実施する際もそのまま使うだけという導入コストの低さゆえに、mikanではRemoteConfigを昔から使っていました。
また、分析にはログをAthenaで分析1し、ABテストの結果などを判断しているのですが、そのログに ab_flags
という、RemoteConfigから取得された設定値を入れることで、
あるユーザがABテストのどのABフラグを持っているかで判断しています。
次にRemoteConfigを使ってよかったメリットとデメリットを示します。
メリット
デメリット
- ヒューマンエラーを防ぐためには専用の仕組みを作る必要がある
- 通信環境が悪いユーザのバイアス
- 通信環境が悪いとRemoteConfigから値を取得できないため、 その場合はABの振り分けがデフォルト値にアサインされてしまい、 ABテストの結果に影響してしまうことになります。
- サーバーサイドのAB振り分けロジックには利用できない
上記にあげた以外に、RemoteConfigにはキャッシュがあり (デフォルトだと12時間詳細はこちら) 一度値を取得し振り分けられたら、キャッシュが消えるまではそのままの値が保持されます。 これは便利なのですが、値を変更しても最大で12時間たたないとその変更後の値にはならないため注意が必要です。 (もちろん取得時間などは調整可能です)
RemoteConfigを使わずに実装した
デメリットであげたもので、特に以下2つ解消するためにABの振り分けロジックを考えました。
- 通信環境が悪いユーザのバイアス
- サーバーサイドでも使える振り分けロジック
前提として、ABテストの結果の分析にはAthenaを使っているので、Athenaで分析可能な振り分けを行う必要がありました。
結果として以下のようなアルゴリズムで実装しました。
- Firebase AuthのUIDとABテスト固有の文字列を連結
- ①の文字列をハッシュ化
- ②から先頭4バイト取得
- 10進数に変換
- ④の絶対値を取得し、100で割った余りを使いABテストの振り分けを行う
サーバーサイドとクライアントと分析基盤で同じ値が取得できることを確認するためにそれぞれで実装を書いてみました。
package main import ( "bytes" "encoding/binary" "fmt" "crypto/sha1" ) func main() { salt := "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ uid := "00wOT95NyMZvnprfjN4uU0AJ67C2" // Firebase Auth UID(開発環境のもの) // saltとuidをくっつけた文字列をバイト配列に変換し、SHA1でハッシュ化 value := sha1.Sum([]byte(salt + uid)) // ハッシュ化されたバイト配列から先頭4バイト取得(32bit符号付き整数へ変換するため) // 先頭4バイトからint32へ変換 var intValue int32 buf := bytes.NewReader(value[:4]) binary.Read(buf, binary.BigEndian, &intValue) fmt.Println(intValue) // ABテストの振り分けに利用する10進数 }
iOSアプリで利用するSwiftの実装
import UIKit import CommonCrypto let salt = "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ let uid = "00wOT95NyMZvnprfjN4uU0AJ67C2" // Firebase Auth UID(開発環境のもの) let str = salt + uid var hash: [UInt8] = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) let strLen = CC_LONG(str.lengthOfBytes(using: String.Encoding.utf8)) CC_SHA1(str, strLen, &hash) let raw = hash[0...3].reduce(0) { $0 << 8 + Int32($1) } print(raw)
package org.kotlinlang.play import java.nio.ByteBuffer import java.security.MessageDigest fun main() { val salt = "cdf3fa31-4cb5-4663-91a8-9274f722e232" // ABフラグ val uid = "00wOT95NyMZvnprfjN4uU0AJ67C2" // Firebase Auth UID(開発環境のもの) val target = salt + uid val targetBytes = target.toByteArray() val sha1 = MessageDigest.getInstance("SHA-1") val sha1Result = sha1.digest(targetBytes) // 分析基盤の処理上、4byteまでしか受けつけれないため、揃えるために先頭4byteを取得するようにしてます val sliceResult = sha1Result.slice(0..3).toByteArray() val result = ByteBuffer.wrap(sliceResult).int println(result) }
分析に使うAthenaでの実装
SELECT from_big_endian_32( SUBSTR( SHA1( CAST( 'cdf3fa31-4cb5-4663-91a8-9274f722e232' -- ABフラグ || '00wOT95NyMZvnprfjN4uU0AJ67C2' AS VARBINARY -- Firebase Auth UID(開発環境のもの) ) ), 1, 4 ) )
メリット
- オフラインでもクライアント側に振り分けロジックを記載すれば振り分けを正確に行うことが可能
- サーバーサイドでも同じようなロジックでABテスト可能
デメリット
- 振り分け時にABテスト時に割合を徐々に拡大する必要がある場合に対応できない
- ABテストの比率50%:50%でテストしたいものだったので、 今回はクライアント側で振り分けロジックの実装をしました。 しかし、5%からはじまり徐々に拡大していく必要のあるABテストの場合はサーバー側で比率を調整し、 それをクライアントから取得するといったように実装しないと、アプリのアップデートありきの割合調整 になるため、比率調整が難しくなってきます。
- 比率だけRemoteConfigから取得することもできましたが、 その場合どうしても、オンラインで通信環境が良いユーザのバイアスが含まれてしまいます。
- 集計が複雑になる
- UIDからハッシュ化などでユーザごとに振り分けがどちらなのか計算する必要があるため、集計が少々複雑になります。 (分析用ログに振り分けに利用したABフラグを付与することで回避できます。)
まとめ
今回紹介した方法では、RemoteConfigを使ったABテストのバイアスがかかってしまう問題を解消できました。 また、共通のロジックを使いサーバーサイドでもABテストができるようになりました。
しかし、管理側で比率をいつでも調整できないことは、今後の課題になります。 RemoteConfigだと通信環境が悪いユーザをABテストしづらいです。 特にオフラインのユーザはサーバーから比率を取得したり、ABフラグを取得することができないためどうしようもないのですが、 通信環境が悪い程度なら、通信コストをもっと抑えた実装をすることで、ABテストを実施できるかもしれません。 ここは調査していきたいです。
最後になりますが、mikanでは現在積極採用中です!
ABテストの分析やその機構の実装に興味がある方、英語が好き、日本の英語教育を変えたいと思っている方は、ぜひ一緒に働きましょう!!
-
弊社で分析してくれている石塚がこれまでの分析基盤の歴史を書いてくれています。また、これらのためのインフラ構成図もあります。↩