はじめに
Firestoreのすばらしさと同時に難しさを感じるのがインデックスの部分です。
作成に時間がかかるし、何を作ればいいかよくわからなくなる複合インデックスが理解が進むとFirestoreがより扱えるようになると思ったのでまとめてみます。
大前提
Firestoreはある程度複雑な検索条件も扱えるようになっていますが、すべての検索がインデックスを必要とします。
ん?と思われた方もいらっしゃると思いますが、確かに単純に検索条件を使うだけだとインデックスの作成を必要としません。
実は、複合インデックスはそれぞれのユースケースによって変わってくるので自分で作る必要がありますが、単一フィールド インデックスはデフォルトでは自動で付加されるようになっています。
そのおかげで気にせず我々はクエリーを作れるようになっています。
ただ、単一フィールド インデックスも状況によってはマイナス面も発揮するので、設定で除外することができるようになっています。
そしてその除外した方がいいケースは以下の通りです。
通常の使い方
複合インデックスは、orderBy
もしくは不等号のwhere
と他のwhere
を組み合わせたとき必要になります。
例えば以下のようなケースです。
// 不等号を使ったケース
query(
collection(getFirestore(), 'messages'),
where('isPublic', '==', true),
where('createdAt', '>', new Date(2023, 4, 19))
)
// orderByを使ったケース
query(
collection(getFirestore(), 'messages'),
where('isPublic', '==', true),
orderBy('createdAt')
)
この場合はそれぞれ同じ複合インデックスを必要とします。
以下がその複合インデックスになります。
フィールド | オーダー |
---|---|
isPublic | 昇順 |
createdAt | 昇順 |
これを毎回頭で考えながら作っていくのはなかなか脳に負荷がかかる作業ですが、Firestoreはその負荷軽減のために例外を出して、インデックスの作成を依頼するURLを表示してくれます。
基本的にはこの例外を使って作っていくのが間違いがないので安心です。
実運用で使っている環境で例外を出すのは問題なので、例外を出していい環境で作成してから以下のコマンドでファイルに書き出して、デプロイコマンドで適応するのがいいと思います。
# インデックス情報を書き出し
firebase firestore:indexes > firestore.indexes.json
# インデックスを対象の環境へデプロイ
firebase deploy --only firestore:indexes -P 自分の環境
これでほとんどのケースはいいと思っています。
というか私も長い間、この方法しかやってきていませんでした。
さきほどのケースだと勿体無いケース
自動で必要なインデックスの作成を指示してくれるのはうれしいのですが、その方法だと勿体無いケースが存在します。
例えば、通常はisPublic
だけで検索するが、userId
でも絞り込みをしたいときもあるケースなどがこれに当たります。
'isPublic`だけで検索するときは、さきほど記載した通りです。
その上で以下のクエリーを実行してみます。
query(
collection(getFirestore(), 'messages'),
where('isPublic', '==', true),
where('userId', '==', 'user1'),
orderBy('createdAt')
)
すると、さきほどの作成した複合インデックスだとuserId
分キーが少ないのでURLでは以下の複合キーの作成を求められます。
フィールド | オーダー |
---|---|
isPublic | 昇順 |
userId | 昇順 |
createdAt | 昇順 |
想定通りの結果だと思います。
実は、これ以外にも今回のクエリーを実行できる複合インデックスがあります。
それが以下の複合インデックスになります。
フィールド | オーダー |
---|---|
userId | 昇順 |
createdAt | 昇順 |
自分でGUI(indexファイルに書けばそれでもいけます)で設定する必要がありますが、これでもOKです!
さらにもちろん以下のクエリーでもインデックスを追加する必要がなくなるのでちょっとお得な感じです。
query(
collection(getFirestore(), 'messages'),
where('userId', '==', 'user1'),
orderBy('createdAt')
)
この動きを考えるとorderBy
や不等号があり、2個以上の追加条件があるクエリーに関しては分割して行った方が得な気がします!
といっても、ここで書いたユースケースのようにクエリーが状況によって変わるときに有効であって、全然変わらないのに複数の複合インデックスに分けるのは流石に無駄ですし、自分でインデックスを考えるのは間違えるリスクもあるので、やめた方がいいでしょう。
公式ページにも載っている方法です。
結果として追加するインデックスの数が少なくなるようにするのがベストプラクティスだと思います。
終わりに
管理するインデックス数が減るのはインデックスの制限を超えないようにする効果もありますし、追加変更のパフォーマンスも向上するので、少し複雑なクエリーを実装するときは、立ち止まって自分で複合インデックスを追加することを考えてみるといいと思います。
あと余談ですが、RDBを使ったことある人だと違和感ある仕様ですよね。
RDBだと複合インデックスって大が小を兼ねるという感じで、A,B,Cの並びの複合インデックスがあれば、Aのインデックスを使った検索、A,Bを使った検索まで早くなるので大きめに作るということも考えるのですが、Firestoreの場合は分割していく方が汎用性あるんですよね。B-treeの概要は把握しているのでRDBの挙動は把握できるのですが、Firestoreの場合どうなっているんでしょ?理解できたらまた追記しようと思います。
こんな感じの始めた当初は考えもしなかったけど、知ってると便利なFirebaseのトピックをまとめて本にしたので、よかったら技術書典でみてみてください。