この記事は Todd Kerpelmen、デベロッパー アドボケートによる The Firebase Blog の記事 "Firebase Remote Config loading strategies" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。


Todd Kerpleman



Todd Kerpelman
Developer Advocate
FirebaseDevelopers チャンネル ファンの皆さんなら、Remote Config からアプリに値を読み込む最善策を紹介した動画が公開されたことをご存じかもしれません。




ですが、動画を見るより記事を読みたい方もいるでしょう。そこで、この内容を便利なブログ形式でもご紹介します。

すでに Remote Config を使ったことがある方なら、アプリでの Remote Config の実装は主に次の 3 つのステップで行われることをご存じでしょう。
  1. Remote Config のさまざまな既定値を設定します。これは、コードで、またはローカルの plist や xml ファイルから値を取り込むことで、端末上でローカルに設定されます。
  2. 次に、fetch() を呼び出して新しい値をクラウドからダウンロードします。
  3. 最後に activateFetched() を呼び出し、最初に設定された既定値に対してダウンロードした値を適用します。
この時点で、Remote Config に特定の値を問い合わせると、クラウドからアップデートされた値か、ローカルに設定されている既定値のどちらかが取得されます。これはすばらしい動作です。アプリの すべての 値を Remote Config 経由で取得できるようになるからです。既定値とは異なる値のみがダウンロードされるため、ネットワークの呼び出しは適切かつ最小限に抑えることができます。しかも、アプリのあらゆる動作を後からクラウド経由で変更できるという柔軟性が得られます。

とても簡単だと思いませんか。基本的には、そのとおりです。問題なのは、先ほどのステップ 2 です。ここではネットワークの呼び出しが必要になりますが、現実の世界では、トンネル、エレベーター、砂漠などのネットワーク接続が不安定な場所でアプリを使う人々がいます。そのため、ネットワークの呼び出しが完了するまでにどのくらい時間がかかるかは予想できません。つまり、ユーザーがアプリを使い始める前にこのプロセスが完了することは保証されません。

これを踏まえて、次の 3 つの戦略が考えられます。

戦略 1: 反映して更新

これはもっとも単純な戦略です。そのため、サンプルアプリやチュートリアルでは、一般的にこの方法が使われています。この考え方では、クラウドから新しい値をダウンロードし終えたタイミングで、完了ハンドラを使って activateFetched() を呼び出し、それをすぐに反映させてから、アプリをアップデートして処理を続けます。


この戦略が優れているのは、ユーザーがすぐにアプリを使い始めることができる点です。しかし、アプリを使用している最中にボタンのテキストや UI 配置などの重要な値が変更されるのは違和感があるでしょう。ユーザー エクスペリエンスの低下につながるかもしれません。そのため、多くのデベロッパーは次のようなソリューションを選ぶ傾向にあります。

戦略 2: ロード中画面の追加

よく使われる戦略として、ユーザーがアプリを開始した際にロード中画面を表示する方法があります。先ほどと同じように、完了ハンドラで activateFetched を呼んで値を即座に適用しますが、アプリのインターフェースを更新するのではなく、ロード中画面を閉じてメイン コンテンツに遷移します。ロード中画面が終わってアプリに入ると、すべてがうまくいっているように見えます。ほとんどの場合、クラウドから最新の値がダウンロードされ、アプリがそれを使う準備も完了していることが保証されます。



しかし、この方式の明らかに大きな欠点は、ロード中画面が追加されたことです。開発にかかる時間や手間もさることながら、ユーザーがアプリに入る際の壁になります。しかし、すでにアプリにロード中画面があり、開始時に他の作業やネットワークの呼び出しを行っているのであれば、これは優れた戦略となるでしょう。その場合は、他の初期化作業と合わせて Remote Config の値を取得しましょう。

しかし、アプリにまだロード中画面がない場合は、別の戦略を試してみることをお勧めします。たとえば、次のようなものです。

戦略 3: 値を読み込み、次回に反映

これはあまり直感的ではないかもしれませんが、ぜひお読みください。ユーザーがアプリを開始した際、即座に activateFetched() を呼び出します。すると、クラウドから取得した前回の値が適用されます。ユーザーはすぐにアプリを使い始めることができます。
その間に、非同期に fetch() を呼び出してクラウドから新しい値を取得します。完了ハンドラでは、何もしません。そもそも、完了ハンドラを追加する必要すらありません。クラウドから取得した値は、ユーザーが次にアプリを起動して activateFetched を呼び出すまで、端末のローカルに保存されたままになります。



これは、ユーザーがすぐにアプリを使い始めることができる優れた戦略で、アプリのインターフェースが突然変わったりするような奇妙な動作も起こりません。明らかな欠点は、ユーザーがアップデートされた Remote Config の値を確認できるまで 2 セッションが必要になることです。この動作がアプリにふさわしいかどうかは、ご自身で判断してください。
タワー ディフェンス ゲームの設定値の微調整に Remote Config を使うような場合は、おそらく問題ないでしょう。しかし、日ごとにメッセージを送信したり、日付に固有のコンテンツを提供したりする場合(アプリのスキンを休日用に変更するなど)にはふさわしくないかもしれません。

戦略 3.5: 戦略 2 と 3、または 1 と 3 の中間

Remote Config が優れている点の 1 つは、最後に fetch() の呼び出しが成功したのはいつなのかがわかることです。そのため、Remote Config データの最終取得日時を最初に確認するという中間戦略をとることができます。それが最近(たとえば、48 時間以内など)であれば、そのまま先に進み、「前回取得した値を適用して、次回のために値を取得する」戦略をとることができます。それ以外の場合は、最初の 2 つの戦略のうちどちらかを実行します。



キャッシュについて

ここまで読み込み戦略の話をしてきたので、関連するキャッシュの話題に進みましょう。Remote Config の値は、fetch() を呼び出すたびに即座に取得されるのではなく、12 時間キャッシュされます。この点を勘違いしているデベロッパーもいるようです。また、キャッシュ時間を短くすることはできますが、ネットワークの呼び出しを頻繁に実行しすぎると、クライアントまたは Remote Config サービスによる制限がアプリに適用されることもあります。

では、そもそもなぜこのようなキャッシュや制限の仕組みが存在しているのでしょうか。

その理由の 1 つは、たとえ何百万というユーザーが使うアプリがあっても、確実にこの無償サービスを維持できるようにするためです。適切に動作するキャッシュの仕組みが追加されているので、アプリがどんなに有名になったとしても、このサービスを無償で利用できることが保証されています。

さらに、この仕組みは質の悪いコードからサービスを守る方法としても有効です。中には、意図せず fetch() を何度も呼び出すようなデベロッパーもいます(もちろん、皆さんのことではありません)。あるデベロッパーが意図せず DDoS を行ったとしても、サービス全体が影響を受けないようにする必要があります。クライアント側のライブラリでキャッシュされた値を提供したり、頻繁に行われるネットワークの呼び出しを制限したりすれば、安全にサービスを維持できます。

これは、ユーザーにとってもメリットになります。優れたキャッシュ戦略があれば、アプリから不要なネットワークの呼び出しを何度も行ってユーザーの電池やデータプランを使い果たすようなことはなくなります。そのため、優れたキャッシュ戦略は常に実装すべきものです。

もちろん、Remote Config の実装の開発やテストの際に、この動作を不便に感じることがあるでしょう。そのため、デベロッパー モードをオンにしてローカルでの制限動作を無視できるようになっています。しかし実際には、たとえユーザーが毎日アプリを使ったとしても、通常、こういったキャッシュ動作はメリットだけをもたらします。

しかし、もっと頻繁に値をプッシュしたい場合はどうすればよいでしょうか。たとえば、Remote Config で「時間ごとのメッセージ」のような機能を提供したいような場合です。率直に言えば、これは Remote Config サービスが適切なソリューションではないことの表れかもしれません。タイムリーに何かを行いたい場合は、Realtime Database の利用を検討するとよいでしょう。

一方で、頻度の少ない Remote Config アップデートを大至急適用したい場合もあるかもしれません。前回の変更で意図せずにアプリ内の秩序を乱してしまった場合や、ただちに新しい値を全員に適用したい場合などが考えられます。キャッシュを回避してクライアントに値の取得を強制させるには、どうすればよいでしょうか。

考えられるソリューションの 1 つは、Firebase Cloud Messaging を使うことです。FCM を使うと、すべての端末にデータのみの通知を送り、保留されている緊急アップデートについて知らせることができます。この通知を受信したアプリは、なんらかのフラグをローカルに保存します。次にユーザーがアプリを起動した際に、そのフラグを確認し、それが true であれば、即時性が保証されるようにキャッシュ時間 0 で Remote Config の値を取得します。完了時には、忘れずにフラグをクリアして通常のキャッシュ動作に戻すようにしてください。

受信した通知に対してクライアントを応答させ、即座にスリープを解除して Remote Config サービスから新しい値を取得させたくなるかもしれませんが、インストールされたすべてのアプリが一斉にこれを行うと、サーバー側の制限の対象になる可能性が高くなります。そのため、このような動作をさせるのではなく、前述の戦略を使用します。そうすることで、すべてのユーザーがアプリを開くまでの期間、ネットワークの呼び出しを分散させることができます。

いかがでしょうか。今回は、Remote Config から値を読み込む際のちょっとしたテクニックについて紹介しました。なにか参考になりましたか?YouTube 動画のコメントでぜひ共有してください。または、Firebase Talk グループでどのように Remote Config を実装しているかをお知らせください。


Posted by Khanh LeViet - Developer Relations Team