web-dev-qa-db-ja.com

Firestoreのデータ取得時のパフォーマンスの問題

Firestoreでは、1/10の比率のリアルタイムデータベースと比較して、ドキュメントに保存されている基本データを取得する際にパフォーマンスの問題が発生します。

Firestoreを使用すると、最初の呼び出しで平均3000ミリ秒かかります

 this.db.collection(‘testCol’)
   .doc(‘testDoc’)
   .valueChanges().forEach((data) => {
     console.log(data);//3000 ms later
 });

リアルタイムデータベースを使用すると、最初の呼び出しで平均300ミリ秒かかります

 this.db.database.ref(‘/test’).once(‘value’).then(data => {
     console.log(data); //300ms later
 });

これは、ネットワークコンソールのスクリーンショットです。

Firestore slow performance issue get Data

AngularFire2 v5.0 rc.2でJavascript SDK v4.50を実行しています。

誰もこの問題を経験しましたか?

60
Olivier P

更新:2018年2月12日-iOS Firestore SDK v0.10.0

他のいくつかのコメンターと同様に、最初のgetリクエストの応答が遅くなっていることにも気付きました(後続のリクエストには約100ミリ秒かかります)。私にとっては30秒ほど悪くはありませんが、接続性が良い場合は2〜3秒程度で、アプリの起動時にユーザーエクスペリエンスが低下するのに十分です。

Firebaseは、この「コールドスタート」問題を認識しており、それに対する長期的な修正に取り組んでいるとアドバイスしています。残念ながらETAはありません。接続が不十分な場合、取得リクエストがキャッシュからの読み取りを決定するまでに時間がかかる(30秒以上)ことは別の問題だと思います。

Firebaseはこれらすべての問題を修正しますが、Firebaseのオンライン/オフライン状態を手動で制御するために、新しいdisableNetwork()およびenableNetwork()メソッド(Firestore v0.10.0で利用可能)の使用を開始しました。特定のシナリオでクラッシュを引き起こす可能性のあるFirestoreのバグがあるため、コードのどこで使用するかveryに注意する必要がありました。


更新:2017年11月15日-iOS Firestore SDK v0.9.2

遅いパフォーマンスの問題が修正されたようです。以下で説明するテストを再実行しましたが、Firestoreが100個のドキュメントを返すのにかかる時間は、一貫して約100ミリ秒であるようです。

これが最新のSDK v0.9.2の修正であったのか、それともバックエンドの修正(またはその両方)であったのかはわかりませんが、Firebaseポッドを更新することをお勧めします。私のアプリの応答性は著しく向上しています-Realtime DBでの方法と同様です。


また、FirestoreがRealtime DBよりもはるかに遅いこと、特に大量のドキュメントを読み取る場合も発見しました。

更新されたテスト(最新のiOS Firestore SDK v0.9.0を使用):

RTDBとFirestoreの両方を使用してiOS Swiftにテストプロジェクトを設定し、それぞれに対して100の順次読み取り操作を実行しました。 RTDBの場合、100個のトップレベルノードのそれぞれでobserveSingleEventとobserveメソッドをテストしました。 Firestoreの場合、TestColコレクションの100個のドキュメントのそれぞれでgetDocumentメソッドとaddSnapshotListenerメソッドを使用しました。ディスクの永続性をオンおよびオフにしてテストを実行しました。各データベースのデータ構造を示す添付画像を参照してください。

同じデバイスと安定したWi-Fiネットワーク上の各データベースに対してテストを10回実行しました。既存のオブザーバーとリスナーは、新しい実行のたびに破棄されました。

リアルタイムDB observeSingleEventメソッド:

func rtdbObserveSingle() {

    let start = UInt64(floor(Date().timeIntervalSince1970 * 1000))
    print("Started reading from RTDB at: \(start)")

    for i in 1...100 {
        Database.database().reference().child(String(i)).observeSingleEvent(of: .value) { snapshot in
            let time = UInt64(floor(Date().timeIntervalSince1970 * 1000))
            let data = snapshot.value as? [String: String] ?? [:]
            print("Data: \(data). Returned at: \(time)")
        }
    }
}

リアルタイムDB監視方法:

func rtdbObserve() {

    let start = UInt64(floor(Date().timeIntervalSince1970 * 1000))
    print("Started reading from RTDB at: \(start)")

    for i in 1...100 {
        Database.database().reference().child(String(i)).observe(.value) { snapshot in
            let time = UInt64(floor(Date().timeIntervalSince1970 * 1000))
            let data = snapshot.value as? [String: String] ?? [:]
            print("Data: \(data). Returned at: \(time)")
        }
    }
}

Firestore getDocumentメソッド:

func fsGetDocument() {

    let start = UInt64(floor(Date().timeIntervalSince1970 * 1000))
    print("Started reading from FS at: \(start)")

    for i in 1...100 {
        Firestore.firestore().collection("TestCol").document(String(i)).getDocument() { document, error in

            let time = UInt64(floor(Date().timeIntervalSince1970 * 1000))
            guard let document = document, document.exists && error == nil else {
                print("Error: \(error?.localizedDescription ?? "nil"). Returned at: \(time)")
                return
            }
            let data = document.data() as? [String: String] ?? [:]
            print("Data: \(data). Returned at: \(time)")
        }
    }
}

Firestore addSnapshotListenerメソッド:

func fsAddSnapshotListener() {

    let start = UInt64(floor(Date().timeIntervalSince1970 * 1000))
    print("Started reading from FS at: \(start)")

    for i in 1...100 {
        Firestore.firestore().collection("TestCol").document(String(i)).addSnapshotListener() { document, error in

            let time = UInt64(floor(Date().timeIntervalSince1970 * 1000))
            guard let document = document, document.exists && error == nil else {
                print("Error: \(error?.localizedDescription ?? "nil"). Returned at: \(time)")
                return
            }
            let data = document.data() as? [String: String] ?? [:]
            print("Data: \(data). Returned at: \(time)")
        }
    }
}

基本的に、各メソッドは、メソッドの実行が開始されると、ミリ秒単位でUNIXタイムスタンプを出力し、各読み取り操作が戻ると別のUNIXタイムスタンプを出力します。最初のタイムスタンプと最後のタイムスタンプの差を取得して返しました。

結果-ディスクの永続性が無効になりました:

Disk persistence disabled

結果-ディスクの永続性が有効になりました:

Disk persistence enabled

データ構造:

Data Structure

FirestoreのgetDocument/addSnapshotListenerメソッドがスタックすると、およそ30秒の倍数の期間スタックしているように見えます。これはおそらく、FirebaseチームがSDKのどこでスタックしているのかを特定するのに役立つでしょうか?

35
Saul

更新日2018年3月2日

これは既知の問題であり、Firestoreのエンジニアは修正に取り組んでいるようです。この問題についてFirestoreのエンジニアと数回メールを交換し、コードを共有した後、これが今日の彼の対応でした。

「あなたは実際に正しい。さらに確認すると、getDocuments()APIのこの速度低下はCloud Firestoreベータ版の既知の動作です。エンジニアは「コールドスタート」とタグ付けされたこのパフォーマンスの問題を認識していますが、 Firestoreクエリのパフォーマンスを改善するために最善を尽くします。

私たちはすでに長期的な修正に取り組んでいますが、現時点ではタイムラインや詳細を共有することはできません。 Firestoreはまだベータ版ですが、今後さらに改善されることを期待してください。」

うまくいけば、これはすぐにノックアウトされます。


Swift/iOSを使用

これを約3日間処理した後、問題は間違いなくget()、つまり.getDocumentsと.getDocumentであるようです。私thoughtが極端な断続的な遅延を引き起こしていましたが、そうではないようです:

  1. それほど優れたネットワーク接続性
  2. .getDocument()のループを介した繰り返し呼び出し
  3. Get()呼び出しの連鎖
  4. ファイヤーストアコールドスタート
  5. 複数のドキュメントを取得しています(1つの小さなドキュメントを取得すると20秒の遅延が発生しました)
  6. キャッシュ(オフライン永続性を無効にしましたが、これは何もしませんでした。)

この問題は、私が行っていたすべてのFirestoreデータベース呼び出しで発生するわけではないことに気付いたので、これらすべてを除外することができました。 get()を使用した取得のみ。キックについては、データと出来上がりを取得するために.getDocumentを.addSnapshotListenerに置き換えました。最初の呼び出しを含む毎回のインスタント検索。コールドスタートはありません。これまでのところ、.addSnapshotListenerに関する問題はなく、getDocument(s)のみです。

とりあえず、時間を重視する.getDocument()をドロップし、.addSnapshotListenerに置き換えてから

for document in querySnapshot!.documents{
// do some magical Unicorn stuff here with my document.data()
}

...これがFirestoreによって解決されるまで動き続けるため。

14
Terrence

私は今朝までこの問題を抱えていました。 iOS/Swiftを介したFirestoreクエリは、返される1アイテムの非比例クエリ時間-最大3,000までの単純で完全なインデックス付きクエリを完了するのに約20秒かかります。

私の解決策は、オフラインデータの永続性を無効にすることでした。私の場合、Firestoreデータベースのニーズには適合していませんでした。Firestoreデータベースでは、データの大部分が毎日更新されています。

iOSおよびAndroidユーザーはこのオプションをデフォルトで有効にしていますが、Webユーザーはデフォルトで無効にしています。膨大なドキュメントのコレクションを照会している場合、Firestoreが非常に遅く見えるようになります。基本的には、クエリを実行しているデータ(およびクエリを実行しているコレクション-内のすべてのドキュメントをキャッシュしている)のコピーをキャッシュするため、メモリ使用量が高くなります。

私の場合、デバイスが必要なデータをキャッシュするまで、すべてのクエリが非常に待機していました。したがって、まったく同じコレクションから返されるアイテムの数が増えると、比例しないクエリ時間が発生します。これは、各クエリでコレクションをキャッシュするのに同じ時間がかかったためです。

オフラインデータ-Cloud Firestoreドキュメントから

クエリされた同じコレクションからこの効果を表示するためにいくつかのベンチマークを実行しました(オフライン永続性を有効にした)。ただし、.limitパラメーターを使用して返されるアイテムの量は異なります。

Benchmarks 100個のアイテムが返された(オフライン永続性が無効になった)状態で、クエリが完了するまでに1秒もかかりません。

私のFirestoreクエリコードは次のとおりです。

let db = Firestore.firestore()
self.date = Date()
let ref = db.collection("collection").whereField("Int", isEqualTo: SomeInt).order(by: "AnotherInt", descending: true).limit(to: 100)
ref.getDocuments() { (querySnapshot, err) in
    if let err = err {
        print("Error getting documents: \(err)")
    } else {
        for document in querySnapshot!.documents {
            let data = document.data()
            //Do things
        }
        print("QUERY DONE")
        let currentTime = Date()
        let components = Calendar.current.dateComponents([.second], from: self.date, to: currentTime)
        let seconds = components.second!
        print("Elapsed time for Firestore query -> \(seconds)s")
        // Benchmark result
    }
}
6
Hendies

ネクサス5Xをエミュレータと実際のAndroid電話Huawei P8で使用して、私が現在やっていることや研究から、

FirestoreとCloud Storageはどちらも、最初のdocument.get()と最初のstorage.getDownloadUrl()を行うときに応答が遅くなるという頭痛の種です。

リクエストごとに60秒以上の応答があります。遅い応答は、実際のAndroid電話でのみ発生します。エミュレータではありません。もう一つの奇妙なこと。最初の出会いの後、残りの要求はスムーズです。

応答が遅いことに出会う簡単なコードを次に示します。

var dbuserref = dbFireStore.collection('user').where('email','==',email);
const querySnapshot = await dbuserref.get();

var url = await defaultStorage.ref(document.data().image_path).getDownloadURL();

同じことを研究しているリンクも見つけました。 https://reformatcode.com/code/Android/firestore-document-get-performance

1
Kyo Kurosagi