Cloud Firestoreは進化したFirebase Realtime Database

  • 60
    いいね
  • 1
    コメント

Cloud Firestoreファーストインプレッション

リリース発表をついに来たか!って思いました。そしてFirestoreとFirebase Realtime Databaseがどう違うのかを読んで、大きく進化したなぁって思いました。そして実際に使ってみて、これはもはや別物って思いました。

FirestoreとFirebase Realtime Databaseの違いについてはすでにこちらでまとめられていたのでこちらをご覧ください。
https://qiita.com/Yatima/items/54ea22d0cea1acc6cbcb

Cloud Firestoreのすごいところ

FirestoreにはBoringSSLが使われている

BordingSSLはGoogleによるOpenSSLのfork。2014年にForkを発表して、すでにAndroidなどでは使われていたようですが、本格的にGoogleはBordingSSLを導入していくんですねぇ。素晴らしい。

gRPCも使われている

gRPC は、RPC (Remote Procedure Call) を実現するためにGoogleが開発したプロトコルの1つです。Protocol Buffers を使ってデータをシリアライズし、高速な通信を実現できる点が特長です。

https://qiita.com/oohira/items/63b5ccb2bf1a913659d6

保存できるデータ型が増えた。

Cloud Firestore では、ブール値、数値、文字列、地理的位置、バイナリ blob、タイムスタンプなど、さまざまな値のデータ型がサポートされています。

地理的位置を保存できると言うことは!?おそらくここのソリューションも今後提供して来ることが予想されます。

https://firebase.google.com/docs/firestore/manage-data/data-types?authuser=0

SubCollectionは分離されている

Firebase Realtime Databaseが単純なツリー構造だったのに対しFirestoreのデータ構造はCollection, Document, SubCollectionがあります。

https://firebase.google.com/docs/firestore/data-model?authuser=0#hierarchical-data

Firebase Realtime Databaseでも同じような構造をツリー構造で構築することは可能です。しかし、SubCollectionには決定的な違いがあります。Firebase Realtime Databaseでは上位のツリー構造の上位のノードを削除するとそれに付随する下位のデータは全て削除されました。Firestoreでは違います。

ドキュメントを削除しても、そのサブコレクションは削除されません。

サブコレクションが関連付けられているドキュメントを削除しても、そのサブコレクションは削除されません。その後もサブコレクションには、リファレンスによるアクセスが可能です。たとえば、db.collection('coll').doc('doc') によって参照されるドキュメントは存在しなくなったにもかかわらず、db.collection('coll').doc('doc').collection('subcoll').doc('subdoc') によって参照されるドキュメントは存在する場合があります。ドキュメントを削除するときにサブコレクション内のドキュメントも削除する場合は、コレクションを削除するに示すように、手動で削除する必要があります。

つまり親ノードのデータ量が重くならない

FirestoreのSubCollectionはではないく参照です、Firebase Realtime Databaseでは上位ノードでデータを取得すると下位のデータを全て取得していたためデータが非常に重くなっていました。
Firestoreではこれが解決されています。ちなみにFirebase Realtime Databaseでも同じことはできます。

Firebase Model FrameworkのSaladaではRelationクラスでこれを実現しています。
https://github.com/1amageek/Salada/blob/master/Salada/Relation.swift

バッチ処理は強力な機能

Firebase Realtime DatabaseにもupdateValuesと言って複数のデータを同時に更新するメソッドは準備されていました。Firestoreではそれがさらに使いやすくなっています。

// Get new write batch
let batch = db.batch()

// Set the value of 'NYC'
let nycRef = db.collection("cities").document("NYC")
batch.setData([:], forDocument: nycRef)

// Update the population of 'SF'
let sfRef = db.collection("cities").document("SF")
batch.updateData(["population": 1000000 ], forDocument: sfRef)

// Delete the city 'LA'
let laRef = db.collection("cities").document("LA")
batch.deleteDocument(laRef)

// Commit the batch
batch.commit() { err in
    if let err = err {
        print("Error writing batch \(err)")
    } else {
        print("Batch write succeeded.")
    }
}

クエリはRealmのような使い心地

定義したクエリの参照を監視し続けることができます。
クエリの使い心地よりも注目すべきはこのプロパティhasPendingWrites
いつ使うかと言うとチャットです。

例えばチャットでメッセージを送信した瞬間を想像してください。
Firebase Realtime Databaseではバックエンドのイベントをオブザーブしていたので、データがバックエンドに保存された後UIが更新されることになり若干の遅れを体感していたはずです。
しかし、Firestoreでは、バックエンドに保存される前であってもクライアントから書き込みがあったことを取得できるのでその遅れを感じなくてよくなります。

db.collection("cities").document("SF")
    .addSnapshotListener { documentSnapshot, error in
        guard let document = documentSnapshot else {
            print("Error fetching document: \(error!)")
            return
        }
        let source = document.metadata.hasPendingWrites ? "Local" : "Server"
        print("\(source) data: \(document.data())")
    }

// After 2 seconds, make an update so our listener will fire again.
let deadlineTime = DispatchTime.now() + .seconds(2)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
    self.db.collection("cities").document("SF").updateData([
        "population": 1000000
        ])
}

// RESULT:
// Server data: ["state": CA, "name": San Francisco, "population": 999999]

// Local data: ["state": CA, "name": San Francisco, "population": 1000000]
// Server data: ["state": CA, "name": San Francisco, "population": 1000000]

完了だけを監視することもできるようです。

書き込みが完了したことだけを知りたい場合、hasPendingWrites を使用する代わりに、完了コールバックをリッスンすることができます。

やっぱりFirebaseのQueryは。。

Firebase Realtime Databaseよりは表現力は格段に上がっていますが、これだけのためにFirestoreを選択する理由にはならないと思います。

https://firebase.google.com/docs/firestore/query-data/order-limit-data?authuser=0

Realtimeに制限ができた

Cloud Firestore では、1 つのドキュメントを 1 秒間に約 2 回しか更新できません。
そんなに強烈な制限ではないですが、時間的な制限があるようです。

データ数の取得は今まで通り

Collectionに入っているデータ数を取得したいときは次のようにする必要があります。

        let db = Firestore.firestore()
        let citiesRef = db.collection("cities")

        citiesRef.getDocuments { (snapshot, error) in
            print(snapshot?.count)  // データ数の取得
        }

例えばフォロワーカウントをとりたい場合を想像してください。カウントなので数値だけが取れればいいはずですが、Firebaseでは全てのデータを取得してからでないと取得できません。
また、カウントを効率的に取得するためにはデータを保存したタイミングで別に数値型のプロパティを用意しトランザクションをしながらインクリメントしていくしかありません。

Firebase SDKにFeature Requestを送っているので、ここでいいねを押してもらえるとプライオリティが上がるかも。

https://github.com/firebase/firebase-ios-sdk/issues/257

ちなみにSaladaではRelationにインクリメントする機能を持たせているため、countってするだけで取得できるようにしています。

https://github.com/1amageek/Salada/blob/master/Salada/Relation.swift#L118

まだSaladaを使う理由はあります。

logo.png

SaladaはSwiftで作られたFirebase Model Frameworkです。
https://github.com/1amageek/Salada

  • Firebase Realtime Databaseの方が早い
  • モデルを全て面倒見てくれる
  • Firebase Storageとの連携
  • RelationはSubCollectionと同じ機能
  • QueryはないがElasticSearchと連携する

でもFirestoreいい感じだから、Saladaをアップデートします。