Open20

Flutter で Firestore を使っていると unavailable になる問題

@iktakahiro / Takahiro Ikeuchi@iktakahiro / Takahiro Ikeuchi

Flutter のパッケージ cloud_firestore を使っていると、不意に下記のエラーが出る。

The service is currently unavailable. This is a most likely a transient condition and may be corrected by retrying with a backoff.
  • firebase_core: 0.5.1
  • cloud_firestore: 0.14.2

これまでに(だいたい)分かっていること

  • GitHub 上で複数同様の問題が観測されている
  • iOS 側では問題が起きていないので、おそらく Android SDK に起因する問題(または cloud_firestore とネイティブSDKのインテグレーション)
  • エラーメッセージには Exponential Backoff アルゴリズムを使ってリトライせよ、という示唆があるが、しても改善しない
  • Firebase Authentication のトークンが expire されるタイミングでおきている疑いもあるが、強制リフレッシュをかけても解決しない
  • 1ヶ月以上にわたり発生しているので、Firestore の障害ではなさそう
  • 起きたり起きなかったりする
@iktakahiro / Takahiro Ikeuchi@iktakahiro / Takahiro Ikeuchi
  • cloud_firestore: 0.14.4
  • firebase_core: 0.5.3

で確認中。ただし変更差分を見ると、直接的に問題解決に寄与するコミットはなさそう

飯塚浩也飯塚浩也

お疲れ様です!!
弊社のアプリ(react native)でも同様のcrash がおき、一点こう対応しましたという、報告になります。

const commonConfigDoc = () =>
  firestore()
    .collection(`config`)
    .doc(`common`)

firestore との通信で、get() するときに、error handling をしていないことが原因でした・・・
このメソッドを使うときに、今までは

   const { latestAppVersion } = await FirebaseService.getCommonConfig()

と書いて lastApVersion を 使い回していたのですが、

 const { latestAppVersion } = await FirebaseService.getCommonConfig().catch(e => ...)

if(!latestAppVersion) {
  ...
}

というコードに変更しました!
しばらく、crash の動向を追って参ります!

飯塚浩也飯塚浩也

ごめんなさい、返信が遅れました!!!

弊社もこのエラーがかなり発生率が下がってます!(先日のエラーハンドリングし忘れもあるかもですが)

引き続きwatch していきます!!

@iktakahiro / Takahiro Ikeuchi@iktakahiro / Takahiro Ikeuchi

コメントありがとうございます!

現在運用中のコードを簡略化したものですが、リトライ処理を行ったうえでもなお例外が発生しているので、リトライ処理では復帰できない何かが起きている、というところまでは分かっています。

Future<DocumentSnapshot> getDocument({
   String path,
   String id,
}) async {
  try {
    final ref = await withRetrying<DocumentSnapshot>(
      () {
        return FirebaseFirestore.instance.collection(path).doc(id).get();
      },
    );
    return ref;
  } catch (e) {
    // リトライ処理でも上手くいかなかった場合、ここに到達
    rethrow;
  }
}

withRetrying の中身はざっくりこんな感じです。

import 'package:retry/retry.dart';

const firestoreRetryOptions = const RetryOptions(maxAttempts: 6);

Future<T> withRetrying<T>(Future<T> Function() fn) async {
  final ref = await firestoreRetryOptions.retry(() async {
    final _ref = await fn();
    return _ref;
  }, retryIf: (e) async {
    if (e is FirebaseException &&
        e.code == firestoreErrorCodePermissionDenined) {
      return false;
    }
    return true;
  }
  return ref;
}
@iktakahiro / Takahiro Ikeuchi@iktakahiro / Takahiro Ikeuchi

さすがに公式サポート事案では、と思ったのでお問い合わせ。現在も発生中。

公式からの回答を意訳すると、Flutter の Firebase SDK は公式サポートではないのでコミュニティサポートを受けて下さい、とのことでした。

yukiyuki

これ結局何が問題なんでしょう?Firestoreのサーバー側かクライアント側なのか...
エミュレーターでは通るんですけどね。

yukiyuki

ありがとうございます!それ設定してもすぐには解決しなかったんですけど、時間を空けたら治っていました。
でもこの問題が発生しているときはPlay StoreでのアプリのアップデートやYouTube MusicなどGoogle製アプリの送受信がとても遅くなっているような気がします。
他のサードパーティアプリはいつも通りです。

monomono

リージョンはどちらにしてますか?
僕は普段基本的にデフォルトのMulti-regionの nam5 (us-central)にしてて、この問題が気になったことなかったです。

Cloud Firestore のロケーション  |  Firebase Documentation

ただ、Regionalのasia-northeast1のプロジェクトでこの問題が起こって、リージョンの影響もありそうと推測しました。
Multi-regionがRegionalよりも可用性高いのは妥当ですし。

僕は普段基本的にデフォルトのMulti-regionの nam5 (us-central)にしてて

ちなみに、これは以下の理由です:

  • これでも大抵の用途で遅くない
  • Multi-regionの方が可用性・耐久性・SLAなど優れている
  • 課金額はRegionalより1.5-2倍くらい高くなるが許容範囲
  • 国外対応なども考慮するとasia-northeast1にするのも微妙
@iktakahiro / Takahiro Ikeuchi@iktakahiro / Takahiro Ikeuchi

コメントありがとうございます! 僕の環境の リージョンは asia-northeast1 ですね。

そして マルチリージョンの採用なるほどです...!!! 確かに asia-northeast1 にするより us-central のほうが良いと思いました。

monomono

僕の環境の リージョンは asia-northeast1 ですね。

ありがとうございます。
なるほど、マルチリージョン選ぶことで回避できる(あるいは発生頻度を大きく下げられる)可能性あるかもしれないですね🧐