Firestore在获取数据方面性能下降

Oli*_*r P 67 javascript performance firebase angularfire2 google-cloud-firestore

与具有1/10比率的实时数据库相比,我检索存储在文档中的基本数据时,Firestore的性能问题很慢.

使用Firestore,第一次调用平均需要3000毫秒

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

使用实时数据库,第一次调用平均需要300毫秒

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

这是网络控制台的屏幕截图:

Firestore缓慢的性能问题获取数据

我正在使用AngularFire2 v5.0 rc.2运行Javascript SDK v4.50.

有没有人遇到过这个问题?

Sau*_*aul 41

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

与其他一些评论者类似,我也注意到第一个获取请求的响应速度较慢(后续请求需要大约100毫秒).对我而言,它并不像30秒那么糟糕,但是当我有良好的连接时,可能大约2-3秒,这足以在我的应用程序启动时提供糟糕的用户体验.

Firebase已经建议他们知道这个"冷启动"问题并且他们正在为它做长期修复 - 不幸的是没有ETA.我认为这是一个单独的问题,当我连接不良时,在请求决定从缓存中读取之前,可能需要很长时间(超过30秒).

虽然Firebase解决了所有这些问题,但我已经开始使用新的disableNetwork()enableNetwork()方法(在Firestore v0.10.0中提供)来手动控制Firebase的在线/离线状态.虽然我必须非常小心我在我的代码中使用它,因为有一个Firestore错误可能会导致在某些情况下崩溃.


更新: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,我测试了observeSingleEvent并观察了100个顶级节点中每个节点的方法.对于Firestore,我在TestCol集合中的每个文档中使用了getDocument和addSnapshotListener方法.我打开和关闭磁盘持久性测试.请参阅附图,其中显示了每个数据库的数据结构.

我为同一设备上的每个数据库和稳定的wifi网络运行了10次测试.在每次新的运行之前,现有的观察者和听众都被摧毁了.

实时数据库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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

实时数据库观察方法:

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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当方法开始执行时,每种方法基本上以毫秒为单位打印unix时间戳,然后在每次读取操作返回时打印另一个unix时间戳.我取了初始时间戳和最后一个时间戳之间的差异来返回.

结果 - 禁用磁盘持久性:

磁盘持久性已禁用

结果 - 启用磁盘持久性:

启用磁盘持久性

数据结构:

数据结构

当Firestore getDocument/addSnapshotListener方法卡住时,似乎卡住了大约30秒的倍数的持续时间.也许这可以帮助Firebase团队隔离SDK卡在哪里?

  • [Firebaser在这里]感谢您抽出宝贵时间提供如此详细的数据,我们始终对此表示赞赏.问题不在于系统"慢",因为很少有查询被卡住或需要花费大量时间才能返回.我们很快就会有一些修复措施,我们相信会改善这种情况. (6认同)
  • 所以 firestore 更贵也更慢.. 希望 firebase 团队看到这个 (3认同)
  • 我们也在网络sdk上遇到"卡住"的查询.挂起10-20秒,然后数据到达(在v4.8.0上). (3认同)

Ter*_*nce 19

更新日期2018年3月2日

看起来这是一个已知问题,Firestore的工程师正在努力修复.在这个问题上与Firestore工程师进行了几次电子邮件交流和代码共享后,这是他今天的回复.

"你实际上是正确的.进一步检查后,getDocuments()API的这种缓慢是Cloud Firestore beta中的已知行为.我们的工程师意识到这个性能问题标记为"冷启动",但不要担心我们正在做我们最好的提高Firestore查询性能.

我们已经在进行长期修复,但目前我无法分享任何时间表或细节.虽然Firestore仍处于测试阶段,但预计会有更多改进."

所以希望这很快就会被淘汰出局.


使用Swift/iOS

在处理了这个约3天之后,似乎问题肯定是get()即.getDocuments和.getDocument.我认为的事情导致极端但间歇性的延迟,但似乎并非如此:

  1. 没有那么好的网络连接
  2. 通过循环遍历.getDocument()重复调用
  3. 链接get()调用
  4. Firestore冷启动
  5. 获取多个文档(获取1个小文档导致20秒延迟)
  6. 缓存(我禁用了离线持久性,但这没有做任何事.)

我能够将所有这些都排除在外,因为我注意到这个问题并没有发生在我正在制作的每个Firestore数据库调用中.仅使用get()进行检索.对于踢,我用.addSnapshotListener替换.getDocument来检索我的数据和瞧.每次包括第一次通话即时检索.没有感冒开始.到目前为止,.addSnapshotListener没有任何问题,只有getDocument(s).

现在,我只是简单地删除.getDocument(),其中时间至关重要,并用.addSnapshotListener替换它然后使用

for document in querySnapshot!.documents{
// do some magical unicorn stuff here with my document.data()
}
Run Code Online (Sandbox Code Playgroud)

...为了继续前进,直到Firestore解决了这个问题.


Hen*_*ies 7

直到今天早上我才遇到这个问题.我通过iOS/Swift查询Firestore大约需要20秒才能完成一个简单的完全索引查询 - 返回1个项目的非比例查询时间 - 一直到3,000.

我的解决方案是禁用脱机数据持久性.就我而言,它不适合我们的Firestore数据库的需求 - 每天都有大部分数据更新.

iOS和Android用户默认启用此选项,而Web用户默认禁用此选项.如果您查询大量文档,它会使Firestore看起来非常慢.基本上它会缓存您查询的任何数据的副本(以及您查询的任何集合 - 我相信它会缓存所有文档),这会导致内存使用率过高.

在我的情况下,它导致了每个查询的巨大等待,直到设备缓存了所需的数据 - 因此,从完全相同的集合返回的越来越多的项目的非比例查询时间.这是因为在每个查询中缓存集合花费的时间相同.

离线数据 - 来自Cloud Firestore文档

我执行了一些基准测试,以便从同一个查询集合中显示此效果(启用了脱机持久性),但使用.limit参数返回的项目数量不同:

基准 现在返回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
    }
}
Run Code Online (Sandbox Code Playgroud)


JPJ*_*JPJ 7

差不多 3 年后,firestore 已经完全退出测试版,我可以确认这个可怕的问题仍然存在;-(

在我们的移动应用程序中,我们使用 javascript / node.js firebase 客户端。在经过大量测试以找出为什么我们的应用程序的启动时间约为 10 秒后,我们确定了将 70% 的时间归因于……嗯,firebase 和 firestore 的性能和冷启动问题:

  • firebase.auth().onAuthStateChanged() 大约触发。1.5 - 2 秒后,已经很糟糕了。
  • 如果它返回一个用户,我们使用它的 ID 从 firestore 获取用户文档。这是对 firestore 的第一次调用,相应的 get() 需要 4 - 5 秒。相同或其他文档的后续 get() 需要大约。500 毫秒。

因此,用户初始化总共需要 6 - 7 秒,这是完全不可接受的。我们对此无能为力。我们无法测试禁用持久性,因为在 javascript 客户端中没有这样的选项,默认情况下始终启用持久性,因此不调用 enablePersistence() 不会改变任何内容。