Skip to main content

第4章:サブコレクションの使いどころ(親子関係に強い)🧩

この章のゴールは、「親子でぶら下がる大量データはサブコレが強い」を体感しつつ、トップレベル案との比較まで自分でできるようになることです💪✨ (題材:postscomments


1) まず結論:サブコレがハマる“3つの型”🧠✨

Subcollection Tree Structure

Firestoreはドキュメントの中にサブコレクションを持てます。つまり、posts/{postId}/comments/{commentId} みたいな階層でデータを置けるんだね📚 (Firebase)

サブコレが特に強いのはこの3つ👇

A. 「親が決まってる時だけ読む」データ🧩

例:記事詳細ページでだけ表示するコメント欄

  • 画面が postId を持ってる(親が分かってる)
  • だから posts/{postId}/comments に“寄せる”と迷子になりにくい🙂

B. 「増え続ける」データ📈

コメント・履歴ログ・通知・監査ログ…みたいに、将来どんどん増えるやつ。 Firestoreの公式ドキュメントでも「時間とともに増えうるデータは、ドキュメント内の配列に詰めるより“コレクション(サブコレ)にする”」方向が語られてます。(Google Cloud Documentation) (ドキュメントは最大 1 MiB なので、配列に詰め込みすぎるといつか破裂する💥)(Google Cloud Documentation)

C. 「親の権限で守りたい」データ🔐

サブコレはパスが親を含むから、Rulesや設計上の見通しが良くなりがち。 「このコメントはどの投稿のもの?」がパスだけで明確になるのは運用で効きます👌


2) 逆に、トップレベルが向く“2つの型”⚖️

Top-Level Collection Structure

サブコレ万能じゃない!ここ大事🙂

A. “横断検索”が頻発するならトップレベルがラク🔎

例:

  • 「自分が書いたコメント一覧」
  • 「炎上してるコメントだけ一覧」
  • 「全文検索(別サービス連携)用にコメントをまとめて流したい」

こういうのが主役なら comments をトップレベルにして、postId をフィールドで持つ方がシンプルになりがち。

B. “削除・移動・集計”の運用が重いなら注意⚠️

サブコレは「親を消したら子も消える」みたいなカスケード削除はされません。 公式にも「ドキュメント削除してもサブコレは自動削除されない」って明記があります😇 (Firebase) (この爆弾💣は次章でちゃんと処理するよ!)


3) 今回の題材:コメントは “サブコレ案 vs トップレベル案” どっち?🤔🧩

Subcollection vs Top-Level Comparison

案1:サブコレ(王道)🧩

  • posts/{postId}
  • posts/{postId}/comments/{commentId}

強いところ

  • 設計が直感的(投稿にコメントがぶら下がる)
  • 投稿詳細画面の実装が素直
  • 将来増えるコメントを配列で抱えずに済む(1MiB回避)(Google Cloud Documentation)

気をつけるところ

  • 横断検索は「コレクショングループ」を使う前提になる
  • インデックス管理がちょい増える

案2:トップレベル(検索に強い)📚

  • posts/{postId}
  • comments/{commentId}(フィールドに postId を入れる)

強いところ

  • どんな一覧もクエリが作りやすい(where("postId","==",...) 等)
  • データ移動・管理の運用が単純になりやすい

気をつけるところ

  • 「このコメントは投稿に属する」という構造がパスから消える
  • Rulesや運用で “postId の整合性” をちゃんと守る必要が増える

4) サブコレでも“横断検索”はできる:コレクショングループ🧩🔎

Collection Group Query

サブコレを採用しても、同じ名前(例:全部 comments)なら collection group query で横断検索できます✨ (Firebase)

ただし! フィルタや orderBy を使うコレクショングループは、collection group スコープのインデックスが必要になることがあります。(Firebase) (足りないとエラーが出て、コンソール/CLIで作る流れになるやつ🛠️)


5) 手を動かす:2案のクエリを“同じ要件”で書いてみよう✍️⚡

ここでは「同じUI要件」を想定して、クエリの書きやすさを比較するよ🙂

要件(超よくある)📱

  1. 投稿詳細でコメント一覧(新しい順)
  2. 「自分のコメント一覧」
  3. 迷惑コメントだけ一覧(moderationFlag=true)

5-1) サブコレ案のクエリ例🧩

import { collection, collectionGroup, getFirestore, limit, orderBy, query, where } from "firebase/firestore";

const db = getFirestore();

// 1) 投稿詳細:その投稿のコメント一覧(新しい順)
function qCommentsOfPost(postId: string) {
return query(
collection(db, "posts", postId, "comments"),
orderBy("createdAt", "desc"),
limit(50)
);
}

// 2) 自分のコメント一覧(横断)※collection group
function qMyComments(uid: string) {
return query(
collectionGroup(db, "comments"),
where("authorId", "==", uid),
orderBy("createdAt", "desc"),
limit(50)
);
}

// 3) 迷惑コメントだけ(横断)※collection group
function qFlaggedComments() {
return query(
collectionGroup(db, "comments"),
where("moderationFlag", "==", true),
orderBy("createdAt", "desc"),
limit(50)
);
}

ポイント👉

  • 投稿詳細はめちゃ素直👍
  • 横断系は collectionGroup(db, "comments") が主役になる🙂
  • フィルタ+ソートはインデックスが要ることがある(エラーが先生)(Firebase)

5-2) トップレベル案のクエリ例📚

import { collection, getFirestore, limit, orderBy, query, where } from "firebase/firestore";

const db = getFirestore();

// comments/{commentId} に postId を持つ前提

function qCommentsOfPost(postId: string) {
return query(
collection(db, "comments"),
where("postId", "==", postId),
orderBy("createdAt", "desc"),
limit(50)
);
}

function qMyComments(uid: string) {
return query(
collection(db, "comments"),
where("authorId", "==", uid),
orderBy("createdAt", "desc"),
limit(50)
);
}

function qFlaggedComments() {
return query(
collection(db, "comments"),
where("moderationFlag", "==", true),
orderBy("createdAt", "desc"),
limit(50)
);
}

ポイント👉

  • 横断検索が最初から素直!
  • その代わり、**「このコメントはこの投稿に属する」**を postId で守り続ける設計が必要になる(改ざん対策も含む)🔐

6) ミニ課題:あなたのアプリならどっち?📝🔥

次の質問に“自分の言葉”で答えてみてね👇

  1. コメントは「投稿詳細で読む」が9割?それとも「コメント一覧」が主役?📱

  2. 「自分のコメント一覧」「通報コメント一覧」を最初から作る?🔎

  3. 投稿削除の時、コメントはどう扱いたい?(残す/消す/凍結)🧊💥

  4. AIでコメントを要約・判定するなら、結果はどこに置く?🤖

    • 例:コメントdocに moderationFlag を持つ
    • 例:posts/{postId}/comments/{commentId}/aiReviews/{reviewId} に履歴として残す(後で監査しやすい)

7) AIで“設計の迷い”を秒速で減らす🤖⚡(Antigravity / Gemini CLI)

Googleの Antigravity は「エージェントが計画→調査→実行」みたいに動くIDEコンセプトのCodelabが出ています。(Google Codelabs) Gemini CLI はターミナルで動くオープンソースのAIエージェントで、ReActループで作業を進める説明があります。(Google for Developers)

コピペで使える依頼文📎✨

  • サブコレ案 vs トップレベル案を比較して!

    • postscomments を、サブコレ案とトップレベル案で2案作って。各案で“必要なクエリ3つ(投稿コメント/自分のコメント/通報一覧)”と“必要そうなインデックス”も列挙して。運用リスク(削除/集計/改ざん)も比較して。」
  • コレクショングループのインデックス当たりを付けて!

    • collectionGroup('comments')where(authorId==)orderBy(createdAt desc) を使う時、必要になるインデックスの考え方を説明して。エラーが出た時の読み方も。」
  • AI結果をFirestoreに残す設計レビューして!

    • 「コメントのモデレーション結果をFirestoreに保存したい。履歴を残す設計(監査ログ)と、最新だけ持つ設計を比較して。個人情報・コスト・改ざん耐性も見て。」

※ AIをアプリに入れるときは App Checkレート制限が重要になってくるよ(Firebase AI Logic側でデフォルト “1ユーザー100RPM” などの話がある)(Firebase) このへんは「ログ設計」や「誰が叩けるか」の形に直結する🔥


8) チェック(この章で“できたら勝ち”✅)

  • 「サブコレが向く場面」を3つ言える🧩
  • 「トップレベルが向く場面」を2つ言える⚖️
  • サブコレでも collectionGroup で横断検索できるのを理解した🔎 (Firebase)
  • 親を消しても子が消えないことを知った(次章の地雷💣)(Firebase)

次の第5章は、ここで出した地雷――削除・移動・集計で「サブコレが詰まりやすいポイント」を、ちゃんと安全策つきで片付けるよ💥🧯