一昔前は、データの保存といえば MySQL や PostgreSQL といったRDBMS(Relational DataBase Management System)を使うことがメインでしたが、最近は Firestore を利用することも増えてきました。
データを保存するという役割は同じですが、RDBとは使い方・設計思想が大きく異るかと思います。
この記事では、RDBとは異なるFirestoreの制約を確認していきます。
集計関数が無い
RDBでは、インデックスやデータ量には依存しますが、数百・数千レコードを対象としても COUNT(*)
や SUM(amount)
といった集計が比較的手軽にできます。
ですが、Firestoreでは現実的ではありません。
例えば、1つの伝票にそれぞれ10明細が紐付いており、20伝票をリスト表示する画面があったとします。
そのリストに「合計金額」も表示したい場合、RDBではサブクエリで SUM
をするなどで、高速に結果を取得できます。
一方、Firestoreで同様のことをする場合は、クライアントサイドで200ドキュメントを読み込み、処理として集計する必要があります。これでは、コストや性能の面で問題があります。
このような場合、明細データの追加・更新時に伝票データの「合計金額」を更新しておき、画面表示時には伝票データのみの参照に抑えることが一般的かと思います。
他の制約においても、このように「書き込み時点で、読み込みのことを考えてデータを作成・更新しておく」という考え方が必要になってきます。
検索は苦手
2つ以上の条件を指定して検索するためには、インデックスを作成する必要があります。
さらに、インデックスを作成しても実行できないクエリが存在します。
範囲(
<
、<=
、>
、>=
)または不等値(!=
)の比較は 1 つのフィールドに対してのみ実行できます。
Cloud Firestore で単純なクエリと複合クエリを実行する | Firebase Documentation
例えば、「作成日時はA以降で、更新日時がB以降」といった、2つのカラムに対する範囲検索ができません。また、範囲検索と別のカラムでのソートもできません。
画面の仕様を変更してしまうか、別の検索用カラムで完全一致検索にするなど、回避策が必要となります。
例えば上記の例であれば、「実際には作成日が”特定の年月”のデータで、更新日時がB以降」で良いのであれば、作成年月(”yyyyMM”の文字列)という別カラムを追加しておくことで、一方の条件を完全一致に変えることができ、この制約を回避できます。
全文検索はできない
特定の文字列を含む投稿を検索するなど、全文検索が必要な場合には、 Algolia などの外部のサービスを利用することが一般的となります。
Cloud Firestore では、ネイティブ インデックスの作成やドキュメント内のテキスト フィールドの検索をサポートしていません。
さらに、コレクション全体をダウンロードして、クライアントサイドでフィールドを検索することは現実的ではありません。
Search with Algolia という Firebase Extensions を利用することで、FirestoreデータをシームレスにAlgoliaに同期することもできます。
件数指定での読み飛ばしができない
RDBMSでは、offsetを指定することでページングを表現できます。
Firestoreでは、limitは指定できますが、offsetは指定できません。
そのため、「ページ番号単位での読み込み」といった仕様に対応するのは難しくなります。
一方で、無限スクロールのような「連続したデータを読み込む」ことは容易に実現できるようになっています。
複数データの一括更新・削除ができない
「条件に該当したデータの、とあるフラグをすべて更新する」とか、「親データを削除したから、そのIDを持つ子データを一括で削除する」といったことができません。
条件指定で一旦取得して、個別に更新・削除していく必要があります。
トランザクションやバッチといった機能は存在するので、atomicに更新・削除することはできます。
ただし、1トランザクションでは500件の書き込みしか行なえません。
Commit オペレーションに渡すか、トランザクションで実行することができる書き込みの最大数 500
使用量と上限 | Firebase Documentation
security ruleはcollection単位のみで、フィールド単位には指定できない
RDBを利用する場合、クライアントとDBの間にAPIを実装するかと思います。
そのため、読み取り制限などはAPI側に”処理”として実装するのが一般的かと思います。
Firestoreの場合は、security ruleとして読み取り制限を定義するため、クライアントが直接Firestoreを参照することが一般的かと思います。
security ruleを設定することで、collection単位でさまざまな制約を付けることができます。
ただし、1つのデータの中で、「特定のフィールドだけは閲覧できない」といった制約を付けることはできません。
そのため、「ほかユーザーに見せる、公開用のユーザー情報」と「本人だけが見える、個人情報などを含んだユーザー情報」が存在する場合には、別々のcollectionで管理するのが一般的となります。
1つのドキュメントに対して、1秒に何度も書き込めない
ドキュメントへの最大継続書き込み速度: 1 秒あたり 1
1 秒あたり 1 回を超える書き込み速度が持続すると、レイテンシが増加し、競合エラーの原因になります。これはハードリミットではなく、短時間のバーストでは上限を超えても構いません。
使用量と上限 | Firebase Documentation
というソフトリミットがあります。
前述の通り集計ができないので、例えば投稿に対する「いいね数」を表示するためには、整数としてカウントアップしていく必要があります。
ただし、「いいね」はたくさんのユーザーから1つの投稿に集中する場合があるので、上記のソフトリミットに制限される可能性がありそうです。
公式ページに、回答が用意されています。
カウンタをもっと頻繁に更新できるようにするには、分散カウンタを作成します。
各カウンタは「シャード」のサブコレクションを持つドキュメントであり、カウンタの値はシャードの値の合計です。
分散カウンタ | Firebase Documentation
簡単に言うと、10個のカウンタドキュメントに分散して保存することでソフトリミットを1/10程度に引き下げ、読み込むときにはそれを合算することで読み込みコストもある程度で済ませる形です。
まとめ
Firestoreは、比較的低コストで運用できたり、リアルタイムに変更を検知することが簡単に実装できたりと、メリットが大きいです。
しかし、今まで利用してきたRDBMSとは、目指している方向や得意な場面が異なっています。
制約を理解した上で、適材適所で使い分けていくことが重要だと思います。