[iOS]FirestoreのQueueはFirebaseApp単位で管理してるって知ってた?


Firestoreを使っていると、バックエンドを薄くするためにクライアントサイドで集計処理をしたり、データの更新をしたりすることがあると思います。
弊社も例に漏れず、アプリの起動時にいくつかのクエリを並行して走らせています。
それで特に大きなパフォーマンスの問題もなく動作していたのですが、先日クライアントサイドジョインをガッツリする大きめのクエリを追加したときに事件は起きました。

なんかクエリめちゃめちゃ詰まってるんだけど

大きいクエリが遅いのは当然なのですが、なんと他のクエリも完全に詰まってホーム画面表示に十数秒かかるような現象が起こりました。
これはまずい、なんとかせねば。

原因を特定するぞい

  1. Firestore以外からのResponseは即座に返ってきていたので、どうやらFirestoreの問題のようです。

  2. 弊社のアプリはAndroid版もリリースしているのですが、そちらでは同様のクエリを走らせても特に大きなパフォーマンスの低下は見られませんでした。iOS(もしくはObjective-C)特有の問題のようです。

上記2点から、「firebase-ios-sdkでのDispatchQueueの扱いとかそのへんっぽいなー」という雑な仮説が導かれました。
試しに大きいクエリだけデフォルトのFirestoreインスタンスではなく、独自で名前を付けて初期化したインスタンスで実行してみました。

//デフォルトのインスタンス
Firestore.firestore()

//独自インスタンス 今回はこっちを使用
lazy var myFirebaseApp: FirebaseApp = {
    if let app = FirebaseApp.app(name: "Hoge") {
        return app
    }
    return FirebaseApp.configure(name: "Hoge", options: FirebaseOptions.defaultOptions()!)
}()

lazy var myFirestore: Firestore = {
    return Firestore.firestore(app: myFirebaseApp)
}()

一発大正解で、クエリの詰まりが劇的に改善されました。

「動くからOK!」 → 駄目です

やっぱりちゃんと確証を得たいですよね。ドキュメントに目を通したけどそれらしい記述が見当たらないのでソースを読むことに。

いましたいました。
https://github.com/firebase/firebase-ios-sdk/blob/c5b5a44603ad1452a9caef1b26e58ed7fb050d2b/Firestore/Source/API/FSTFirestoreComponent.mm#L97

std::string queue_name{"com.google.firebase.firestore"};
if (!self.app.isDefaultApp) {
    absl::StrAppend(&queue_name, ".", util::MakeString(self.app.name));
}

auto executor = absl::make_unique<ExecutorLibdispatch>(
    dispatch_queue_create(queue_name.c_str(), DISPATCH_QUEUE_SERIAL));
auto workerQueue = absl::make_unique<AsyncQueue>(std::move(executor));

渡したのがデフォルトインスタンスでなければ渡された名前を元にqueue名をつけてdispatch_queue_createしてますね。
やはりQueueの管理はFirebaseApp単位になるようです。

まとめ

バックグラウンドで重いクエリを走らせるときは独自インスタンスを利用すると良いかもしれません。
注意点としては、
・Authの管理もFirebaseApp単位なので別途SignInが必要
・あんまりポコポコ増やすと管理・デバッグがしんどくなるかも?
あたりかなと思います。

弊社ではデフォルトインスタンスとバックグラウンド用の独自インスタンスの2つ、最小限に絞って利用していこうと思います。