Firestore 获取数据时性能缓慢的问题
Posted
技术标签:
【中文标题】Firestore 获取数据时性能缓慢的问题【英文标题】:Firestore slow performance issue on getting data 【发布时间】:2018-03-24 21:08:08 【问题描述】:与 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
);
这是网络控制台的截图:
我正在使用 AngularFire2 v5.0 rc.2 运行 javascript SDK v4.50。
有人遇到过这个问题吗?
【问题讨论】:
如果您进行第二次调用(对不同的文档/集合),您会看到什么性能?如果您不使用 angularfire,您会看到同样的问题吗? 我也有类似的经历。第一次通话有点慢,有时5-10秒。我正在制作一个聊天应用程序 - 第一个消息需要一段时间才能传递,但随后的消息几乎是即时的。 Firestore 仍是测试版,他们可能仍在整理怪癖。 这里有类似的经历。首先 onSnapShot 花费了非常多的时间——对于一些导致我们的应用程序无法使用的用户来说,最多需要 2 分钟 同样的问题,非常令人沮丧。有些人报告说写操作会释放“挂起”的查询。 同样的问题,使用简单的 collection.get(document) 最多 1.5 分钟 【参考方案1】:更新:2018 年 2 月 12 日 - iOS Firestore SDK v0.10.0
与其他一些评论者类似,我也注意到第一次获取请求的响应较慢(后续请求大约需要 100 毫秒)。对我来说,它没有 30 秒那么糟糕,但当我有良好的连接性时可能会在 2-3 秒左右,这足以在我的应用启动时提供糟糕的用户体验。
Firebase 已告知他们已经意识到这个“冷启动”问题,并且他们正在努力解决这个问题 - 遗憾的是没有 ETA。我认为这是一个单独的问题,当我的连接性较差时,get 请求决定从缓存中读取可能需要很长时间(超过 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):
我使用 RTDB 和 Firestore 在 ios Swift 中建立了一个测试项目,并在每个项目上运行了 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 团队看到这一点 [Firebaser here] 感谢您抽出宝贵时间提供如此详细的数据,我们一直很感激。问题不在于系统“较慢”,而是极少数查询卡住或需要大量时间才能返回。我们即将推出一些修复程序,我们相信这些修复程序会改善这种情况。 感谢您为我们提供最新信息。我为最新的 Firestore SDK v0.9.0 添加了一些新结果,这可能有助于您的团队找出问题的根源。我还遇到了快照侦听器的另一个问题:***.com/questions/46710371/… 根本原因可能与此主题相关,也可能不相关,但如果 Firebase 团队可以查看它,那就太好了。非常感谢! 我们在 web sdk 上也遇到了“卡住”的查询。挂起 10-20 秒,然后数据到达(在 v4.8.0 上)。 我最近注意到了这一点,并向 Firebase 报告。他们知道“冷启动”问题并正在努力修复。与此同时,我正在尝试上面更新中详述的解决方法,并且我已经取得了不同程度的成功。【参考方案2】:更新日期 2018 年 3 月 2 日
这似乎是一个已知问题,Firestore 的工程师正在努力修复。在与 Firestore 工程师就此问题进行了几次电子邮件交流和代码共享后,这是他今天的回复。
“您实际上是正确的。经过进一步检查,getDocuments() API 的这种缓慢是 Cloud Firestore 测试版中的已知行为。我们的工程师知道这个性能问题标记为“冷启动”,但不要担心我们正在尽最大努力提高 Firestore 查询性能。
我们已经在进行长期修复,但目前我无法分享任何时间表或细节。虽然 Firestore 仍处于测试阶段,但预计会有更多改进。”
所以希望这很快就会被淘汰。
使用 Swift / iOS
在处理了大约 3 天之后,问题似乎肯定是 get(),即 .getDocuments 和 .getDocument。我认为的事情导致了极端但间歇性的延迟,但似乎并非如此:
-
网络连接不太好
通过循环 .getDocument() 重复调用
链接 get() 调用
Firestore 冷启动
获取多个文档(获取 1 个小文档导致 20 秒延迟)
缓存(我禁用了离线持久性,但这没有任何作用。)
我能够排除所有这些问题,因为我注意到这个问题并没有发生在我进行的每个 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查询的性能是一致的就好了。 我还发现使用 addSnapshotListener 的 FirebaseUI 回收器适配器的性能也很慢。 这个“冷启动”问题还存在吗?我对您 3 月份的更新提到 Firebase 工程师知道他们将其标记为“冷启动”问题感到有些困惑,因为在您的原始答案中,您写道您已经排除了“4. Firestore 冷启动”是问题? 我仍然看到 android 的性能缓慢和大量内存问题。你们打算提供有关此修复的任何更新 最新版本的 firestore i 仍然会出现此问题。 iOS 和使用快照监听器就像一个魅力。很棒的发现。【参考方案3】:直到今天早上我都遇到了这个问题。我通过 iOS/Swift 进行的 Firestore 查询大约需要 20 秒才能完成一个简单的、完全索引的查询 - 返回的 1 个项目的查询时间不成比例 - 一直到 3,000 个。
我的解决方案是禁用离线数据持久性。就我而言,它不适合我们的 Firestore 数据库的需求——它的大部分数据每天都会更新。
iOS 和 Android 用户默认启用此选项,而 Web 用户默认禁用此选项。如果您要查询大量文档,它会使 Firestore 看起来异常缓慢。基本上它会缓存您正在查询的任何数据的副本(以及您正在查询的任何集合 - 我相信它会缓存其中的所有文档),这可能导致高内存使用。
在我的情况下,它会导致每次查询都需要等待很长时间,直到设备缓存了所需的数据 - 因此,从完全相同的集合返回越来越多的项目的查询时间不成比例。这是因为在每个查询中缓存集合所花费的时间相同。
Offline Data - from the 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
【讨论】:
这些测试编号是来自 Web、iOS 还是 Android? 基准来自 iOS,但我希望在所有平台上都有性能提升 - 取决于您查询的集合的大小 不知道您是 Firebase 团队的一员!这个查询的好处是内存使用量会达到很高的数字(600-700mb ram),我假设我们的集合存储在缓存中。查询将始终挂起,然后在内存逐渐上升然后达到同一点(700mb-ish)时完成。禁用持久性后,这种效果就停止了,我们的内存仍如预期(100-150mb),同时以超快的速度返回我们的结果。如果您需要更多信息,请询问。 嗯,这很有趣。您能否创建一个示例 Xcode 项目来复制它并将其通过电子邮件发送给我?如果是这样,那就是 google dot com 的 samstern。我很想仔细看看。 @SamStern :这已经有一段时间了,但我在 Android 上遇到了完全相同的问题。关于可能导致这种情况的任何线索?我尝试构建一个最小的项目,但该项目没有这个问题!【参考方案4】:差不多 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/enable-offline
正确,在javascript中它默认是关闭的,我上面提到的时间是默认设置的。在我们的例子中,我们需要新鲜和更新的用户配置文件数据,所以使用持久性不是一种选择。【参考方案5】:
嗯,根据我目前正在做的和研究,在模拟器和真正的安卓手机华为 P8 中使用 nexus 5X,
Firestore 和 Cloud Storage 都让我头疼的是响应慢 当我做第一个 document.get() 和第一个 storage.getDownloadUrl()
它给我每个请求超过 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
【讨论】:
以上是关于Firestore 获取数据时性能缓慢的问题的主要内容,如果未能解决你的问题,请参考以下文章
Firestore:仅当有更新时才从服务器获取数据,否则从缓存中获取
从firestore动态获取数据时RenderItem不渲染[重复]
从 StreamBuilder BloC firestore Flutter 获取数据时出错