Firebase RTDB:使用分页读取数据需要大量时间

问题描述 投票:0回答:1

我需要从 RTDB 获取大量数据(10MB)。所有数据存储到

stations
节点。当我在单个请求中获取所有数据时,有时我的 Android 应用程序会因 OutOfMemory 错误而崩溃。所以我需要使用分页阅读,但我有一个问题。

在我的例子中,从

stations

 读取完整数据的请求大约需要 10-12 秒。但是,当我发出多个请求(分页)读取 2MB 的页面时,每个请求也需要大约 10 秒。结果,获取 
stations
 数据的总时间从 10 秒(单次读取)增加到 50 秒(分页读取)。我可以让分页工作更快吗?谢谢。

Timeline structure

private fun fetchDayStationsPaged(dayTag: String, lastNodeId: String? = null, stations: MutableList<StationCloud> = mutableListOf(), callback: (data: List<StationCloud>, errorMessage: LoadError?) -> Unit){ val path = String.format(TimelineManager.KEY_TIMELINE_STATIONS, dayTag) val query = if (lastNodeId == null) database .getReference(path) .orderByKey() .limitToFirst(2000) else database .getReference(path) .orderByKey() .startAfter(lastNodeId) .limitToFirst(2000) Timber.d("loader recursion stations $dayTag/${stations.size}") fetchDayStations(query) { data, errorMessage -> if (errorMessage == LoadError.NotExist) callback(stations, null) else if (errorMessage != null) callback(emptyList(), errorMessage) else { stations.addAll(data) fetchDayStationsPaged(dayTag, data.last().nodeId, stations, callback) } } } private var loaderDisposable: Disposable? = null private fun fetchDayStations(ref: Query, callback: (data: List<StationCloud>, errorMessage: LoadError?) -> Unit){ loaderDisposable?.dispose() loaderDisposable = FirebaseHelper .dbReadAsSingle(ref) .subscribeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread()) .doOnDispose { callback(emptyList(), LoadError.Cancelled) } .subscribe ({ snapshot -> if (snapshot.exists()) { val stations = mutableListOf<StationCloud>() snapshot.children.forEach { item -> item.getValue(StationCloud::class.java)?.also { station -> stations.add(station.copy(nodeId = item.key)) } } Timber.d("loader fetchDayStations stations size = ${stations.size}") callback(stations.toList(), null) } else callback(emptyList(), LoadError.NotExist) }, { Timber.e(it) callback(emptyList(), LoadError.CantGet) }) } private fun dbReadAsSingle(ref: Query): Single<DataSnapshot> { return Single.create { emitter -> ref.get().addOnCompleteListener { task -> Timber.d("runQueryCloudFirst task succeed = ${task.isSuccessful}") if (task.isSuccessful && emitter.isDisposed.not()){ Timber.d("runQueryCloudFirst children size = ${task.result.childrenCount}") emitter.onSuccess(task.result) } else task.exception?.also { //todo check a bug: timeout exception doesn't work when offline //https://github.com/firebase/firebase-android-sdk/issues/5771 Timber.e(it, "runQueryCloudFirst") emitter.onError(it) } } } }
    
android firebase-realtime-database
1个回答
0
投票
如果我理解正确的话,您正在从 Firebase 的本地缓存加载数据。在这种情况下,这种行为是有意义的,因为 Firebase 实际上必须从本地缓存中读取完整的

stations

 数据,然后返回该数据的一部分。因此,它必须加载每个切片的完整数据,从而导致线性性能问题。

Firebase 实时数据库 API 中没有任何内容可以对此进行更改。最好的选择通常是更改数据模型以适应用例。

处理 NoSQL 数据库时的一个好习惯是根据屏幕上显示的内容对数据进行建模,并仅加载实际向用户显示的数据。


在移动场景中,您似乎不太可能在单个屏幕上显示 5-7MB 的数据。即使其中 10% 也已经远远高于平均水平。因此更有可能的是,您正在加载大量实际上并未直接显示的数据。这就是您的解决方案所在:

    如果您仅显示每个子节点的子集,请考虑创建一个不同的顶级节点,其中仅包含每个子节点所需的信息。例如:
  • station_list_info
     可能只包含您实际显示的少数属性。该节点可能会
    小得多,因此会导致更少的内存问题。
  • 如果您要显示基于子节点的聚合值,请考虑将这些聚合值实际存储在数据库中,而不是在(每个)客户端上计算它们。如果您像这样存储聚合值,则每当您创建/更新/删除站点时都会更新它 - 因此写入数据变得更加复杂。另一方面,读取聚合值就变成了一个微不足道的操作。
这两种方法都是使用 NoSQL 数据库时的常见方法,您通常最终会得到反映您的用例的数据模型。要了解更多信息,我建议阅读

NoSQL 数据建模技术

© www.soinside.com 2019 - 2024. All rights reserved.