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

IT技术 javascript performance firebase google-cloud-firestore angularfire2
2021-02-17 00:37:09

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

使用 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 性能缓慢问题获取数据

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

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

5个回答

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

与其他一些评论者类似,我也注意到第一个 get 请求的响应较慢(后续请求需要大约 100 毫秒)。对我来说,它没有 30 秒那么糟糕,但是当我有良好的连接性时可能会在 2-3 秒左右,这足以在我的应用程序启动时提供糟糕的用户体验。

Firebase 已经告知他们已经意识到这个“冷启动”问题,并且他们正在努力解决这个问题——不幸的是没有 ETA。我认为这是一个单独的问题,当我的连接性较差时,在 get 请求决定从缓存中读取之前可能需要很长时间(超过 30 秒)。

虽然火力地堡修复所有这些问题,我一直在使用新开工disableNetwork()enableNetwork()方法(在公司的FireStore v0.10.0可用),手动控制火力地堡的在线/离线状态。尽管我在代码中使用它时必须非常小心,因为在某些情况下有一个 Firestore 错误可能会导致崩溃。


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

现在看来性能缓慢的问题已经得到解决。我重新运行了下面描述的测试,现在 Firestore 返回 100 个文档所需的时间似乎一直在 100 毫秒左右。

不确定这是最新 SDK v0.9.2 中的修复还是后端修复(或两者),但我建议每个人都更新他们的 Firebase pod。我的应用程序的响应速度明显更快 - 类似于它在实时数据库上的方式。


我还发现 Firestore 比实时数据库慢得多,尤其是在读取大量文档时。

更新的测试(使用最新的 iOS Firestore SDK v0.9.0):

我在 iOS Swift 中使用 RTDB 和 Firestore 设置了一个测试项目,并对每个项目运行了 100 次顺序读取操作。对于 RTDB,我在 100 个顶级节点中的每一个上测试了observeSingleEvent 和observe 方法。对于 Firestore,我在 TestCol 集合中的 100 个文档中的每个文档中使用了 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)")
        }
    }
}

实时数据库观察方法:

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 时间戳。我取了初始时间戳和最后一个时间戳之间的差异来返回。

结果 - 磁盘持久性禁用:

磁盘持久性已禁用

结果 - 启用磁盘持久性:

启用磁盘持久性

数据结构:

数据结构

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

所以 firestore 更贵也更慢.. 希望 firebase 团队看到这个
2021-04-22 00:37:09
感谢您让我们保持最新状态。我为最新的 Firestore SDK v0.9.0 添加了一些新结果,这可能有助于您的团队隔离问题的根源。我还遇到了快照侦听器的另一个问题:stackoverflow.com/questions/46710371 /...根本原因可能与此主题有关,也可能与此主题无关​​,但如果 Firebase 团队可以查看它,那就太好了. 非常感谢!
2021-04-27 00:37:09
我们还在 Web sdk 上遇到“卡住”查询。挂起 10-20 秒,然后数据到达(在 v4.8.0 上)。
2021-05-02 00:37:09
[Firebaser here] 感谢您抽出宝贵时间提供如此详细的数据,我们一直对此表示感谢。问题不在于系统“变慢”,而是极少数查询卡住或需要大量时间才能返回。我们即将推出一些修复程序,我们相信这些修复程序会改善情况。
2021-05-06 00:37:09
我最近注意到了这一点并向 Firebase 报告。他们知道“冷启动”问题并正在努力解决。与此同时,我正在尝试上面更新中详述的解决方法,并且我已经取得了不同程度的成功。
2021-05-08 00:37:09

更新日期 2018 年 3 月 2 日

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

“您实际上是正确的。经过进一步检查,getDocuments() API 的这种缓慢是 Cloud Firestore 测试版中的已知行为。我们的工程师知道这个标记为“冷启动”的性能问题,但不要担心,因为我们正在做我们尽最大努力提高 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()
}

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

我仍然看到 android 的性能缓慢和大量内存问题。你们打算提供任何更新吗?
2021-04-18 00:37:09
最新版本的 firestore i 仍然会出现此问题。iOS 和使用快照侦听器的效果非常棒。很棒的发现。
2021-04-25 00:37:09
我还发现 FirebaseUI 回收器适配器的性能也很慢,它使用 addSnapshotListener。
2021-04-28 00:37:09
这个“冷启动”问题还存在吗?我对你 3 月份的更新感到有些困惑,提到 Firebase 工程师知道他们将什么标记为“冷启动”问题,因为在你最初的回答中你写道你已经排除了“4. Firestore 冷启动”的可能性问题?
2021-05-09 00:37:09
我也看到了相同的行为,但仅限于 Android。现在我也回到快照。但是如果 get 查询的性能是一致的就好了。
2021-05-10 00:37:09

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

我的解决方案是禁用离线数据持久性。就我而言,它不适合我们 Firestore 数据库的需求——它的大部分数据每天都在更新。

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

就我而言,它导致每次查询都需要大量等待,直到设备缓存了所需的数据 - 因此,从完全相同的集合中返回的项目数量不断增加,查询时间不成比例。这是因为在每个查询中缓存集合花费了相同的时间。

离线数据 - 来自 Cloud Firestore Docs

我执行了一些基准测试来显示来自同一个查询集合的这种效果(启用离线持久性),但使用 .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
    }
}
呵呵 这很有趣。您能否创建一个示例 Xcode 项目来复制它并将其通过电子邮件发送给我?如果是这样,那就是 google dot com 上的 samstern。我很想仔细看看。
2021-04-20 00:37:09
这些测试编号是来自 Web、iOS 还是 Android?
2021-04-25 00:37:09
没想到您在 Firebase 团队中!这个查询的赠品是内存使用量会达到很高的数字(600-700mb ram),我假设我们的集合存储在缓存中。查询将始终挂起,然后在内存逐渐上升并达到相同点(700mb-ish)时完成。禁用持久性后,此效果停止,我们的记忆保持如预期 (100-150mb),同时超快地返回我们的结果。如果您需要更多信息,请询问。
2021-05-02 00:37:09
@SamStern:这已经有一段时间了,但我在 Android 上面临着完全相同的问题。关于什么可能导致这种情况的任何线索?我试图构建一个最小的项目,但该项目没有这个问题!
2021-05-02 00:37:09
基准测试来自 iOS,但我希望在所有平台上都有性能提升 - 取决于您查询的集合的大小
2021-05-07 00:37:09

差不多 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() 不会改变任何内容。

在 javascript 客户端中,它默认关闭:For the web, offline persistence is disabled by default. To enable persistence, call the enablePersistence method但我可以在关闭时确认,我们的初始请求时间从类似的 ~8 秒下降到大约 ~500 毫秒firebase.google.com/docs/firestore/manage-data/启用离线
2021-05-03 00:37:09
正确,在 javascript 中它默认关闭,我上面提到的时间是默认设置。在我们的例子中,我们需要新的和更新的用户配置文件数据,所以使用持久性不是一种选择。
2021-05-08 00:37:09
此问题的任何解决方法?一些指向何处寻找答案的指针将不胜感激。
2021-05-15 00:37:09

好吧,根据我目前在模拟器和真正的安卓手机华为 P8 中使用 nexus 5X 所做的和研究,

当我第一次执行 document.get() 和第一次 storage.getDownloadUrl() 时,Firestore 和 Cloud Storage 都让我头疼

它给我每个请求超过 60 秒的响应。缓慢的响应只发生在真正的安卓手机中。不在模拟器中。另一个奇怪的事情。第一次相遇后,其余请求顺利。

这是我遇到响应缓慢的简单代码。

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