Firestoreとは
Goolge Cloudの提供するサーバレスなNoSQLサービス.
- Google Cloud からFirestoreの機能を切り出したのがFirebase Firestoreなのでここでは同一のもとして述べる.
- GCFと略すとどちらかというと Google Cloud Functionsを指すかも.
Firestoreの特徴
データベースというよりは、検索可能なデータ置き場. 複雑なデータ処理が要求されるようなアプリには余り向いてない.
Firestoreの操作
- Cloud Firestore でデータを取得する | Firebase Documentation
- Cloud Firestore にデータを追加する | Firebase Documentation
Quick Reference
いつも忘れるので簡単にまとめておく.
- 追加
- add: 新規作成(IDを自動生成)
- add(data): CollectionReference => none
- set: 新規作成(IDを指定)
- set(data): DocumentReference => none
- set: 新規作成(IDを自動生成)
- set(data): DocumentReference => none
- add: 新規作成(IDを自動生成)
- 取得
- get: 単一ドキュメント取得
- get(): DocumentReference => DocumentSnapshot
- get: 複数ドキュメント取得
- get(): CollectionReference => QuerySnapshot
- where: 条件指定
- orderBy: 並べ替え
- limit: 個数指定
- get: 単一ドキュメント取得
- 更新
- update: ドキュメント内のフィールド更新
- update(data): DocumentReference => none
- Map型フィールドを更新:
- ドット表記 でネストしたデータを更新.
- update({“foo.bar”: 1})
- ネスト表記でネストデータを親から上書き.
- update({“foo”: {“bar”: 1}})
- ドット表記 でネストしたデータを更新.
- 配列フィールドを更新:
- arrayUnion: 要素を配列に追加
- arrayRemove: 要素を配列から削除
- set: ドキュメントを上書き
- set({merge: true}): : ドキュメント内のフィールド更新
- update: ドキュメント内のフィールド更新
- 削除
- delete: 単一ドキュメント削除
- delete(): DocumentReference => none
- collectionの削除IFはない.
- collection内の全てのドキュメントがなくなったら自動で削除
- Web UIから手動で削除
- delete: 単一ドキュメント削除
💡updateと set(merge:true)の違い
どちらもドキュメント内のフィールドを更新する方法. ドキュメント内のフィールドが存在しない場合の挙動に違いがある.
- updateの場合はエラーを返す.
- set(merge:true)の場合はフィールドは自動で作成される.
以上を踏まえてパターン整理.
- doc がnot existの場合
- doc新規作成
- add/set
- doc新規作成
- doc がexistの場合
- doc上書き
- set: docまるごと上書き
- fieldが存在するとき
- set{merge: true}: 与えられたフィールドのみ更新. 他は残す.
- fieldが存在しないとき
- update: 与えられたフィールドを更新.
- doc上書き
Firestoreの基本概念: データモデル(DataModel)
ref: Cloud Firestore データモデル | Firebase Documentation
ドキュメントはストレージの単位. コレクションはドキュメントのコンテナ.
非リレーショナルなドキュメントデータベース
Google Cloud公式ブログの記事でFirestoreの設計概念である非リレーショナルなドキュメントデータベースについての解説がわかりやすい.
この記事から推測するとコレクションやドキュメントのような概念は Firestore特有ではなくその一つ上の抽象概念であるドキュメントデータベースのもの.
- コレクション
- ドキュメント
- リファレンス
- サブコレクション
Cloud Firestore の使用経験がないユーザー向けの Firestore の説明 | Google Cloud Blog
クエリ(query)
Firestoreに送信するリクエストのこと.
リファレンス(Reference)
リファレンス(Reference)はデータベース内の場所を参照するだけのオブジェクト. リファレンスを作成してもネットワーク操作は実行されない.
- DocumentReference
- CollectionReference
Referenceに対して.getをするとSnapshotが手に入る.
スナップショット(Snapshot)
スナップショット(Snapshot)はデータのオブジェクト.
.getData()によってデータ本体(JSON)を取り出す.
Firestore: データの関係(Relationship)
Firestore キー(or ID)
あるドキュメントの参照として別のドキュメントを示すkeyやidをStringで保持する.
これが普通の発想でありFirestoreというドメインに特化していない. 残りの2つはFirestoreの独自機能なので便利である分注意も必要.
Firestore サブコレクション(Subcollection)
ふたつのコレクションが親子関係にあり, 子のコレクションは1つの親にのみ依存するならばサブコレクションをつかうことでツリー構造に管理できる.
Firestore 参照型(reference type)
他のコレクションやドキュメントへの参照を保持することができる. SQLにおける外部キーの概念.
参照型をつかうとidよりも格納されているものが明確できる.
💡考察: データはそのままデータとして扱いたい
ClojureのData as dataのconceptからすると jsonをそのまま格納したほうがsimpleなので, ドメインに依存する参照型はあまり積極的に使う理由はないな. マップの中のマップは見かけるのでサブコレクションは形容できる.
セキュリティルールも考慮に入れる必要あり.
設計哲学的に迷ったらよりシンプルな方法を採用したい.
Client SDK
- Node.js client library | Google Cloud
- npm install @google-cloud/firestore
- https://github.com/googleapis/nodejs-firestore
- npm install firebase-admin —save
JavaのFirestoreに関わるクラスとメソッド
複雑すぎるな…
-
- .document()
- .document()
通信の観点で整理.
Referenceは通信前, getによって通信がバックグラウンドで開始する.
Futureは通信をバックグラウンドで実行のため通信中. 通信結果はこの時点ではわからない. exists()などで結果を確認しないといけない.
Snapshotは通信後. データはkey-valueのペアのため, getId/getDataでそれぞれアクセス.
Firestore: DB設計のベストプラクティス
FirestoreのDB設計は正解はないが指針やベストプラクティスがある.
- 更新頻度によってデータを分ける.
- セキュリティによってデータを分ける.
- 速度を優先するなら非正規化, そうでなければ(保守を優先するなら)正規化.
- データの結合はクライアントで行う(Application Side Join)
Google Firebase公式のデータ構造選択の指針
データ構造の選択 | Firebase Documentation
- ドキュメント内のネストデータ
- 時間経過とともに変化しないものならこれがいい, read only.
- サブコレクション
- 時間経過とともにデータが大きくなる場合はこれ.
- サブコレクションはかんたんに削除できない(自力で削除).
- ルートレベルのコレクション
- 全てルートにコレクションを配置, サブコレクションは使わない.
FirestoreはNoSQLの仲間なのでNoSQLのベストプラクティスが参考になる.
Firestore: DB操作のベストプラクティス
Firebase公式のベストプラクティスのドキュメント.
Best practices for Cloud Firestore
フィールドが存在しないときの書き込みの挙動
RDBの慣習で新規作成と更新でcreated_at, updated_atをフィールドに持つことを想定するとする. このとき更新をかけようとするとcreated_atは更新せずにupdated_atに現在のタイムスタンプを入れたい.
このような存在しない場合のみ上書きの挙動はfirestoreではサーバ側の判定ではなくアプリ側でgetによってdocumentReferenceを取得してexist判定をするしかない.
そもそもRDBの慣習であるcreated_at, udpated_atというものはNoSQLでは不要なのかもしれない.
複数フィールドへのwhereとorderByは複合インデックスを貼る
Firestoreの制約で絞り込んでから並び替えはできない(where -> orderby). 並び替えてから絞り込みはできる(orderby -> where). where A-> orderby Bをしようとしたら, where A -> orderby A -> orderby BというようにdummyのソートをAにかけてさらにAとBの間に複合インデックスを貼る.
Aのフィールドに対して並びかけをしてBのフィールドに対して条件による絞り込みをする場合, AとBの間に複合インデックスをはらないとエラーする.
INVALID_ARGUMENT: inequality filter property and first sort order must be the same: actress_count and last_crawled_time
ref. Cloud Firestore でデータを並べ替えたり制限する | Firebase
Firestoreサーバサイドの複雑なクエリに期待しない
それほど使い込んだわけではないので個人的な所感.
複雑なロジックによるDB操作は基本的にクライアント側で実装する. 複数フィールドに対する並べ替えや絞り込みや, 複数コレクションに対するクエリとマージはRDBの常識は通用しない.
Firestoreにはいろいろな制約事項があり, たしかにStackOverflowを漁れば裏技は見つかるかもしれない. しかし基本的にはwhereである程度のシンプルな絞り込みをしてクライアント側にデータを取得してそこからゴニョゴニョしたほうが結果的に早く問題解決に至れる.
Firestore HOWTO/Tips
Firestoreでこれはどうやるの?ってやつまとめ.
💡timestamp対するbetweenなクエリをするには?
whereと不等号ではなく, orderBy & startAt(startAfter) & endAt(endBefore)を利用する.
💡ネストしたマップのデータをを更新したい
update & ドット表記 でネストしたデータを更新.
(これは裏技っぽくて知らないとできない… )
💡コレクションの名前を変更したい(rename)
renameの手段はないので別の名前のコレクションを新規作成してデータをコピーして元のコレクションを削除する.
なおコレクションの削除もIFはないので中身のドキュメントをすべて削除する必要がある.
ドキュメントIDのリストで一括取得したい
バッチ書き込みするには?
いちおう手段はあるようなのでメモ. まだ試してない.
トランザクションとバッチ書き込み | Firestore | Firebase
Firestore: 命名規約
Firestoreの命名規約に関する明確なドキュメントは見当たらなかった.おそらく開発言語の命名規約に従うのがいい.
複数言語で開発する場合は, Cloud APIsのドキュメントに従うことにする. これはおそらくJavaの慣習.
命名規則 | Cloud APIs | Google Cloud
- コレクション/ドキュメント
- 複数形の単語 or 複合語
- アメリカ英語のスペルとセマンティクス(not イギリス英語).
- ex. events, children, deletedEvents
- フィールド名
- snake_case(lower_case_underscore_separated_names)推奨.
- 前置詞を含めないことが推奨
- reason_for_error よりも error_reason.
- cpu_usage_at_time_of_failure よりも failer_time_cpu_usage.
- 後置形容詞を使用しないことも推奨
- items_collected よりも collected_items
- objects_importedよりも imported_objects
- タイムスタンプ
- タイムゾーンやカレンダーに関わらずgoogle.protobuf.Timestampが推奨.
- start_time, end_timeのように, 末尾にtimeをつけることが推奨.
- google.type.Dateと_dateのprefix推奨.
- ソフトウェア開発での慣習的略語があればそれを推奨.
- config/id/spec/stas…
Firestore Error Messages
トラブルシューティングとその対処についてまとめ.
INVALID_ARGUMENT: value for xxx is too large to be used in a query
timestampの昇順データに対する where < による終わりからはじめへの検索で発生. おそらく降順データに対する where > でも発生.
クエリ対象のデータか大きすぎる.
DEADLINE_EXCEED: 書き込み制限タイムアウト
google.api_core.exceptions.DeadlineExceeded: 504 The datastore operation timed out, or the data was temporarily unavailable.
1秒間の書き込みは1回くらいにしないとエラーが発生する. ソフトリミットのようで発生は散発的.
- ref. Usage and limits | Firebase Documentation
- Deadline Exceeded error occurs due to Firestore limit of Maximum write rate to a document - 1 per second.
Active Recalls
Firestoreのリファレンスとスナップショットの違いはなんですか?
リファレンスはクラウド上のデータへのパスの参照.
スナップショットはローカルのデータとメタデータを合わせたオブジェクト.
両者の違いはネットワーク通信をするかどうか.
Firestoreデータ更新で updateと set({merge:true})の違いはなんですか?
ドキュメントのフィールドを更新するときのフィールドの有無で挙動が変わる.
フィールドが存在しないとき, updateはエラーするがsetmerge:trueは自動で追加される.