Runner in the High

技術のことをかくこころみ

FirestoreのCollection Groupに関して(ユースケースや注意点)

個人開発でFirestoreを使っているので情報を集めている。なんか日本語だとググっても要領を得ない記事ばっかりだったが、英語でちゃんと調べたらFirebase Blogでしっかり書いてあった

firebase.googleblog.com

FirestoreなどのNoSQLデータベースは前提として非正規化(denormalization)してサブコレクションをつくることでデータの関連を表現することが推奨されている。これには様々な理由*1があるが、Firestoreではコレクション・グループ(Collection Group)という機能を使うことで正規化したデータのようにサブコレクションを扱うことできる。

実際の例

たとえば「Firestoreで食べログ的なアプリケーションを作る」というケースでは単純に考えてRestaurantコレクション配下に、そのレストランについているレビューのデータを表現するReviewコレクションが存在する、ということは容易に想像できる。 https://3.bp.blogspot.com/-j9G1FSNk8gw/XQqlQGI_I3I/AAAAAAAADoI/ldmeyycIbmMbcH7-rUHX60rfNIMkDdc7gCLcBGAs/s1600/1.png このデータ設計では、ある特定のレストランのレビュー一覧を取得することは簡単であるが、もしレストラン関係なくデータとして存在するすべてのレビューを横断して取得したい、というユースケースの際には困ることになる。もし雑にやるとしたら、まずはレストランの一覧を取得した上で、こんどはレストラン毎にレビューの一覧を取得するという、どう考えてもN+1にしかならない処理をしなくてはいけなくなる。

それどころか「全てのレビューから日付が昨日以前のものだけクエリしたい」や「ある特定のユーザーが書いているレビューのみだけクエリしたい」というケースの場合にも一旦すべてのデータを取得せなばならず、どう考えてもオーバーヘッドと金がかかる処理にならざるをえない。

コレクション・グループによる解決

つまり、これを解決するのがコレクショングループである。非正規化されているデータであっても、インデクスが貼ってあればまるで正規化されたRDBMSでJOINするかのようにデータをクエリすることができる。すごい。

コレクション・グループを使うには事前にインデクスを貼っておく方法と、もしどこにインデクスを貼っていいか分からなければ、とりあえず collectionGroup メソッドを使ってログに出てくる警告メッセージからオンデマンドでインデクスをFirestoreに作らせる方法があるとのこと。

firebase.google.com

注意点

最後に、注意点として記載されていたがコレクション・グループでは異なるコレクション配下であるとしても重複したサブコレクション名を許容できないとのこと。たとえばRestraurantコレクションとCouriersコレクションそれぞれにサブコレクションとしてReviewsというものがあると、それらは個別のものとしてみなされないらしい。解決策としてコレクションの名前にユニークなプレフィクスを付ける(たとえばCourierReviews的な)というものがあるらしいが、これは早めに気づいておかないと大きな問題になる気がする。

おもしろかったFAQとして「Firestoreのデータ構造もRDBMSのように正規化してトップレベルにRestraurantやReviewのようなコレクションを配置して毎回クエリで絞りこめばいいのにコレクション・グループを推す意味はなんなのか」というものがあり、コレに対する答えとしてFirestoreはパフォーマンス上の制約からカスタムインデクス*2が最大で200個までしか許容されていないという仕様が上げられている。Firestoreでデータを正規化するのか、しないのかの議論は個別のユースケースによるものであるので、一概に絶対に非正規化することが常に正しい言い切れないが。

RDB技術者のためのNoSQLガイド

RDB技術者のためのNoSQLガイド

*1:NoSQLはパフォーマンスを重視して作られているためJOINの概念がない、そもそもRDBMSの補助を期待して作られているので正規化したデータはRDBMSに置けばよい、など

*2:正規化したコレクションに貼る外部キーへのインデクス。Firestoreではインデクスが貼られていないとクエリの対象にできない。