Firestore で開発していますか?これらの15のルールは、最も一般的な落とし穴を回避するのに役立ちます。最初の Firebase プロジェクトを始める場合でも、既存のプロジェクトをスケールする場合でも、これらのベストプラクティスはデータモデリングからコスト最適化まですべてをカバーしています — 実際の本番アプリから学んだものです。
データモデリングのルール
データをどのように構造化するかが、クエリのパフォーマンス、コスト、保守性のすべてを決定します。まずこれを正しく行いましょう。
1. 深いネストよりもフラットなコレクションを優先する
深くネストされたサブコレクションはクエリを難しくし、密結合を生みます。コレクション階層を浅く保ちましょう — 通常は1〜2レベルの深さです。すべてを親の下にネストするのではなく、ドキュメント参照を持つルートレベルのコレクションを使用してください。例えば、ユーザーごとの注文のみをクエリする場合を除き、users/{id}/orders の下にネストするのではなく、userId フィールドを持つルートコレクションとして orders を保存してください。
2. 読み取りパフォーマンスのためにデータを非正規化する
SQL では重複を避けるために正規化します。Firestore では、複数の読み取りを避けるために非正規化します。UI がすべてのコメントの横にユーザーの名前を表示する場合は、各コメントドキュメントに authorName を直接保存してください — users コレクションへの別の読み取りを強制しないでください。トレードオフを受け入れましょう:書き込みはより複雑になりますが、読み取りは高速で安価です。
3. 無制限の一対多の関係にはサブコレクションを使用する
ドキュメントが数百または数千の関連アイテム(チャット内のメッセージ、ユーザーの注文)を持つ可能性がある場合は、サブコレクションを使用してください。ドキュメント内の配列とは異なり、サブコレクションは無制限のアイテムを保持でき、ページネーションを使用した効率的なクエリをサポートします。例:chats/{chatId}/messages/{messageId}。
4. 最適なパフォーマンスのためにドキュメントを20KB未満に保つ
Firestore のハード制限はドキュメントあたり1MBですが、20KB未満を目指してください。大きなドキュメントは、少数のフィールドのみが必要な場合に帯域幅を無駄にします — そして Firestore はサイズに関係なくドキュメント読み取りごとに課金します。ドキュメントが大きくなる場合は、サブコレクションまたは別のコレクションに分割してください。
5. クエリ可能なデータには配列を避ける
Firestore の配列には制限があります:個々の要素を更新できず、array-contains クエリはクエリごとに1つしかサポートせず、配列は数百アイテムを超えてうまくスケールしません。タグやカテゴリには array-contains を使用しますが、関係や増大するリストには、代わりにサブコレクションまたはマップフィールドを使用してください。
クエリの最適化
Firestore クエリは設計上高速ですが、その制約に従って作業する場合のみです — 逆らうのではなく。
6. 複合インデックスを事前に作成する
Firestore はすべての一意のクエリパターンにインデックスを必要とします。エラーメッセージを待つのではなく、インデックスを事前に計画してください。firestore.indexes.json で定義し、firebase deploy --only firestore:indexes でデプロイします。すべての where + orderBy の組み合わせには、独自の複合インデックスが必要です。
7. オフセットではなくカーソルベースのページネーションを使用する
Firestore では決してオフセットベースのページネーションを使用しないでください — スキップされたすべてのドキュメントを読み取り(課金)します。代わりに、前のページの最後のドキュメントで startAfter() を使用してください。これはより高速で安価です。Firestore は実際に必要なドキュメントのみを読み取るためです。
8. クエリ結果を明示的に制限する
常にクエリに .limit() を追加してください。これがないと、Firestore はすべての一致するドキュメントを返します — それは数百万になる可能性があります。今日コレクションが小さいと思っても、6ヶ月後にはそうではありません。良いデフォルトはクエリあたり20〜50ドキュメントです。
9. コレクション全体の読み取りを避ける
フィルタなしで collection('users').get() を実行していることに気づいたら、データモデルを再考する必要があります。分析には集約クエリ(count()、sum())を使用し、表示クエリには常に where() でフィルタリングしてください。すべてのドキュメントを読み取ることは、予期しない Firestore 請求の第1の原因です。
セキュリティルールパターン
Firestore セキュリティルールは、唯一のサーバーサイドバリデーションです。これを誤ると、データベース全体が露出します。
10. ルールで常にフィールドタイプを検証する
フィールドが存在するかどうかだけでなく、その型を検証してください。request.resource.data.age != null だけをチェックすると、悪意のあるクライアントが age: "not a number" を送信する可能性があります。セキュリティルールレベルで型を強制するために is string、is number、is bool を使用してください。
11. コレクションごとの詳細なルールを使用する
データベースレベルで allow read, write: if true を決して使用しないでください。各コレクションに特定のルールを記述してください:誰が読めるか、誰が作成できるか、誰が更新できるか、誰が削除できるか。すべてを拒否することから始めて、段階的にアクセスを開放してください。サブコレクションには match パターンを使用してください。
12. クライアント側のデータを決して信頼しない
クライアントは任意のデータを送信できます。必須フィールドを検証し、データ型をチェックし、enum 値を強制し、参照フィールドが実際のドキュメントを指していることを確認してください。request.auth を使用してユーザーが主張する本人であることを確認し、resource.data を使用して既存の値と比較してください。
コスト最適化
Firestore の価格は読み取り、書き込み、ストレージに基づいています。クエリ方法のわずかな変更が、請求に大きな影響を与える可能性があります。
13. 操作を減らすために書き込みをバッチ化する
writeBatch() または runTransaction() を使用して、複数の書き込みを単一の操作に結合してください。500の書き込みのバッチは、操作の観点からは500の個別の書き込みと同じコストがかかりますが、ネットワークの往復を削減し、原子性を確保します。バッチは最大500操作に制限してください。
14. 頻繁に読み取られるドキュメントをキャッシュする
Firestore のクライアント SDK には組み込みのオフライン永続化があります — これを有効にしてください。サーバーサイドアプリケーションの場合、変更が少ないが絶えず読み取られるドキュメント(アプリ設定やユーザープロファイルなど)のために、キャッシュレイヤー(Redis、インメモリ)を実装してください。キャッシュされた読み取りはすべて、支払わなくて済む読み取りです。
15. すべてのドキュメントを読み取る代わりに集約クエリを使用する
ドキュメントの数が必要ですか?すべてのドキュメントを取得してクライアント側でカウントするのではなく、countQuery() を使用してください。Firestore 集約クエリ(count()、sum()、average())は、完全なドキュメントではなくインデックスエントリを読み取り、エントリあたり約1/1000のコストで済みます。
ボーナス:データベース構造をドキュメント化する
チームがこれらを見つけられなければ、これらすべてのベストプラクティスは役に立ちません。Firestore データベースが成長するにつれて、コレクション構造、フィールド型、バリデーションルールを追跡することが課題になります。JSON Schema は、データベース構造を記述する標準的な方法を提供します — そして FireSchema のようなツールは、これらのスキーマをチーム全体が参照できるインタラクティブで閲覧可能なドキュメントに変換します。
💡 ヒント:各コレクションのフィールド、型、バリデーションルールをドキュメント化する .schema.json ファイルを作成してください。これがチームの唯一の信頼できる情報源になります。
次のステップ
Firestore とデータベースドキュメントについて学び続けましょう: