😬

[iOS]Firebase Realtime RemoteConfigの挙動を調べてみた

2024/01/24に公開

概要

Firebase RemoteConfigを使用しているアプリに新たにFirebase Realtime RemoteConfigを導入したときに、調べたことを共有する。

  • Realtime RemoteConfigとは
  • どうやって導入するか
  • 使用するにあたって、エッジケースとなりそうな状況での挙動

環境

  • Firebase SDK for iOS v10.14の実装に基づいています。(github)

従来のRemoteConfigの制限

従来のRemoteConfigでは、コンソールで値を更新した後に、更新後の値をクライアント側で使用するためには、クライアント側でRemoteConfig.fetch()を行う必要があった。
RemoteConfig SDKでは、キャッシュ機能が実装されている。一度RemoteConfig.fetch()を行ったあとは、一定時間(デフォルトでは12時間)の間、再度RemoteConfig.fetch()を呼び出してもキャッシュを参照し、実際にはAPI呼び出しを行わない。つまり、キャッシュが有効な間は、コンソールで値を変更したとした後にRemoteConfig.fetch()を行ったとしても、最新の値をfetchできない。

キャッシュの有効期間は変更でき、短い値を設定することもできる。(e.g. 0秒)
常に最新の値を利用したいという場合は、キャッシュ期間を短くし、頻繁にRemoteConfig.fetch()を呼び出すことで、それを実現できる。
しかし、頻繁にRemoteConfig.fetch()を行うと、SDKによってThrottle状態にされてしまう可能性がある。

バージョン 6.3.0 よりも前の SDK では、フェッチ リクエストは 60 分間に 5 回までと制限されていました(新しいバージョンでは制限が緩和されています)。[1]

そのため、従来のRemoteConfigでは、リアルタイムな値の更新には、制限があった。

Firebase Realtime RemoteConfig とは

従来のRemoteConfigでは難しかった、リアルタイムな値の更新を可能にするAPI。
FirebaseSDK v10.7.0以上で利用できる。
Realtime RemoteConfigでは、RemoteConfigバックエンドとクライアントの間でHTTP接続を確立する。
接続を開始した時に、現在クライアントにキャッシュされているConfigとサーバ側のConfigのバージョンを比較し、バージョンに差異がある場合は、SDKが自動的にRemoteConfig.fetch()を行う。
新しいバージョンがない場合、接続を開いた状態で維持し、サーバに新しいバージョンが公開されるまで待機する。
また、サーバで新しいバージョンが公開されると、SDKが自動的にRemoteConfig.fetch()を行う。クライアント側では、fetch()の後に実行するcallbackを設定し、その中でfetchした値をactivateすればよい。

最新の値を取得する処理はSDKが管理してくれて、Throttle状態になるリスクもないので、とてもありがたい。

導入方法

Listnerの登録

  • Listnerと、変更を受け取ったときのcallbackを登録する
func exampleAddListner() {
    remoteConfig.addOnConfigUpdateListener { configUpdate, error in
      // 変更を受け取ったときに以下が実行される
      guard let configUpdate, error == nil else {
        print("Error listening for config updates: \(error)")
      }

      // どのkeyが更新されたかを確認できる
      print("Updated keys: \(configUpdate.updatedKeys)")

      // 値をローカルストレージに保存
      self.remoteConfig.activate { changed, error in
        //
      }
    }
}

値の利用

従来のRemoteConfigと同様に値を読み出すことができる。

let val = remoteConfig.configValue(forKey: "key")

各シチュエーションでの挙動

アプリが起動したとき

RemoteConfigバックエンドとの接続を確立したときに、ローカルとサーバでconfigのバージョンの比較を行い、必要があれば、RemoteConfig.fetch()が自動的に行われる。
アプリが起動する前にコンソールで更新した値は、アプリの起動後、Listnerを登録し、接続が確立したタイミングでfetchされる。

アプリがバックグラウンド状態になったとき

アプリがバックグラウンドに入ると更新の接続を自動的に停止し、アプリがフォアグラウンドになると再開される。
アプリがバックグラウンド状態のときに、コンソールで更新した値は、アプリが再び接続を確立したときにfetchされる。(e.g. アプリがフォアグラウンド状態になったとき)
接続の中断や再開はSDKが管理してくれるので、特別な処理を実装する必要はない。

更新の伝搬の遅延

試した限りでは、コンソールで値を変更すると、すぐに(1秒以内程度)でクライアント側でシグナル(更新後の値)を受け取ることができた。

接続に失敗したとき

RemoteConfigバックエンドとの接続を確立ときに、Error Responseやネットワークが利用できなかった場合(e.g. 機内モード)も、SDKが対処してくれる。

接続に失敗した場合は、一定の時間が経ったあとに、SDKが自動的に再接続の処理を行ってくれる。(デフォルトで最大7回)
しかし、失敗を繰り返すたびに、再接続処理を行うまでの時間が指数関数的に増加する。(1回目は2分、2回目は4分、、最大で4時間)
再接続処理を行える残りの回数と、次に再接続処理を行う時間は、ローカルストレージに保存されているため、アプリを再起動しても、前回の状態が引き継がれる。
一度接続に成功すると、再接続できる回数と次に再接続処理を行う時間はリセットされる。

アプリの起動時に従来のRemoteConfig.fetch()を呼び出す必要があるのか?

Realtime RemoteConfigを利用すれば、手動でのRemoteConfig.fetch()は不要になったように思える。
しかし、ドキュメントには以下の記述がある。

アプリの起動時(またはアプリのライフサイクル中)にフェッチを呼び出し、ユーザー セッション中にリアルタイム Remote Config の更新をリッスンして、サーバーに公開後すぐに最新の値を使用できるようにすることをおすすめします。[2]

起動時にRemoteConfig.fetch()を呼び出したほうがよい理由は以下のものだと予想する。

前節の"接続に失敗したとき"で述べたように、再接続処理を行う回数と次の処理までの時間は、アプリを再起動しても保存される。
前回の接続に失敗して再接続待ちの状態でアプリを再起動し、再びRemoteConcigバックエンドとの接続を確立しようとした場合を考える。 次の再接続処理までの待ち時間によっては、接続を確立できるまでに、時間がかかってしまう可能性がある。
そのため、起動してすぐのタイミングで最新の値を確実に取得するために、手動でRemoteConfig.fetch()を呼び出す必要があると考えられる。

Ref

脚注
  1. https://firebase.google.com/docs/remote-config/get-started?hl=ja&platform=ios ↩︎

  2. https://firebase.google.com/docs/remote-config/real-time?hl=ja&platform=ios ↩︎

Discussion