Firebase(Firestore)のアンチパターンについて調べてみた
はじめに
Flutterを使ってアプリ開発をする際に、動かすまでが早いイメージのあったFirebaseに入門することにした。
NoSQL VS RDBみたいなところは正直よくわからないけど、使って理解するタイプなのでざっとアンチパターンを確認して使い始めてみることにする。
アンチパターンと銘打っているが、FirestoreでDBを設計する際にはどんなことに気をつけて設計すればいいのかまとめたい。
ちなみに筆者はNoSQL使ったことない。
Firestoreとは
Cloud Firestore は、Firebase と Google Cloud Platform からのモバイル、ウェブ、サーバー開発に対応した、柔軟でスケーラブルなデータベースです。
とのこと。2019年2月ごろに正式リリースされたらしい。
公式ドキュメントで特徴として取り上げているのは以下5点。
- 柔軟性
- 高機能なクエリ処理
- リアルタイム アップデート
- オフライン サポート
- 拡張性のある設計
柔軟性
ドキュメントとサブコレクションを使って自由度の高い使い方ができる。
高機能なクエリ処理
よくも悪くもシンプルなクエリしか発行できない印象。(裏返すと早いらしい)
DB設計にコツがいるのはこの辺が理由という印象。
デフォルトではクエリにはインデックスが付いているので、クエリのパフォーマンスは、データセットではなく結果セットのサイズに比例します。
この辺もポイントかも知れない。NoSQLを使った実装ではクライアントサイドジョインが前提だと聞いたけど、だからと言ってクライアントにデカめのデータを返すとDB側もクライアント側も辛そう。
リアルタイム アップデート
データ同期して、複数端末のデータを更新できるとのこと。
トランザクションとかどうなっているんだろう。シンプルなクエリが前提だからあまり気にしなくていいのか?
楽観ロックなのか悲観ロックなのかとかも気になる。
オフライン サポート
アクティブに使用されるデータをキャッシュするようで、オフラインでもデータを操作できるとのこと。(オンラインに戻ったタイミングで全て同期される)
コンフリクトとかしたらどうなるんだろう。
拡張性のある設計
Cloud Firestore は、Google Cloud Platform の強力なインフラストラクチャの優れた機能(自動マルチリージョン データ複製、優れた整合性の確保、アトミックな一括オペレーション、リアル トランザクション サポート)を提供します。
要はインフラ丸投げできるよーってことだと理解した。
ドキュメントとコレクション
個人的最初の関門。
Firestoreにはドキュメントとコレクションという概念があって、ドキュメントはデータの集合体、コレクションはドキュメントの集合体という理解。以下公式に載ってたイメージ。
コレクションとドキュメントは交互にしか定義できないというのが直感的によくわからなかったので実際に定義してみた。
タスク管理アプリのDB設計はこんな感じの構造になりそう。
(公式でサンプル見つからなかった&DB設計の具体例見つからなかったので正しいかどうかは不明)
RDBとFirestoreの違い
RDBのノリで理解しようとすると定義は?みたいになって大混乱だったのだけど、以下のように整理したらしっくりきた。
RDB
先にDBに格納するデータを定義しておいて、クライアントはその制約に従う。
Firestore(NoSQL?)
DB構造も含めクライアントに任されている。(※)
※制約が全くないわけではなく、「セキュリティルール」で制約を定義するよう。
ベストプラクティス
ドキュメントID編
-
ドキュメントIDに
.
と..
を使用しない。 -
ドキュメントIDに
/
を使用しない。 -
単調増加するドキュメントIDを使用しない。
- Customer1, Customer2, Customer3, ...
このように連続性のあるIDを使用するとパフォーマンスが低下する可能性がある。(ホットスポットが発生する)
ホットスポットとはなんぞや
辞書順で近い一連のドキュメントに対して、高頻度で読み取りや書き込みを行ったときにパフォーマンスが低下する問題。散布アルゴリズムというアルゴリズムの特徴らしい。
次のいずれかを行うとホットスポットが生じる可能性がある。
-
非常に高い頻度で新しいドキュメントを作成し、単調に増加する ID を割り当てる。
ドキュメントIDの自動割り当て機能を使うとホットスポットは生じない。
-
ドキュメント数が少ないコレクションで、新しいドキュメントを頻繁に作成する。
-
タイムスタンプのように単調に増加するフィールドを持つ新しいドキュメントを非常に高い頻度で作成する。
-
コレクション内のドキュメントを高頻度で削除する。
-
トラフィックを徐々に増やすことなく、高頻度でデータベースに書き込みを行う。
フィールド名編
追加のエスケープが必要になるので、次の文字は使わない。
- ピリオド(
.
) - 左角かっこ(
[
) - 右角かっこ(
]
) - アスタリスク(``)
- バッククォート(```)
インデックス編
-
インデックスを過剰に使用しない。
書き込みレイテンシも増加するし、ストレージコストも増加する。(Firestoreはストレージ使用量に対して課金される。)
-
タイムスタンプのように単調に増加する値にインデックスを作成しない。
書き込み、読み取りレートが高いアプリケーションにおいてレイテンシに影響を与えるホットスポットが生じる可能性がある。
インデックスの除外
以下のケースに該当する場合はインデックスを削除した方が良い場合がある。
-
大きな文字列フィール
インデックス作成から除外することでストレージコストの削減できる。
-
コレクションへの書き込みレートが高く、連続した値を持つドキュメントが含まれる
コレクション内のドキュメント間で順次に増加または減少するフィールド(タイムスタンプなど)のインデックスを作成する場合は、コレクションへの最大書き込みレートは 500 回/秒。
連続した値を持つフィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外すれば、この制限を回避できる。
-
大規模な配列フィールドまたはマップ フィールド
大規模な配列フィールドまたはマップ フィールドの場合、インデックス エントリの上限となっている、ドキュメントあたり 40,000 件に近づく可能性がある。大規模な配列フィールドまたはマップ フィールドに基づいてクエリを実行することがない場合は、そのフィールドをインデックス作成から除外すべし。
終わりに
Firestoreの概要を調べてみました。
パフォーマンス改善のためのベストプラクティスもあるようなのですが、一旦使ってみて追記しようと思います。セキュリティルールについてはボリュームがありそうなので、別途まとめようと思います。
Discussion