yourmystar tech blog
著者: fulu 公開日:

【Firestore】人類は集計の苦しみから解放されました

この記事はユアマイスター アドベントカレンダー 2022の 16 日目の記事です。

Firebase Summit 2022で Firestore の Count 機能が発表されました 🎉

Developer Preview Count() function: With the new count function in Firstore, you can now get the count of the matching documents when you run a query or read from a collection, without loading the actual documents, which saves you a lot of time.

https://firebase.blog/posts/2022/10/whats-new-at-Firebase-Summit-2022#reduce-time-spent-managing-your-database-with-firestore

いままで Firestore からデータを集計することにたくさん悩まされてきました。

本記事では Count 機能がない時代でどのように集計していたかに触れながら Count 機能を試していきます。

前提

以下のようなデータ構造を前提にサンプルコードを記述しております。

ユーザー: /users/:userId

<userId>: {
  displayName: '山田 太郎',
}

ユーザーの投稿: /users/:userId/posts/:postId

<postId>: {
  title: 'タイトル',
  body: '本文',
}

避けるべき集計方法

以下のようにクライアント側でコレクションの全データを取得して集計する方法もありますが、Firestore はドキュメントの読み取り、書き込み、削除ごとに課金される仕組みなので、この方法では取得件数分の読み取りが発生し、課金額が増えてしまいます。

import { collection, getDocs, getFirestore } from 'firebase/firestore'

const postsRef = collection(getFirestore(), 'users', userId, 'posts')
const snapshot = await getDocs(postsRef)
console.log(snapshot.docs.length) // 自分の投稿数を取得する

集計結果を格納するフィールドを作る

いままでは Firestore から集計結果を取得する方法がなく、クライアント側での集計も避ける必要があったため、集計結果を格納するフィールドを用意していました。

例えば、users のドキュメントに投稿数を格納する postsCount を用意してここから集計結果を取得するイメージです。

/users/:userId

<userId>: {
  displayName: '山田 太郎',
  postsCount: 100, <-- postsのドキュメントの件数を格納するフィールド
}

この方法で集計結果の整合性を保つために、posts のドキュメントが作成または削除される度に postsCount を更新する必要があります。

import { getDoc, getFirestore, increment, writeBatch } from 'firebase/firestore'

const userRef = doc(getFirestore(), 'users', userId)
const snapshot = await getDoc(userRef)

const batch = writeBatch(getFirestore())
const postRef = doc(getFirestore(), 'users', userId, 'posts', postId)
batch.set(postRef, { title: '新しい投稿のタイトル', body: '新しい投稿の本文' })
batch.update(userRef, { postsCount: increment(1) })
await batch.commit()

これからの集計方法

Count 機能の発表で getCountFromServer という関数が追加されて、Firestore から集計結果を取得できるようになりました。
(※ Web 用の SDK を使用しています。他の SDK はこちらを確認ください。)

この関数を使えば簡単に集計結果を取得することができます。

import { collection, getFirestore, getDocs, getCountFromServer } from 'firebase/firestore'

const postsRef = collection(getFirestore(), 'users', userId, 'posts')
const postsCount = await getCountFromServer(postsRef)
console.log(postsCount) // { count: xxxx }

発行するクエリの集計対象のデータ件数が 1000 件ごとに 1 回の読み取りとみなされるので、財布への負担も軽減されます。

For aggregation queries such as count(), you are charged one document read for each batch of up to 1000 index entries matched by the query. For aggregation queries that match 0 index entries, there is a minimum charge of one document read.

https://firebase.google.com/docs/firestore/pricing#pricing_overview

ポストするはてなブックマークに追加シェアする