🔥

Firestore設計判断マトリクス:セキュリティ・パフォーマンス・コストを同時に満たす実践パターン

に公開

はじめに

Firestoreは「後から直すコストが高い」データベースです。

RDBMSであればテーブル構造を変更しても、ビューや正規化で吸収できる場合があります。しかしFirestoreでは、コレクション階層(パス設計)やドキュメント設計を誤ると、Security Rules・クエリ・インデックス・課金が絡んで“設計ごと移行”が必要になるケースが珍しくありません。

本記事では、セキュリティ・パフォーマンス・コストの3つの制約を同時に満たすFirestore設計パターンを、4つの代表パターンに整理して解説します。

この記事で学べること

本記事を読むことで、以下の知識が得られます。

Firestoreの3大制約(セキュリティ・パフォーマンス・コスト)とトレードオフ

Firestoreの設計では、以下の3つの制約が相互に影響します。

  • セキュリティ: Security Rulesでのアクセス制御(パス基準 or ドキュメント内フィールド基準)
  • パフォーマンス: コレクション階層とクエリ効率(collection group / インデックス含む)
  • コスト: 読み取り・書き込み回数(+ストレージ、ネットワーク、クエリ種別による差分)

これらを個別に最適化しても、全体としては失敗します。本記事では、3つを同時に考える設計判断を解説します。

4つの代表的設計パターンと選択基準

以下の4パターンを、適用場面・Security Rules・クエリ例とともに提示します。

  1. ユーザー完全分離型(例:個人日記、タスク管理)
  2. 共有データ+参照型(例:SNS、コメント)
  3. サブコレクション+集約型(例:チャット、スレッド)
  4. ハイブリッド型(例:レポート生成、分析結果保存)

概算に基づく課金比較とパフォーマンス指標

「返ってくるドキュメント数=読み取り回数」を基本に、4パターンの読み取り回数・N+1・集約の効果を比較します。

よくあるアンチパターンと回避策

以下の3つのアンチパターンと、それを回避する方法を説明します。

  1. トップレベルに何でも置く(Rulesが複雑化し、クエリが通らなくなる)
  2. 1ドキュメントが肥大化(更新コストが増大、1MiB制限に抵触)
  3. 非正規化しすぎ(整合性と書き込みコストの悪化)

プロジェクト初期の設計チェックリスト

「後から変えにくい」設計を初手で決めるためのチェックリストを提供します。

想定読者

本記事は、以下のような読者を想定しています。

  • Firebase Hostingでアプリを公開した経験がある
  • Firestoreを使い始めたが、設計に悩んでいる
  • RDBMSは分かるが、NoSQLの設計は不安

前提知識として、以下を仮定します。

  • Firestoreの基本概念(コレクション、ドキュメント、クエリ)
  • JavaScriptでのFirebase SDK操作(getDoc, getDocs
  • Security Rulesの雰囲気(allow read: if ... など)

Firestoreの3大制約とトレードオフ

Firestoreの設計では、以下の3つの制約が常に付きまといます。

セキュリティ(Security Rules):ユーザー単位アクセス制御の原則

Firestoreでは、Security Rulesでアクセス制御を実装します。

まず、パス(コレクション階層)でユーザーを表現できる設計は、ルールがシンプルです。

// firestore.rules(抜粋)
// 例: users/{uid}/tasks/{taskId} にアクセスできるのは本人のみ
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function signedIn() { return request.auth != null; }
    function isOwner(uid) { return signedIn() && request.auth.uid == uid; }

    match /users/{uid}/tasks/{taskId} {
      allow read: if isOwner(uid);
      allow create, update, delete: if isOwner(uid);
    }
  }
}

一方、トップレベルに置く設計(tasks/{taskId})でも、ドキュメント内に所有者ID(例: userId)を持たせればルールは書けます

// firestore.rules(抜粋)
// 例: tasks/{taskId} は userId フィールドで所有者を判定する
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    function signedIn() { return request.auth != null; }

    match /tasks/{taskId} {
      allow read: if signedIn() && resource.data.userId == request.auth.uid;

      allow create: if signedIn()
        && request.resource.data.userId == request.auth.uid;

      // 所有者(userId)のすり替え防止
      allow update: if signedIn()
        && resource.data.userId == request.auth.uid
        && request.resource.data.userId == resource.data.userId;

      allow delete: if signedIn() && resource.data.userId == request.auth.uid;
    }
  }
}

ただし重要な注意点があります。

  • Rulesはフィルタではありません(許可されないドキュメントを自動で除外してはくれません)
  • クエリは 「返り得る結果セットがすべて許可される」場合のみ 成功します
    → つまり tasks を一覧するなら、クライアントは where('userId', '==', uid) など ルールと整合する条件を必ず付ける必要があります(条件が合わないと permission-denied で失敗)

図1: 設計判断マトリクス
図1: 4つの設計パターンの比較(セキュリティ・パフォーマンス・コストの3軸評価)

パフォーマンス(クエリとインデックス):コレクション階層の深さとクエリ可能性

Firestoreでは、サブコレクションは“その親パス配下”での検索が基本です。

例えば、以下の構造では「全ユーザーのタスクを横断して一覧」を取れません(=users/{uid} をまたげません)。

users/alice/tasks/task1
users/bob/tasks/task2

横断検索が必要なら、コレクショングループクエリを検討します。

// コレクショングループクエリ(全ユーザーの tasks を検索)
const q = query(collectionGroup(db, 'tasks'), where('status', '==', 'open'));

コレクショングループクエリ自体は便利ですが、次の点に注意が必要です。

  • クエリ条件(where / orderBy の組み合わせ)によっては 複合インデックス(composite index)が必要になる
    → 実行時にエラーと作成リンクが出るので、それに従って作成するのが基本です
  • ルール設計によっては「必須フィルタ(例: ownerId)」が増え、クライアント実装が難しくなる

一方、トップレベルにコレクションを置くと、横断検索はシンプルです。

tasks/task1
tasks/task2

ただしこの場合、Security Rules(所有者判定)とクエリ条件(ownerId絞り込み) がセットになります。

図2: コレクション階層の違い
図2: パターン別のコレクション階層(ユーザー分離型 vs 共有データ型 vs サブコレクション型)

コスト(読み取り・書き込み・ストレージ):ドキュメントアクセス回数を起点に考える

Firestoreの主要な課金要素は以下です。

  • ドキュメントの読み取り / 書き込み / 削除
  • ストレージ(データ+インデックス)
  • ネットワーク(送受信)
  • 一部クエリの追加課金(例: 集計クエリの index entry reads など)

本記事ではまず、「返ってきたドキュメント数 ≒ 読み取り回数」 を起点に概算します。

  • 単価はリージョン等で変わります
    → 例として、Firebase公式の billing example に出てくる単価(reads: $0.06/100K, writes: $0.18/100K)を用いて計算します

つまり、以下のような設計判断が必要です。

  1. 非正規化してドキュメントを肥大化 → 1回の読み取りで全情報を取得(読み取り回数削減)
  2. 正規化してドキュメントを分割 → 参照解決で複数回の読み取りが必要(読み取り回数増加)

例えば、SNSのポスト一覧を表示する場合、以下の2つの設計があります。

設計A: 非正規化(ポストに投稿者情報を埋め込む)

posts/post1 = {
  content: "Hello",
  author: { uid: "alice", displayName: "Alice", photoURL: "..." }
}
  • メリット: 1回の読み取りで表示可能
  • デメリット: ユーザー情報が変わると全ポストを更新(書き込みが増える)

設計B: 正規化(ポストには投稿者IDだけ)

posts/post1 = {
  content: "Hello",
  authorId: "alice"
}
users/alice = {
  displayName: "Alice",
  photoURL: "..."
}
  • メリット: ユーザー情報の更新コストが低い
  • デメリット: ポスト一覧表示に複数回の読み取りが必要(読み取りが増える)

図3: 課金比較グラフ
図3: 4パターンの概算モデル(100件・1000件・10000件の読み取り回数比較)

設計判断マトリクス(4つの代表パターン)

ここでは、4つの代表的設計パターンを提示します。

それぞれに、コレクション設計・Security Rules・クエリ例・適用場面をセットで説明します。

以降のSecurity Rulesは読みやすさのため抜粋しています。実際は service cloud.firestore { match /databases/{database}/documents { ... } } の中に書きます。

パターン1: ユーザー完全分離型(例:個人日記、タスク管理)

コレクション設計

users/{uid}/tasks/{taskId}
users/{uid}/notes/{noteId}
  • 全データを users/{uid}/... 配下に配置
  • ユーザー間でデータを共有しない

Security Rules例

function signedIn() { return request.auth != null; }
function isOwner(uid) { return signedIn() && request.auth.uid == uid; }

match /users/{uid} {
  // プロフィール等のユーザードキュメントも扱うなら、本人のみ許可
  allow read, create, update, delete: if isOwner(uid);

  match /tasks/{taskId} {
    allow read: if isOwner(uid);
    allow create, update, delete: if isOwner(uid);
  }
  match /notes/{noteId} {
    allow read: if isOwner(uid);
    allow create, update, delete: if isOwner(uid);
  }
}
  • パスだけで所有者が分かるためルールがシンプル
  • 「本人の配下」を読むクエリは、基本的に permission-denied になりにくい

クエリ例(JavaScript SDK)

import { collection, query, where, getDocs } from 'firebase/firestore';

// 自分のタスクを取得
const tasksRef = collection(db, 'users', user.uid, 'tasks');
const q = query(tasksRef, where('status', '==', 'open'));
const snapshot = await getDocs(q);

snapshot.forEach(doc => {
  console.log(doc.id, doc.data());
});

適用場面と制約

適用場面:

  • 個人日記、タスク管理、メモアプリ
  • ユーザー間でデータを共有しない
  • 横断検索が不要

制約:

  • 全ユーザーのタスクを一覧表示できない(必要ならコレクショングループクエリ)
  • ユーザー間でのデータ共有が難しい(別設計が必要)

課金概算(単価例を用いた概算):

  • 100タスクの一覧表示: 100回読み取り = $0.00006
  • 1000タスクの一覧表示: 1000回読み取り = $0.0006
  • 10000タスクの一覧表示: 10000回読み取り = $0.006

パターン2: 共有データ+参照型(例:SNS、コメント)

コレクション設計

posts/{postId} = {
  content: "...",
  authorId: "alice",
  createdAt: Timestamp
}

users/{uid} = {
  displayName: "Alice",
  photoURL: "..."
}
  • トップレベルに共有データ(posts
  • ユーザー情報は users/{uid} で管理
  • ポストには authorId だけを持たせる(参照)

Security Rules例

function signedIn() { return request.auth != null; }

match /posts/{postId} {
  // 例: 公開ポストなら誰でも読める(機微情報を置かないこと)
  allow read: if true;

  // 作成は認証済みユーザーのみ
  allow create: if signedIn()
    && request.resource.data.authorId == request.auth.uid;

  // 更新は投稿者のみ(authorIdのすり替えも防ぐ)
  allow update: if signedIn()
    && resource.data.authorId == request.auth.uid
    && request.resource.data.authorId == resource.data.authorId;

  // 削除は投稿者のみ
  allow delete: if signedIn() && resource.data.authorId == request.auth.uid;
}

match /users/{uid} {
  // 例: 公開プロフィールなら誰でも読める(必要なら制限)
  allow read: if true;

  // 作成/更新/削除は本人のみ
  allow create, update, delete: if signedIn() && request.auth.uid == uid;
}

クエリ例(JavaScript SDK)

import { collection, query, orderBy, limit, getDocs, getDoc, doc } from 'firebase/firestore';

// ポスト一覧を取得
const postsRef = collection(db, 'posts');
const q = query(postsRef, orderBy('createdAt', 'desc'), limit(20));
const snapshot = await getDocs(q);

// 各ポストの投稿者情報を取得(参照解決)
const postsWithAuthor = await Promise.all(
  snapshot.docs.map(async (postDoc) => {
    const postData = postDoc.data();
    const authorSnap = await getDoc(doc(db, 'users', postData.authorId));
    return {
      id: postDoc.id,
      ...postData,
      author: authorSnap.data(),
    };
  })
);

console.log(postsWithAuthor);

適用場面と制約

適用場面:

  • SNS、ブログ、コメント機能
  • ユーザー間でデータを共有する
  • 横断検索が必要(全ポストを時系列表示など)

制約:

  • ポスト一覧表示に N+1問題 が発生しやすい(20ポスト = 20回 + 最大20回のユーザー取得)
  • ユーザー情報をキャッシュしないと課金が増える
    → 同一ユーザーが連続するケースは、メモ化やバッチ取得などで重複読み取りを減らす

課金概算(単価例を用いた概算):

  • 20ポストの一覧表示: 20ポスト + 20ユーザー = 40回読み取り = $0.000024
  • 100ポストの一覧表示: 100ポスト + 100ユーザー = 200回読み取り = $0.00012
  • キャッシュあり(同一ユーザーの重複削減): 100ポスト + 50ユーザー = 150回読み取り = $0.00009

パターン3: サブコレクション+集約型(例:チャット、スレッド)

コレクション設計

threads/{threadId} = {
  title: "...",
  lastMessageAt: Timestamp,
  messageCount: 100
}

threads/{threadId}/messages/{messageId} = {
  content: "...",
  senderId: "alice",
  createdAt: Timestamp
}

users/{uid} = {
  displayName: "Alice",
  photoURL: "..."
}
  • スレッド情報(threads/{threadId})に 集約値 を持たせる
  • メッセージはサブコレクション(threads/{threadId}/messages/{messageId})に格納

Security Rules例

function signedIn() { return request.auth != null; }

match /threads/{threadId} {
  // 例: 公開スレッドなら誰でも読める(要件に応じて制限)
  allow read: if true;

  // 作成は認証済みユーザーのみ
  allow create: if signedIn();

  match /messages/{messageId} {
    // 例: 公開スレッドのメッセージは読める
    allow read: if true;

    // 作成は認証済みユーザーのみ(送信者IDの偽装を防ぐ)
    allow create: if signedIn()
      && request.resource.data.senderId == request.auth.uid;

    // 編集/削除が必要なら update/delete を追加(要件次第)
  }
}

クエリ例(JavaScript SDK)

import { collection, query, orderBy, limit, getDocs } from 'firebase/firestore';

// スレッド一覧を取得(集約値のみ)
const threadsRef = collection(db, 'threads');
const q = query(threadsRef, orderBy('lastMessageAt', 'desc'), limit(20));
const snapshot = await getDocs(q);

console.log('スレッド一覧:', snapshot.docs.map(doc => doc.data()));

// 特定スレッドのメッセージを取得
const threadId = 'thread1';
const messagesRef = collection(db, 'threads', threadId, 'messages');
const messagesQuery = query(messagesRef, orderBy('createdAt', 'asc'), limit(50));
const messagesSnapshot = await getDocs(messagesQuery);

console.log('メッセージ一覧:', messagesSnapshot.docs.map(doc => doc.data()));

適用場面と制約

適用場面:

  • チャットアプリ、掲示板、コメントスレッド
  • 1スレッドに大量のメッセージがある(100件以上)
  • スレッド一覧には 最新メッセージ時刻や件数だけ を表示したい

制約:

  • 集約値(lastMessageAt, messageCount)の更新が必要
    → Cloud Functions / Cloud Run などで自動化推奨
  • 全スレッドの全メッセージを横断検索できない
    → 必要ならコレクショングループクエリ(+インデックス設計)

課金概算(単価例を用いた概算):

  • 20スレッドの一覧表示: 20回読み取り = $0.000012(メッセージは読まない)
  • 1スレッドの50メッセージ表示: 50回読み取り = $0.00003
  • 集約値の更新コスト(例): 1メッセージ追加 = 1書き込み(メッセージ) + 1書き込み(スレッド更新) = $0.0000036

パターン4: ハイブリッド型(例:レポート生成、分析結果保存)

コレクション設計

users/{uid}/rawData/{dataId} = {
  timestamp: Timestamp,
  value: 123
}

users/{uid}/reports/{reportId} = {
  type: "monthly",
  period: "2026-01",
  summary: { total: 3000, average: 100 },
  generatedAt: Timestamp
}
  • 生データrawData)と 集約データreports)を分離
  • レポートはCloud FunctionsやCloud Runで定期生成

Security Rules例

function signedIn() { return request.auth != null; }
function isOwner(uid) { return signedIn() && request.auth.uid == uid; }

match /users/{uid} {
  match /rawData/{dataId} {
    allow read, create, update, delete: if isOwner(uid);
  }

  match /reports/{reportId} {
    allow read: if isOwner(uid);

    // 書き込みはAdmin SDKのみ(Cloud Functions / Cloud Run)
    allow create, update, delete: if false;
  }
}

クエリ例(JavaScript SDK)

import { collection, query, where, getDocs } from 'firebase/firestore';

// 生データを取得(通常は表示しない)
const rawDataRef = collection(db, 'users', user.uid, 'rawData');
const q = query(rawDataRef, where('timestamp', '>=', startDate));
const snapshot = await getDocs(q);

// レポートを取得(集約済み)
const reportsRef = collection(db, 'users', user.uid, 'reports');
const reportsQuery = query(reportsRef, where('type', '==', 'monthly'));
const reportsSnapshot = await getDocs(reportsQuery);

console.log('レポート一覧:', reportsSnapshot.docs.map(doc => doc.data()));

適用場面と制約

適用場面:

  • IoTデータ、ログ、分析アプリ
  • 大量の生データを持つが、ユーザーには 集約データだけ を見せる
  • レポート生成はバックエンド(Cloud Functions / Cloud Run)で実行

制約:

  • レポート生成のタイミングを制御する必要がある(リアルタイム性は低い)
  • Cloud Functions / Cloud RunでAdmin SDKを使う必要がある(= IAMで守る)

課金概算(単価例を用いた概算):

  • 月次レポート1件の表示: 1回読み取り = $0.0000006
  • 生データ10000件からレポート生成: 10000回読み取り + 1回書き込み = $0.006 + $0.0000018 = $0.0060018
  • ユーザー体験: 毎回10000件読むのではなく、1回のレポート生成で何度も参照できる

図4: Security Rulesの適用範囲
図4: コレクション階層とSecurity Rulesの対応関係(どのコレクションにどのルールが効くか)

よくあるアンチパターンと回避策

ここでは、よくある設計ミスとその回避策を説明します。

アンチパターン1: トップレベルに何でも置く

問題

以下のような設計をしてしまうと、Rulesとクエリ条件がセットになり、クライアント実装が難しくなります(特に一覧クエリ)。

// 例: 全ユーザーのタスクが tasks に混在
tasks/task1 = { userId: "alice", content: "..." }
tasks/task2 = { userId: "bob", content: "..." }

この構造でも、userId を持たせればルールは書けます。しかし、次の点が落とし穴です。

  • create では resource が存在しないため、resource.data ではなく request.resource.data が必要
  • Rulesはフィルタではないため、クエリは必ず where('userId','==',uid) のように“ルールと一致する条件”が必要
    条件が足りないと 結果を返すのではなくクエリ自体が失敗します(permission-denied

回避策

ユーザー単位で閉じるユースケース(タスク、メモ等)なら、最初から users/{uid}/... に分離するのが安全です。

users/alice/tasks/task1 = { content: "..." }
users/bob/tasks/task2 = { content: "..." }

Security Rulesもパスだけで判定でき、クエリも単純になります。

match /users/{uid}/tasks/{taskId} {
  allow read, create, update, delete: if request.auth != null && request.auth.uid == uid;
}

アンチパターン2: 1ドキュメントが肥大化

問題

以下のような設計をしてしまうと、更新のたびに全体を読み書きすることになります。

// NG: 1ドキュメントに全メッセージを埋め込む
threads/thread1 = {
  title: "...",
  messages: [
    { senderId: "alice", content: "Hello" },
    { senderId: "bob", content: "Hi" },
    // ... 1000件
  ]
}

Firestoreでは、ドキュメント(フィールド値)サイズに上限(1MiB) があります。また、配列に要素を追加するだけでもドキュメント全体の更新になり、競合や書き込みコストも増えがちです。

回避策

サブコレクションに分割します。

threads/thread1 = {
  title: "...",
  messageCount: 1000
}

threads/thread1/messages/msg1 = { senderId: "alice", content: "Hello" }
threads/thread1/messages/msg2 = { senderId: "bob", content: "Hi" }

これで、メッセージ追加は1ドキュメントの書き込みで済みます。

分割タイミングの判断基準(目安)

  • 配列が10要素を超えるかもしれない → サブコレクションを検討
  • 配列が100要素を超える見込み → サブコレクション推奨
  • 要素が動的に増加する → 最初からサブコレクション推奨

アンチパターン3: 非正規化しすぎ

問題

以下のような設計をしてしまうと、更新箇所が増えて課金・整合性が崩れます

// NG: 全ポストにユーザー情報を埋め込む
posts/post1 = {
  content: "...",
  author: { uid: "alice", displayName: "Alice", photoURL: "..." }
}
posts/post2 = {
  content: "...",
  author: { uid: "alice", displayName: "Alice", photoURL: "..." }
}

ユーザーが名前を変更すると、全ポストを更新する必要があります。

  • ポストが1000件 = 1000回書き込み(単価例だと $0.0018 相当)
  • 整合性の問題(更新漏れでデータが古いまま)

回避策

「どこまで非正規化するか」を判断します。

判断基準:

  1. 更新頻度: ほとんど変わらない情報は埋め込みやすい(例: displayName)
  2. 更新影響範囲: 更新時に影響するドキュメント数が小さいなら許容しやすい
  3. 表示頻度: 毎回表示するなら埋め込む価値がある

推奨設計:

// OK: 最低限の情報だけ埋め込む(displayNameのみ)
posts/post1 = {
  content: "...",
  authorId: "alice",
  authorDisplayName: "Alice" // 更新頻度が低い想定
}

// 詳細情報は参照
users/alice = {
  displayName: "Alice",
  photoURL: "...",
  bio: "..." // 変わる可能性がある
}

実装チェックリスト(プロジェクト初期に決める)

以下のチェックリストを使って、プロジェクト初期に設計を確定させます。

ユーザー単位 or 共有データの比率

  • データの大部分はユーザー単位か?(タスク、メモ、設定)
    • → パターン1: ユーザー完全分離型
  • データの大部分は共有か?(SNSのポスト、コメント)
    • → パターン2: 共有データ+参照型

Security Rulesの複雑度想定

  • パス(users/{uid})だけで判定できるか?
    • → パターン1が有利
  • ドキュメント内のフィールド(authorId など)を見る必要があるか?
    • → パターン2/4寄り(create/updateで request.resource.data の検証も必要)
  • 複数条件の組み合わせ(isPublic && (authorId == uid || isMember))が必要か?
    • → ルールのテストを徹底する(Emulator / Rules Playground)

クエリパターン(リスト表示・検索・集計)

  • 全ユーザーのデータを横断検索する必要があるか?
    • → トップレベル(パターン2)またはコレクショングループ(要件次第)
  • ユーザー単位でのみ検索するか?
    • → サブコレクション(パターン1)
  • 1スレッドに大量のサブデータがあるか?
    • → サブコレクション+集約(パターン3)

想定トラフィック(読み取り回数の概算)

  • 1日あたりの想定アクティブユーザー数は?
  • 1ユーザーあたりの読み取り回数は?
  • 月間読み取り回数 = アクティブユーザー × 読み取り回数 × 30日
  • 課金概算 = 月間読み取り回数 × (read単価/100,000)

例(単価例: $0.06/100K reads):

  • 1000 DAU × 100回/日 × 30日 = 3,000,000回/月 ≒ $1.8/月

インデックス戦略

  • 複合クエリ(where + orderBy、複数 where)を使うか?
    • → 複合インデックスが必要になることが多い(実行時に作成リンクが出る)
  • コレクショングループクエリを使うか?
    • → 条件次第で複合インデックスが必要。再現性のため firestore.indexes.json で管理するのも有効

Admin SDKの使い所(運用注意)

  • レポート生成、集計処理があるか?
    • → Cloud Functions / Cloud Runで実行(パターン4)
  • バッチ更新(全ドキュメントの一括更新)があるか?
    • → Admin SDKで実行(Security Rulesをバイパス)
    • クライアントにサービスアカウント鍵を入れない / IAMとSecret管理で守る

まとめ

Firestoreの設計は、コレクション階層・Rules・クエリ条件・課金が密結合です。「後から変えればいい」は成立しにくいので、初手で設計判断を固めるのが重要です。

本記事では、以下を提供しました。

  1. 3つの制約(セキュリティ・パフォーマンス・コスト)を同時に考える視点
  2. 4つの代表的設計パターンと選択基準
  3. 概算モデルに基づく読み取り回数の比較
  4. よくあるアンチパターンと回避策
  5. プロジェクト初期に使える設計チェックリスト

パターン選択は、想定ユースケースから逆算します。

  • 個人データ中心(タスク、メモ) → パターン1
  • 共有データ中心(SNS、コメント) → パターン2
  • 大量サブデータ(チャット、スレッド) → パターン3
  • 生データ+集約(分析、レポート) → パターン4

次のステップ

本記事でFirestoreの設計判断を習得したら、次は以下のテーマに進むことをお勧めします。

Firebase Functions実践(集計処理、Webhook)

  • Cloud Functionsで集約値を自動更新
  • Firestore TriggersでスレッドのmessageCountを更新
  • Webhookで外部APIと連携

Cloud Run × Firestore高度連携

  • Admin SDKでバッチ処理
  • Firestoreのバックアップ戦略
  • Cloud Runからのストリーミング更新(Server-Sent Events)

Firestore × BigQuery連携(分析基盤)

  • Firestoreのデータを自動でBigQueryにエクスポート
  • SQLで集計・分析
  • Data StudioやLooker Studioで可視化

参考リンク

Discussion