【インデックスエラー】failed-preconditionとFirestore複合インデックスを理解する
このページでやること
このページでは、Firestoreで出ることがある failed-precondition というエラーを理解します。
このエラーは、主にFirestoreで検索や並び替えをしたときに出ます。
failed-precondition
特に、次のようなコードで起こりやすいです。
.where(...)
.orderBy(...)
Firestoreが、
この検索をするには、専用のインデックスが必要です。
と教えてくれている状態です。
今日のゴール
failed-precondition が出ても、慌てずに確認できるようにします。
エラーが出る
↓
エラー文を見る
↓
インデックス作成リンクを開く
↓
Firebase Consoleで作成する
↓
数分待つ
↓
もう一度アプリを動かす
npmや環境変数は必要?
このページでは、npmは使いません。
環境変数も設定しません。
必要なのは、Firebase Consoleの操作だけです。
Flutterコード
↓
Firestoreの検索条件
↓
Firebase Consoleでインデックス作成
まず結論
failed-precondition が出たら、エラー文に表示されるURLを探します。
多くの場合、次のような文章が出ます。
The query requires an index.
You can create it here:
https://console.firebase.google.com/...
このURLを開くと、必要なインデックス作成画面に進めます。
初心者の方は、まずこの方法で進めればOKです。
1. failed-preconditionとは?
Step 1:意味を確認する
failed-precondition は、日本語で言うと、
実行する前に必要な条件が足りません。
という意味です。
Firestoreの場合は、多くの場合こうです。
この検索には、先にインデックスを作ってください。
つまり、コードが全部間違っているわけではありません。
Firestore側の準備が足りない状態です。
Step 2:よく出るエラー文
FlutterのターミナルやDebug Consoleに、次のようなエラーが出ます。
FirebaseException ([cloud_firestore/failed-precondition])
The query requires an index.
You can create it here: https://console.firebase.google.com/...
大事なのはここです。
The query requires an index.
意味は、
この検索にはインデックスが必要です。
です。
2. インデックスとは?
Step 3:インデックスは「目次」
インデックスは、Firestoreがデータを速く探すための「目次」です。
本で考えると分かりやすいです。
目次なし
↓
全部のページを探す
↓
遅い
目次あり
↓
必要な場所をすぐ探せる
↓
速い
Firestoreも同じです。
インデックスなし
↓
探しにくい
インデックスあり
↓
速く検索できる
Step 4:なぜインデックスが必要なのか
たとえば、タスクをこの条件で表示したいとします。
status が todo
さらに
createdAt の新しい順
コードでは、こう書くことがあります。
FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('tasks')
.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)
.snapshots();
この検索は、Firestoreから見ると少し複雑です。
statusで絞り込みたい
createdAtで並び替えたい
複数の条件を組み合わせているので、専用のインデックスが必要になることがあります。
3. 単一インデックスと複合インデックス
Step 5:単一インデックス
1つの項目だけを見る検索は、比較的シンプルです。
.where('status', isEqualTo: 'todo')
または、
.orderBy('createdAt')
このような1項目だけの検索は、Firestoreが自動で対応しやすいです。
Step 6:複合インデックス
複数の条件を組み合わせると、複合インデックスが必要になることがあります。
例です。
.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)
または、
.where('status', isEqualTo: 'todo')
.where('priority', isEqualTo: 'high')
.orderBy('createdAt', descending: true)
これは、
status
priority
createdAt
のように、複数の項目を一緒に使っています。
このようなときに、Firestoreが複合インデックスを求めることがあります。
4. このアプリで起こりやすい場所
Step 7:チーム一覧で起こる例
今のチーム一覧では、基本的にこのように取得しています。
FirebaseFirestore.instance
.collection('teams')
.where('memberIds', arrayContains: uid)
.snapshots()
これはシンプルです。
ただし、ここに orderBy を追加すると、インデックスが必要になることがあります。
FirebaseFirestore.instance
.collection('teams')
.where('memberIds', arrayContains: uid)
.orderBy('updatedAt', descending: true)
.snapshots()
この場合は、
memberIds に自分のuidが入っているチーム
さらに
updatedAt の新しい順
という検索になります。
Step 8:タスク一覧で起こる例
タスク一覧で、ステータス別に表示して、さらに新しい順に並べる場合です。
FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('tasks')
.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)
.snapshots()
これは、
statusで絞る
createdAtで並び替える
ので、複合インデックスが必要になることがあります。
Step 9:メンバー一覧で起こる例
メンバー一覧を、権限ごとに絞って、参加日時順に並べる場合です。
FirebaseFirestore.instance
.collection('teams')
.doc(teamId)
.collection('members')
.where('role', isEqualTo: 'member')
.orderBy('joinedAt', descending: true)
.snapshots()
これは、
roleで絞る
joinedAtで並び替える
ので、インデックスが必要になることがあります。
5. エラーが出たときの直し方
Step 10:エラー文のリンクを探す
failed-precondition が出たら、ターミナルやDebug Consoleを見ます。
次のようなURLを探します。
https://console.firebase.google.com/...
Firestoreは、必要なインデックス作成画面へのリンクを出してくれることが多いです。
Step 11:リンクを開く
エラー文のURLをブラウザで開きます。
すると、Firebase Consoleのインデックス作成画面が開きます。
多くの場合、必要な項目が最初から入っています。
Collection ID
Fields
Query scope
初心者の方は、基本的にそのまま作成でOKです。
Step 12:Create indexを押す
画面に表示される作成ボタンを押します。
英語表示の場合です。
Create index
日本語表示の場合です。
インデックスを作成
これで、Firestoreがインデックスを作り始めます。
Step 13:数分待つ
インデックスは、作ってすぐ使えるとは限りません。
作成中
↓
数分待つ
↓
有効
データが少なければ早いです。
データが多いと、少し時間がかかります。
Step 14:アプリをもう一度確認する
インデックスが有効になったら、アプリをもう一度動かします。
flutter run
すでに起動中なら、ホットリスタートします。
R
一覧が表示されれば成功です。
6. リンクが出ない場合
Step 15:Firebase Consoleから手動で開く
エラー文にリンクが出ない場合は、Firebase Consoleから確認します。
Firebase Console
↓
Firestore Database
↓
Indexes
日本語表示の場合です。
Firebase Console
↓
Firestore Database
↓
インデックス
Step 16:Composite indexesを確認する
複合インデックスは、英語では次の名前です。
Composite indexes
日本語表示では、
複合インデックス
のように表示されます。
ここで、必要なインデックスを追加できます。
Step 17:手動作成の例
たとえば、この検索をしたい場合です。
.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)
インデックスは、だいたい次のように作ります。
Collection ID: tasks
Field: status Ascending
Field: createdAt Descending
ただし、手動で作ると間違えやすいです。
基本は、エラー文のリンクから作成するのがおすすめです。
7. 学習中はインデックスを避けてもOK
Step 18:最初はorderByを付けない
初心者のうちは、まずアプリを動かすことを優先します。
インデックスエラーで止まる場合は、いったん orderBy を外してもOKです。
例です。
FirebaseFirestore.instance
.collection('teams')
.where('memberIds', arrayContains: uid)
.snapshots()
このように、まずは絞り込みだけにします。
Step 19:Dart側で並び替える
Firestoreで並び替えずに、取得したあとFlutter側で並び替える方法もあります。
final tasks = snapshot.data?.docs ?? [];
tasks.sort((a, b) {
final aTime = a.data()['createdAt'] as Timestamp?;
final bTime = b.data()['createdAt'] as Timestamp?;
if (aTime == null || bTime == null) {
return 0;
}
return bTime.compareTo(aTime);
});
これは学習中には便利です。
ただし、データが多くなったら、Firestore側で並び替える方がよいです。
Step 20:検索条件を増やしすぎない
最初から検索条件を増やしすぎると、エラーの原因が分かりにくくなります。
最初は、次のようなシンプルな形から始めます。
.collection('tasks').snapshots()
または、
.where('memberIds', arrayContains: uid)
動いたあとで、orderBy や where を追加します。
8. このアプリで作る可能性があるインデックス
チーム一覧用
チーム一覧を新しい順にする場合です。
.where('memberIds', arrayContains: uid)
.orderBy('updatedAt', descending: true)
必要になる可能性があるインデックスです。
Collection ID: teams
memberIds Array contains
updatedAt Descending
タスク一覧用
ステータス別にタスクを表示する場合です。
.where('status', isEqualTo: 'todo')
.orderBy('createdAt', descending: true)
必要になる可能性があるインデックスです。
Collection ID: tasks
status Ascending
createdAt Descending
優先度別タスク一覧用
優先度が高いタスクを新しい順に表示する場合です。
.where('priority', isEqualTo: 'high')
.orderBy('createdAt', descending: true)
必要になる可能性があるインデックスです。
Collection ID: tasks
priority Ascending
createdAt Descending
メンバー一覧用
メンバー権限ごとに表示する場合です。
.where('role', isEqualTo: 'member')
.orderBy('joinedAt', descending: true)
必要になる可能性があるインデックスです。
Collection ID: members
role Ascending
joinedAt Descending
9. permission-deniedとの違い
failed-precondition と permission-denied は、意味が違います。
| エラー | 意味 |
|---|---|
permission-denied | 権限がない |
failed-precondition | 必要な準備が足りない |
The query requires an index | インデックスが必要 |
簡単に言うと、こうです。
permission-denied
↓
あなたには許可されていません
failed-precondition
↓
Firestore側の準備が足りません
見る場所も違います。
| エラー | 見る場所 |
|---|---|
permission-denied | Security Rules / role / uid |
failed-precondition | Indexes / query / orderBy |
10. よくあるエラーと直し方
| エラー | 原因 | 直し方 |
|---|---|---|
failed-precondition | インデックス不足 | エラー文のリンクから作成 |
The query requires an index | 複合インデックスが必要 | Firebase Consoleで作成 |
| 作成したのにまだエラー | まだ作成中 | 数分待つ |
| どのインデックスか分からない | 条件が複雑 | エラー文のURLを使う |
| URLが見えない | Debug Consoleが短い | ログ画面を広げる |
| 作っても別のエラーが出る | 別queryのインデックスが必要 | 新しいエラー文を見る |
| 手動作成で迷う | collectionやfieldが違う | URLから作る |
初心者向け:まず見る場所3つ
failed-precondition が出たら、まずここだけ確認します。
1. エラー文にURLがあるか
2. where と orderBy を一緒に使っているか
3. Firebase ConsoleのIndexesで作成中になっていないか
最短作業まとめ
読むのが大変な人は、ここだけ見てください。
1. エラー文を見る
failed-precondition
The query requires an index.
2. URLを開く
https://console.firebase.google.com/...
3. インデックスを作成する
Create index / インデックスを作成
4. 数分待つ
作成中 → 有効
5. アプリをもう一度動かす
flutter run
チェックリスト
□ failed-preconditionの意味を理解した
□ インデックスはFirestoreの目次だと理解した
□ where + orderByで起こりやすいと理解した
□ エラー文のURLを探した
□ Firebase ConsoleでIndexesを確認した
□ 必要ならインデックスを作成した
□ 作成中は数分待つと理解した
□ permission-deniedとの違いを理解した
□ 学習中はorderByを外す方法もあると理解した
ミニ確認問題
Q1. failed-precondition はどんなときに出やすいですか?
回答
Firestoreの検索に必要なインデックスがないときに出やすいです。
特に、where と orderBy を組み合わせたときに出ることがあります。
Q2. インデックスとは何ですか?
回答
Firestoreがデータを速く探すための目次です。
Q3. エラー文にFirebase ConsoleのURLがある場合、どうしますか?
回答
URLを開いて、インデックスを作成します。
Q4. インデックスを作ったら、すぐ使えますか?
回答
すぐ使えないことがあります。
作成完了まで、数分待つことがあります。
Q5. permission-denied と failed-precondition の違いは何ですか?
回答
permission-denied は権限エラーです。
failed-precondition は、インデックスなど必要な準備が足りないエラーです。
このページのまとめ
failed-preconditionは、Firestoreで必要な準備が足りないときに出る。- 多くの場合、複合インデックス不足が原因。
whereとorderByを組み合わせると起こりやすい。- エラー文にあるFirebase ConsoleのURLから作成するのが簡単。
- インデックス作成後は、反映まで少し待つ。
- 学習中は、まず
orderByを外して進めてもOK。 permission-deniedは権限エラー。failed-preconditionはインデックス系のエラー。- このページではnpmや環境変数は不要。
次のページでやること
次のページでは、users/{uid} のSecurity Rulesを作ります。
本人だけが自分のプロフィールを読み書きできるようにします。
