こんにちは!タイミーでAndroidエンジニアとして働いている @orerus ことmurataです。今回は弊社のアプリ開発チームで経験した、Firebase Remote Config(以下 RemoteConfig)を使用したABテスト実装時のトラブルと、その再発防止策について共有いたします。
はじめに
モバイルアプリ開発において、ABテストは機能改善の効果を測定する上で重要な手法の一つです。今回は、私たちが実装したABテストで発生した予期せぬ動作と、そこから学んだ教訓についてお話しします。
おことわり
なお、今回の話はRemoteConfing自体に問題があるというものではなく、使用方法が適切でなかった為に発生した事象ですのでご留意ください!
発生した事象
実装内容
今回の事象のきっかけとなったのは、アプリ起動時にまず表示される「さがす画面」において、検索結果のソート順切り替え機能のABテストの実装でした。
リリース後は何事も問題なく動作していたのですが、しばらく経ったある特定のタイミングで同時に複数の不具合報告が挙がりました。
報告が挙がった不具合内容は「ソート順がおかしい」「検索結果が表示されない」といった事象でした。
以下は、実際に不具合が発生していた時の画面の例です。 アプリを開いた直後に表示される一番大事な検索結果の部分が空っぽになってしまっていますね・・・。
問題の具体に入る前に、ここでこの「さがす」画面の構成について簡単に説明します。
画面構成
こちらの画面の構成としては、画面上部のカレンダーを含む画面全体、および画面下部の検索結果が表示されている部分とで異なるFragmentが使用されています。(前者をCalendarFragment、後者をResultFragmentと仮称します)
検索結果を表示するResultFragmentは日付ごとに存在しており、ViewPagerにて管理しています。
カレンダーから日付が選択されると、その日付の検索結果を表示するためのResultFragmentが生成され表示されます。
ABテスト制御
ABテストの制御にはRemoteConfigを使用しており、CalendarFragment、ResultFragment、それぞれのFragmentが生成されるタイミングでRemoteConfigからソート機能のON/OFFのConfig値をそれぞれのFragment内に保持して使用しています。
不具合発生!!!
平穏に暮らしていた中で突如同時多発的に不具合報告が挙がったタイミング、それがABテストのロールアウトを行った時でした。
この時は全ユーザーに対してソート順切り替え機能をONにするロールアウトを行いました。
その結果、先述した「ソート順がおかしい」「検索結果が表示されない」といった不具合が複数のユーザーから報告されました。
もちろんすぐに原因調査を行いましたが、手元の環境では事象がなかなか再現せず困っていたところ、ABテストのロールアウトを行っていたことを思い出しRemoteConfigから取得したConfig値の利用箇所周りを重点的に調査した結果、その利用方法に問題があり先述のような不具合が発生する可能性があることが判明しました。
以下、その不具合発生に至った原因について解説します。
原因分析
1. RemoteConfigから取得したConfig値をFragment毎にキャッシュしていた
画面が破棄されるまでの間にConfig値が変化したとしても突然画面内で機能が変化しないように、Fragment生成時にRemoteConfigからConfig値を取得し、インスタンス変数にキャッシュしていました。
このキャッシュのやり方は画面内で1箇所のみでしか行われない場合には問題が生じませんが、画面内に複数のFragmentがある場合に不整合を生じさせる余地が発生してしまいます。
とはいえ、それだけなら不整合が発生する確率は低かったのですが、次の原因がその確率を大きく引き上げてしまいました。
2. Fragmentの生成タイミングの違い
先述した通り、この画面には複数のFragmentが存在しており、またそれぞれ生成タイミングが異なります。
- CalendarFragment
- 「さがす画面」が表示されたタイミングで生成される
- ResultFragment
- 日付が選択されたタイミングで生成される
この生成タイミングの違いにより、現実的に起こり得るケース(例えば「さがす画面」のままアプリが長時間バックグラウンドになっており、復帰後に日付が再選択されたケースなど)でCalendarFragmentおよび複数日付のResultFragmentの間でConfig値に不整合が発生してしまいました。
細かなロジックは省略しますが、この不整合が引き金となり冒頭で紹介したような不具合が発生していました。
なお、今回の事象が防げなかった原因がもう一つあります。
3. QAカバレッジの不足
今回のABテストについてももちろんQAを行っていたのですが、以下の観点が意識されておらずテストケースから見落とされてしまっていました。
- アプリ起動中のRemoteConfigの値更新
- Config値を複数箇所で保持することによるFragment再生成時の不整合発生の可能性
一度起きてしまえば「何故気づかなかったのだろう」と思えるようなシンプルな原因ではあるのですが、「画面が破棄されるまでは同じConfig値が使われる」という思い込みが気づきを遠ざけてしまっていました。
再発防止策
再発防止策として以下の取り組みを実施しました。
1. QAプロセスの改善
QAチェックリストのテンプレートに、RemoteConfigを用いたABテストやFeatureFlagの実装時はアプリを生存させたまま値を動的に更新するテストケースを実施する旨を追加しました。
2. RemoteConfigに関するデバッグ機能の拡充
RemoteConfigは内部でキャッシュされており、先述の動的なConfig値の更新のQAを行うことが困難であった為、デバッグ時にConfig値を容易に変更できる機能を実装しました。
3. ActivityやFragmentでのConfig値のキャッシュを止める
こちらは今後の話になりますが、ActivityやFragmentでConfig値をキャッシュすると類似の問題が発生する可能性がある為、画面(または一連の機能)を構成する単位で必ず同一箇所のConfig値のキャッシュを参照するような構成への変更を検討しています。例えば、画面(または一連の機能)から参照される共通のViewModel内でのキャッシュを考えています。
なお、アプリの起動中は全てのConfig値が変化しないようにするという選択肢も考えられますが、FeatureFlag管理にもRemoteConfigを利用しており、Config値の変更は可能な限り速やかに行いたい(将来的にFirebase Realtime RemoteConfigへの置き換えも視野に入れている)為、そちらの選択肢は選択しませんでした。
まとめ
今回の経験から、以下の教訓を得ることができました。
- 画面または一連の機能(整合性を保ちたい単位)で必ず同一箇所のConfig値のキャッシュを参照するようにする
- 例えば共通のViewModel内でのキャッシュなど
- RemoteConfigから取得する値は変化するものという前提のうえでQAを行う
RemoteConfigは非常に便利な機能ですが、適切な実装と十分なQAがないと思わぬ落とし穴に遭遇する可能性があります。今回の紹介で、自分と同じような体験をしてしまう方を少しでも減らすことができれば幸いです・・・! 皆様安心安全な状態でクリスマスや年末を迎えましょう!