📜

[Firestore]コレクションにするか、MapのArrayにするか

2022/03/15に公開

はじめに

データ量がたいして大きくならない見込みの、技術ブログのタグなどのようなデータをFirestoreで管理する場合

  • コレクションとして複数のドキュメントに分けて保存する
  • Map型(オブジェクト型)の配列として一つのドキュメントに保存する

のどちらの方法を取るべきかを考えました。

以降ではこの2つの保存方法を「コレクション」、「Map型の配列」と呼んでいます。

具体例

具体的なデータの例として、技術ブログなどのタグのデータを考えます。タグの数は100個あるとします。

次項の獲得時間の測定では、このデータを使用して測定しています。

コレクションの例
コレクション

Map型の配列の例
Map型の配列

比較

2つの方法を以下の3項目について比較します。

全件獲得にかかる時間

Firestoreのからタグデータ100件の全件取得をそれぞれ5回行い、データの獲得までにかかる時間を測定しました。

コレクション(ms) MapのArray(ms)
179.6 101.4
68.7 45.6
102.3 46.4
62.9 54.8
74.3 46.9
平均: 97.6 平均: 59.0

値のばらつきが大きいのが気になりますが、コレクションの方がMap型の配列と比べて、2倍近くの処理時間がかかることがわかりました。

実測するまでは、ドキュメントを1件だけ獲得するMap型の配列に比べて、ドキュメントを100件獲得する必要があるコレクション方式では、100倍近くの時間がかかるのではないかと考えていましたが、そこまで大きな違いはなかったのです。

測定に使ったコードはこちら
// コレクションの全件取得
const subCollection = () => {
  const start = performance.now();

  return getDocs(collection(db, "コレクションのパス")).then((snaps) => {
    const end = performance.now();
    console.log(`subCollection: ${end - start} ms`);

    console.log(snaps.docs.map((doc) => doc.data()));
  });
};

// Map型の配列の全件取得
const mapArray = () => {
  const start = performance.now();

  return getDoc(doc(db, "ドキュメントのパス")).then((snap) => {
    const end = performance.now();
    console.log(`mapArray: ${end - start} ms`);

    console.log(snap.data()?.tags);
  });
};

for (let i = 0; i < 5; i++) {
  await subCollection();
  await mapArray();
}

Firestoreの使用量

Firestoreの料金の計算に用いられる「ドキュメント読み取り回数」、「ドキュメント書き込み回数」、「下りネットワークの使用量」を比較しました。

全件獲得する場合

コレクションの「ドキュメント読み取り回数」がドキュメントの総数(今回の例では100)になるのに対して、Map型の配列では1回で済むので、Map型の配列の方が適している

1件だけ獲得する場合

「ドキュメント読み取り回数」はどちらも1回だが、「下りネットワークの使用量」がコレクションではタグ1つ分のデータなのに対して、Map型のArrayでは全件のデータ分加算される。よって、コレクションの方が適している

新規作成・更新する場合

1つのタグを更新・作成する場合は使用量は同じ。
複数のタグをまとめて、作成・更新する場合は1回の書き込みで済むMap型の配列の方が適している

その他のメリット・デメリット

  • Map型の配列でクエリをする場合は、全件取得しクライアント側で配列のフィルター、ソートを行うことになる。データの件数や、クエリの計算量、端末のスペックによっては処理時間が大きくかかるかもしれない。

  • コレクションではFirestore ruleで、ドキュメントごとにアクセス制御出来る。

まとめ

これまでの項を踏まえて、データ量が1MiBを超えることがなさそうな、今回例に上げたタグのようなデータは、Map型の配列として保存した方がメリットが多いと考えます。

特に、全件獲得の際の「ドキュメント読み取り回数」が1になることは、Firebaseの課金料を大きく減らす可能性のある、重要なメリットだと思います。

一方、データ・フィールドの量が増えて、Map型の配列にしたら1MiBを超える可能性のある場合や、データの件数が多くクライアント側でのクエリが困難な場合、Firestore Rules細かいアクセス制御が必要な場合は、コレクションにすることが必須になると思われます。

ここまで読んでいただきありがとうございました。この記事に誤っている点・誤解を招く点や、アドバイスなどがありましたらぜひ、コメントなどで教えていただきたいです!

Discussion