Firebase Remote Configの挙動について

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

※ QiitaのほうがSEO強そうなのでブログに書いた内容の転載ですが同じものを投稿しておきます
-> https://iwsksky.hatenablog.com/entry/2020/07/04/114815

## 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

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

`frc_1/xxxxxxxxxxx/android/xxxxxxxxxxx_firebase_activate.json`

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

`ex.kt`

```kotlin
remoteConfig.getBoolean("any_key_name")
```

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

`frc_1/xxxxxxxxxxx/android/xxxxxxxxxxx_firebase_settings.xml`

```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`

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

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

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

https://github.com/firebase/firebase-android-sdk/blob/bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04/firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java#L291

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

https://github.com/firebase/firebase-android-sdk/blob/bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigCacheClient.java#L140


####  interval

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

> アプリが短期間に何度もフェッチすると、フェッチ呼び出しが抑制され、SDK から FirebaseRemoteConfigFetchThrottledException が返されます。バージョン 17.0.0 よりも前の SDK では、フェッチ リクエストは 60 分間に 5 回までと制限されていました（新しいバージョンでは制限が緩和されています）。
https://firebase.google.com/docs/remote-config/use-config-android?hl=ja


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

```java
public static final long DEFAULT_MINIMUM_FETCH_INTERVAL_IN_SECONDS = HOURS.toSeconds(12);
```
https://github.com/firebase/firebase-android-sdk/blob/master/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java#L64

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

```java
if (cachedFetchConfigsTask.isSuccessful()
        && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) {
      // Keep the cached fetch values if the cache has not expired yet.
      return Tasks.forResult(FetchResponse.forLocalStorageUsed(currentTime));
    }
```
https://github.com/firebase/firebase-android-sdk/blob/master/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java#L171

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

```java
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 {
```
https://github.com/firebase/firebase-android-sdk/blob/bc666db8e0d6e2c9c5d1f5422eac3c96a167dc04/firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java#L187


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

## refs
[1] [https://firebase.google.com/docs/remote-config?hl=ja:title]