sky’s 雑記

主にAndroidとサーバーサイドの技術について記事を書きます

Firebase Remote Configの挙動について

Remote Configは便利なんですが雰囲気で使うと思ったように動かなかったりするのでちゃんとまとめようと思います.

TL;DL

  • fetchAndActivateをするとremoteの値がローカルに保存され参照可能となる
  • ローカルキャッシュの期限は最短でも12分でそれ以上の頻度では更新できない
  • 参照は通信結果ではなく常にローカルキャッシュの値

概要

https://firebase.google.com/docs/remote-config/images/param-precedence.png?hl=ja

図1. RemoteConfig概要図[1]

抑えておきたい概念は以下の通り

  • cache
  • fetch/activate
  • interval

1個ずつ見ていきます.

cache

Remote Configは常にremoteから値を取得するわけではなく,後述するfetchIntervalにlimitがありそれ以上の頻度ではremoteの値を取得できない仕組みになっています.なので基本的にはローカルのキャッシュを参照してたまにremoteの値を取得すると思ってもらって良いです.Device File Explorerでローカルフォルダを見に行くと以下のようにjsonが保存されていて参照するときはこの値を見ています.(any_key_nameとなってるところがRemote Configで設定したフィールド名で今回はboolを設定していると思ってください)

frc_1/xxxxxxxxxxx/android/xxxxxxxxxxx_firebase_activate.json

{"configs_key":{"any_key_name":"true"},"fetch_time_key":1593480378652,"abt_experiments_key":[]}

ex.kt

remoteConfig.getBoolean("any_key_name")

fetchの設定やログもxmlで保持しています.

frc_1/xxxxxxxxxxx/android/xxxxxxxxxxx_firebase_settings.xml

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <long name="fetch_timeout_in_seconds" value="5" />
    <int name="last_fetch_status" value="-1" />
    <boolean name="is_developer_mode_enabled" value="false" />
    <long name="minimum_fetch_interval_in_seconds" value="1800" />
    <long name="backoff_end_time_in_millis" value="-1" />
    <long name="last_fetch_time_in_millis" value="1593480378652" />
    <int name="num_failed_fetches" value="0" />
</map>

fetch/activate

cacheの項目でアプリはactivateされたcacheを参照すると記述しましたがfetchを行っただけではjsonファイルは作成されません.fetchをした段階では以下のようなファイルがローカルのdataディレクトリに作成されます,このjsonはremoteの情報を反映したものです. frc_1/xxxxxxxxxxx/android/xxxxxxxxxxx_firebase_fetch.json

{"configs_key":{"any_key_name":"false"},"fetch_time_key":1593829520560,"abt_experiments_key":[]}

以下のようにactivateすることで初めてfirebase_activate.jsonが作成されます.

return activatedConfigsCache
                  .put(fetchedContainer)
                  .continueWith(executor, this::processActivatePutTask);

firebase-android-sdk/FirebaseRemoteConfig.java at bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04 · firebase/firebase-android-sdk · GitHub

    return Tasks.call(executorService, () -> storageClient.write(configContainer))

firebase-android-sdk/ConfigCacheClient.java at bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04 · firebase/firebase-android-sdk · GitHub

interval

上述のとおりintervalはremoteからフェッチする間隔であり公式によると最短で1時間5回(=12分)のようです.

アプリが短期間に何度もフェッチすると、フェッチ呼び出しが抑制され、SDK から FirebaseRemoteConfigFetchThrottledException が返されます。バージョン 17.0.0 よりも前の SDK では、フェッチ リクエストは 60 分間に 5 回までと制限されていました(新しいバージョンでは制限が緩和されています)。 Android で Firebase Remote Config を使ってみる

デフォルトではインターバルは12時間に設定されます.

public static final long DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS = HOURS.toSeconds(12);

firebase-android-sdk/ConfigFetchHandler.java at master · firebase/firebase-android-sdk · GitHub

キャッシュの有効期限(=インターバル)が切れていなければローカルの値を読みます.

if (cachedFetchConfigsTask.isSuccessful()
        && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) {
      // Keep the cached fetch values if the cache has not expired yet.
      return Tasks.forResult(FetchResponse.forLocalStorageUsed(currentTime));
    }

firebase-android-sdk/ConfigFetchHandler.java at master · firebase/firebase-android-sdk · GitHub

また,なんらかの理由でremoteのfetchに失敗した場合はエクスポネンシャルバックオフでスロットリングがかかるのでその場合もfetchは行われないようです(exceptionが吐かれる)

Date backoffEndTime = getBackoffEndTimeInMillis(currentTime);
    if (backoffEndTime != null) {
      // TODO(issues/260): Provide a way for users to check for throttled status so exceptions
      // aren't the only way for users to determine if they're throttled.
      fetchResponseTask =
          Tasks.forException(
              new FirebaseRemoteConfigFetchThrottledException(
                  createThrottledMessage(backoffEndTime.getTime() - currentTime.getTime()),
                  backoffEndTime.getTime()));
    } else {

firebase-android-sdk/ConfigFetchHandler.java at bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04 · firebase/firebase-android-sdk · GitHub

まとめ

仕組みがわかれば特に難しいことはないんですが雰囲気で使うとremoteの値が取得できないとか思った結果と違うものが取得できたみたいなことになりやすいです.最短でも12分間隔でしかfetchできないというのがミソなのでリアルタイム性が求められるような情報はRemote Configとはあまり相性が良くないと言えるかもしれません.

refs

[1] Firebase Remote Config