ざっくりGCP料金計算

Firestoreのインデックス不足で本番クエリが死んだ話

開発環境では問題なかったのに、本番のデータ量でインデックスなしクエリが崩壊した。

リリース直後から「検索できない」バグ報告

Cloud Firestoreを使ったプロダクトのリリース直後から、一部のユーザーで検索機能が使えないという報告が届いた。エラーログを確認すると「The query requires an index」というFirestoreのエラーが出力されていた。しかし開発環境では全く同じクエリが問題なく動いている。なぜ本番でだけ?

Firestoreには「複合インデックス」という概念がある。複数のフィールドを組み合わせて絞り込みや並び替えをするクエリには、その組み合わせに対応した複合インデックスが必要になる。例えば「statusフィールドがactiveで、createdAtフィールドの降順で並び替える」というクエリには、statusとcreatedAtの複合インデックスが必要だ。

なぜ開発では動いていたか

開発環境ではFirestoreエミュレータを使っており、エミュレータはインデックスの有無に関わらずほとんどのクエリを実行できる。また、実際のFirestoreに接続する開発プロジェクトにはテストデータが数十件しか入っていなかった。ドキュメント数が少ない場合、Firestoreは複合インデックスなしでもクエリを実行できることがある(フルスキャンで対応できる量のとき)。本番にデプロイして数万件のドキュメントが入った状態で初めてインデックスが必要なクエリとして顕在化した。エラーメッセージのURLからFirestoreコンソールに飛ぶとインデックスをワンクリックで作成できるリンクが表示されていた。インデックスの作成自体は簡単だったが、数万件のドキュメントに対してインデックスを構築するには10〜20分かかった。しかも同様の問題が別のクエリでも発生し、インデックスを1つ追加するたびに再度エラーが出る、というモグラ叩き状態になった。

インデックスのデプロイをCI/CDに組み込む

根本的な問題はインデックスの管理がアドホックだったことだ。Firestoreのインデックスはfirestore.indexes.jsonというファイルで定義でき、Firebase CLIでデプロイできる。このインデックス定義ファイルをアプリのコードと同じリポジトリで管理し、CI/CDパイプラインにFirestoreインデックスのデプロイを組み込むことで、アプリの本番リリースと同じタイミングでインデックスもデプロイされるようにした。また本番リリース前のチェックリストに「アプリで使っている全クエリのインデックスがfirestore.indexes.jsonに定義されているか確認する」を追加した。新しいクエリを実装したときに同時にインデックスも定義する習慣をつけることで、この種の問題は再発しなくなった。開発環境とエミュレータでは再現しない問題が本番で出る、という典型的なパターンを体験した出来事だった。