Firestore获取数据时性能下降的问题


78

与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。

有没有人遇到这个问题?


如果您再次拨打电话(调用另一个文档/集合),会看到什么效果?如果不使用angularfire,是否会遇到相同的问题?
山姆·斯特恩

我也有类似的经历。首次通话有点慢,有时会5-10秒。我正在制作一个聊天应用程序-第一个msg需要花一些时间才能发布,但随后的消息几乎是即时的。公司的FireStore仍然测试,他们很可能仍然整理怪癖。
xenetics

1
类似的经验在这里。首先onSnapShot花费的时间太长了-对于某些用户而言,长达2分钟的时间使我们的应用
无法使用

同样的问题,相当令人沮丧。一些报告说写会释放“挂起”查询。
DarkNeuron

同样的问题,最多用一个简单的collection.get 1.5分钟(文件)
aMarCruz

Answers:


44

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

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

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 Pod。我的应用程序响应速度明显更快-类似于Realtime DB上的方式。


我还发现Firestore比Realtime DB慢得多,尤其是在读取大量文档时。

更新了测试(使用最新的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中卡住的位置?


2
因此,消防站更昂贵,速度也更慢。.希望消防队看到这一点
faruk

6
[这里的Firebaser]感谢您抽出宝贵的时间提供此类详细数据,我们对此始终表示感谢。问题不在于系统“速度较慢”,而是极少数查询被卡住或需要大量时间才能返回。我们即将推出一些修复程序,我们相信这将改善这种情况。
山姆·斯特恩

1
感谢您与我们保持联系。我为最新的Firestore SDK v0.9.0添加了一些新结果,这可能会帮助您的团队找出问题的根源。我还遇到了快照侦听器的另一个问题:stackoverflow.com/questions/46710371/…根本原因可能与该主题无关,但如果Firebase团队能够研究一下,那就太好了。非常感谢!
扫罗(Saul)

3
我们还在网络SDK上遇到“卡住”查询。挂起10到20秒,然后数据到达(在v4.8.0上)。
DarkNeuron

1
我最近注意到了这一点,并向Firebase报告。他们已经意识到“冷启动”问题,并且正在研究解决方法。同时,我正在尝试上述更新中详述的解决方法,并且在此方面取得了不同程度的成功。
Saul

20

更新日期2018年3月2日

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

“您实际上是正确的。经过进一步检查,getDocuments()API的这种缓慢是Cloud Firestore beta中的一种已知行为。我们的工程师意识到此性能问题被标记为“冷启动”,但是请不要担心,因为我们正在这样做我们将尽最大努力改善Firestore查询性能。

我们已经在进行长期修复,但目前我无法分享任何时间表或细节。在Firestore仍处于测试阶段时,希望还会有更多改进。”

因此希望这会很快被淘汰。


使用Swift / iOS

经过大约三天的处理,看来问题绝对是get(),即.getDocuments和.getDocument。我认为是造成极端但间歇性延迟的原因,但事实并非如此:

  1. 不太好的网络连接
  2. 通过遍历.getDocument()的重复调用
  3. 链接get()调用
  4. 消防站冷启动
  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中。现在,我也要回到快照。但是,如果get查询的性能一致,那就很好。
sowdri

我还发现FirebaseUI回收器适配器使用addSnapshotListener导致性能下降。
杰夫·帕吉特

此“冷启动”问题是否仍然存在?我对您从3月开始的更新感到有些困惑,因为提到Firebase工程师知道他们将其标记为“冷启动”问题,因为您在原始答案中写道,您已经排除了“ 4. Firestore冷启动”是问题?
trollkotze

我仍然看到android的性能下降和大量内存问题。你们打算提供此修复程序的任何更新
吗?

最新版本的Firestore i仍然会发生此问题。iOS和使用快照侦听器的工作原理很吸引人。很棒的发现。
johndoe

8

直到今天早上,我一直遇到这个问题。我通过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
    }
}

这些测试编号来自Web,iOS还是Android?
山姆·斯特恩

基准是从iOS上,但我希望在所有平台上的性能提升-这取决于你的查询集合的大小
Hendies

没意识到您在Firebase团队中!此查询的要点是,内存使用量会激增至很高的数量(600-700mb内存),我假设我们的集合存储在缓存中。查询将始终挂起,然后在内存逐渐增加然后到达同一点(700mb-ish)时完成。禁用持久性后,此效果停止了,并且我们的记忆保持了预期的状态(100-150mb),同时以超快的速度返回了结果。如果您需要更多信息,请询问。
Hendies

嗯,这很有趣。您能否创建一个示例Xcode项目来复制它并通过电子邮件发送给我?如果是这样,那是在Google dot com的麻烦。我想仔细看看。
山姆·斯特恩

@SamStern:已经有一段时间了,但是我在Android上面临着完全相同的问题。关于什么可能导致此的任何线索?我尝试构建一个最小的项目,但是该项目没有该问题!
rednuht

6

大约3年后,firestore的测试版远远超出标准,我可以确认这个可怕的问题仍然存在;-(

在我们的移动应用程序上,我们使用javascript / node.js firebase客户端。经过大量测试以找出我们应用程序的启动时间为何在10秒左右的原因,我们确定了将70%的时间归因于...好吧,这归因于Firebase和Firestore的性能以及冷启动问题:

  • firebase.auth()。onAuthStateChanged()触发大约 1.5-2sec之后,已经相当糟糕了。
  • 如果返回用户,则使用其ID从Firestore获取用户文档。这是对firestore的首次调用,相应的get()耗时4-5秒。随后的相同或其他文档的get()大约花费。500毫秒。

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


任何解决此问题的方法?一些指向哪里寻找答案的指针将不胜感激。
bjornl

在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/启用离线

正确,在javascript中它默认是关闭的,我上面提到的时间是默认设置。在我们的情况下,我们需要新的和更新的用户配置文件数据,因此使用持久性不是一种选择。
JPJ

1

好吧,从我目前正在做的事情和通过在模拟器和真实的Android手机Huawei P8中使用nexus 5X进行研究,

当我执行第一个document.get()和第一个storage.getDownloadUrl()时,Firestore和Cloud Storage都让我感到反应缓慢

每个请求给我的响应时间超过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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.