Firestore客户端缓存或不缓存或两者兼而有之

2022-04-08 00:00:00 数据 文档 缓存 应用程序 您的

The Firestore client SDKs have this handy feature called offline persistence (currently for Android, iOS, and web only). It helps your app built with Firestore to be usable without an internet connection, as far as database access is concerned. From the documentation:

Firestore客户端SDK具有此方便的功能,称为离线持久性 (当前仅适用于Android,iOS和Web)。 就数据库访问而言,它可帮助您使用Firestore构建的应用程序无需互联网连接即可使用。 从文档中:

This feature caches a copy of the Cloud Firestore data that your app is actively using, so your app can access the data when the device is offline. You can write, read, listen to, and query the cached data. When the device comes back online, Cloud Firestore synchronizes any local changes made by your app to the Cloud Firestore backend.

此功能缓存您的应用程序正在使用的Cloud Firestore数据的副本,因此您的应用程序可以在设备离线时访问数据。 您可以写入,读取,收听和查询缓存的数据。 设备重新联机后,Cloud Firestore会将您的应用所做的所有本地更改同步到Cloud Firestore后端。

While that sums it up pretty nicely, there are some nuances about the way this works that you should be aware of. If you want to make the best use of this cache, read on!

虽然这很好地总结了这一点,但是您应该注意一些有关此工作方式的细微差别。 如果您想充分利用此缓存,请继续阅读!

服务器先运行,然后进行缓存(默认情况下) (Server goes first, then cache (by default))

When you perform a query, by default, the client SDK will consult the server for results. If your internet connection is slow, the SDK will fall back to results from cache, if any. You might see something in the device log like this:

在执行查询时,默认情况下,客户端SDK将向服务器查询结果。 如果您的互联网连接速度很慢,则SDK将退回到缓存的结果(如果有)。 您可能会在设备日志中看到如下内容:

Could not reach Cloud Firestore backend. Backend didn’t respond within 10 seconds.

无法到达Cloud Firestore后端。 后端在10秒内未响应。

This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.

这通常表明您的设备当前没有正常的Internet连接。 客户端将以脱机模式运行,直到能够成功连接到后端为止。

I don’t know how you feel about waiting 10 seconds for results, but as far as I see, this isn’t a configurable option. Anyway, 10 seconds to get a cached result is probably better than no results at all!

我不知道您等待10秒的结果如何,但据我所知,这不是一个可配置的选项。 无论如何,10秒钟才能得到缓存的结果总比没有结果要好!

Now, if you’re wondering how to go directly to the server or cache without this failover behavior, this is possible with source options. When you call get() to perform a query, you can optionally pass a source option to tell the Firestore SDK if you only want server or cache results. The syntax varies between client platforms. With JavaScript, it looks like this:

现在,如果您想知道如何在没有这种故障转移行为的情况下直接进入服务器或缓存,则可以使用source options实现 。 调用get()进行查询时,可以选择传递source选项,以告诉Firestore SDK是否只需要服务器或缓存结果。 语法在客户端平台之间有所不同。 使用JavaScript,如下所示:

documentRef.get({ source: 'server' })    // or 'cache'

When you specify a server source, it will not fall back to cache. And, if you’re offline, it’ll fail immediately with an error:

当您指定服务器源时,它不会回退到缓存。 而且,如果您处于离线状态,它将立即失败并显示错误消息:

Failed to get document from server. (However, this document does exist in the local cache. Run again without setting source to “server” to retrieve the cached document.)

无法从服务器获取文档。 (但是,此文档确实存在于本地缓存中。无需将源设置为“服务器”即可再次运行以检索缓存的文档。)

This error message actually suggests that it saw a document in the cache that would have matched the query. You might see something different.

该错误消息实际上表明它在缓存中看到了与查询匹配的文档。 您可能会看到不同的东西。

缓存不会直接降低读取成本(除非您以这种方式使用) (The cache does not directly reduce the cost of reads (unless you use it that way))

Since the default behavior of get() with no source option always goes to the server first, the local persistence layer doesn’t directly help you reduce the number of billable reads. However, you can write code to query the cache first, then fall back to the server if not present:

由于默认情况下,不带源选项的get()的默认行为总是首先到达服务器,因此本地持久层不能直接帮助您减少可计费读取次数。 但是,您可以编写代码来首先查询缓存,然后如果不存在,则退回到服务器:

let snapshot = await documentRef.get({ source: 'cache' }if (!snapshot.exists) {    snapshot = await documentRef.get({ source: 'server' })}

This could be a good way of saving on the cost of read operations, since reads directly from the cache don’t incur any billable costs. But it comes with one big caveat: if the app never again reads this document from the server, and only ever from cache first, it might never see future changes to the document’s data. Yeah, that could be kind of a problem. Or maybe not, if your documents are always immutable! So you’ll have to make that call for yourself.

这可能是节省读取操作成本的好方法,因为直接从缓存读取不会产生任何可计费的费用。 但这带来了一个很大的警告: 如果应用程序再也不会从服务器读取此文档,并且仅从缓存中读取该文档,则它可能再也看不到文档数据的未来更改。 是的,那可能是个问题。 也许不是,如果您的文档始终是不可变的! 因此,您必须自己打电话。

Instead, you might want to put some sort of expiration timestamp in each document to check how stale it is, which could help decide if you want to go back to the server again in the future. But then you have the problem of keeping those timestamps up-to-date in all your documents. You should estimate that cost for your own specific case.

取而代之的是,您可能希望在每个文档中放置某种过期时间戳记,以检查它的陈旧程度,这可以帮助您确定将来是否要再次返回服务器。 但是,那么您就有了使所有文档中的时间戳保持新的问题。 您应根据自己的具体情况估算该费用。

快照元数据指示数据的已知来源 (Snapshot metadata indicates the known origin of the data)

If you make a query using the default get(), it’s possible to tell where that snapshot came from. Each DocumentSnapshot has a SnapshotMetadata object attached to it. This has a property called fromCache that tells you the origin:

如果使用默认的get()进行查询,则可以确定快照的来源。 每个DocumentSnapshot都附加有一个SnapshotMetadata对象。 它有一个名为fromCache的属性,它告诉您起源:

const snapshot = await documentRef.get()if (snapshot.metadata.fromCache) {    // Buyer beware, this could be stale...}

This property can help you determine how best to render the data in your app. For example, you might want to provide some option to refresh the data, if that’s important for the user to know. Note that fromCache=true does not indicate if there are definitely updates to the document on the server, just that it doesn’t know for sure yet. You’ll see fromCache=false when the snapshot definitely came from the server for this query.

此属性可以帮助您确定如何好地呈现应用程序中的数据。 例如,如果对用户来说很重要,则可能需要提供一些选项来刷新数据。 需要注意的是fromCache=true 表示如果有更新到服务器上的文件,只是它不肯定知道呢。 当快照肯定来自服务器时,您将看到fromCache=false 。

If you’re digging around in that SnapshotMetadata object, you’ll also notice that there’s a hasPendingWrites property. A “pending write” means that the snapshot was written by the client app, and cached locally, but hasn’t been synchronized with the server yet. So, the local cache not only provides read caching, but also write caching.

如果您在该SnapshotMetadata对象中进行挖掘,您还将注意到这里有一个hasPendingWrites属性。 “待写”表示快照是由客户端应用编写的,并在本地缓存,但尚未与服务器同步。 因此,本地缓存不仅提供读取缓存 ,还提供写入缓存 。

有关缓存的脱机写入注意事项的一些注意事项 (A few notes about the caveats of cached offline writes)

It’s worth noting that offline writes to the cache do not count as “successful” operations from the perspective of the SDK. So, if you call set()update(), or delete() while offline:

值得注意的是,从SDK的角度来看,离线写入缓存不算作“成功”操作。 因此,如果在离线时调用set() , update()delete() :

  • JavaScript promises will not resolve or reject

    JavaScript承诺不会解决或拒绝
  • Java/Kotlin Tasks will not complete

    Java / Kotlin任务将无法完成
  • Swift/ObjC callbacks will not be invoked

    Swift / ObjC回调将不会被调用

These things will happen eventually, if the app comes back online and the SDK is able to sync. But if the app process dies while offline and is relaunched online, those methods of tracking the completion of the operation will no longer work. However, the persistent snapshot metadata will indicate what happened, so that’s your source of truth.

如果应用重新联机并且SDK能够同步,这些事情将会发生。 但是,如果应用程序进程在脱机时死掉并重新在线启动,则跟踪操作完成的那些方法将不再起作用。 但是,持久快照元数据将指示发生了什么,因此这就是事实的来源。

In particular, you should note that security rules are not checked while offline, as they are only securely checked on the backend. The client could make tons of offline writes that eventually get rejected by security rules, so you should be aware of that as you present data to the user while they’re offline.

特别是,您应注意, 脱机时不会检查 安全规则 ,因为它们仅在后端安全地检查。 客户端可能进行大量脱机写操作, 被安全规则拒绝,因此当您在用户脱机时向用户提供数据时,您应该意识到这一点。

It sounds really good to have some sort of indicator in your app about the state of a document write, doesn’t it? Fortunately, you can listen for document metadata updates as well.

在您的应用程序中有某种关于文档写入状态的指示器听起来确实不错,不是吗? 幸运的是,您也可以收听文档元数据更新。

收听快照元数据以了解其缓存状态何时更改 (Listen to snapshot metadata to know when its cache state changes)

By default, when you perform a query, your listener gets notified of changes to document data only:

默认情况下,当您执行查询时,您的侦听器仅会收到有关文档数据更改的通知:

documentRef.onSnapshot(snap => {    // snap contains live updates to the document data    // Render snap.data() as desired...})

But you can expand this to include changes to the document metadata as well (fromCache and hasPendingWrites):

但是您可以扩展它以包括对文档元数据的更改( fromCachehasPendingWrites ):

documentRef.onSnapshot({ includeMetadataChanges: true }, snap => {    // snap contains live updates to the document data OR metadata    // Check snap.metadata.fromCache...    // Check snap.metadata.hasPendingWrites...})

Listening to metadata changes like this is handy to let the user know:

听这样的元数据更改很容易让用户知道:

  • When you’re sure they are looking at fresh data from the server.

    当您100%确定他们正在查看服务器中的新数据时。
  • When you’re sure a document update they made was actually saved in the cloud.

    当您100%确定他们所做的文档更新实际上已保存在云中。

缓存不启用Firestore的“脱机优先”用法(“脱机也”) (The cache does not enable “offline-first” usage of Firestore (it’s “offline-also”))

You might wonder if it’s possible to take more control over the behavior of the cache in order to create an app that’s usable offline. Unfortunately, that’s not possible. You can certainly enable or disable the cache entirely, and set its max size, but:

您可能想知道是否有可能对缓存的行为进行更多控制,以创建可100%离线使用的应用程序。 不幸的是,那是不可能的。 您当然可以完全启用或禁用缓存 ,并设置其大大小 ,但是:

  • You can’t pre-populate the cache on first launch. Data only appears in the cache after query results come from the server

    您无法在启动时预先填充缓存。 查询结果来自服务器后,数据仅出现在缓存中

  • You can’t control the contents of the cache. The SDK manages it entirely, adding documents as discovered, or removing old documents when the cache exceeds its capacity (the default is 100MB).

    您无法控制缓存的内容。 当缓存超过其容量(默认值为100MB)时,SDK将对其进行完全管理,添加发现的文档或删除旧文档。

If you want more caching control, especially to enable a full offline-first experience for your app, you’re going to have to implement that yourself. It’s doable to use a local database as the primary point of access for your data, then use Firestore to synchronize that to the cloud. For example, you could use Jetpack Room on Android, Core Data on iOS, or IndexedDB for web apps. But it will be a fair amount of work if you want to implement full two-way sync.

如果您想要更多的缓存控制,尤其是要为您的应用程序提供完整的脱机优先体验,则必须自己实现。 可以使用本地数据库作为数据的主要访问点,然后使用Firestore将其同步到云。 例如,您可以在Android上使用Jetpack Room ,在iOS上使用Core Data或针对Web应用程序使用IndexedDB 。 但是,如果要实现完整的双向同步,则将需要大量工作。

特例 (A special case)

Depending on the contents of your database and your users’ behavior, you might want to mostly cache documents, but not entirely. If you have a specific query that pulls down very large amounts of data, and you don’t want that cluttering the cache (and making offline queries slow) or evicting other more useful data from the cache, you might want to execute a query in a special context where its results are never cached. While the SDK doesn’t provide an API for this, you can work around it by:

根据数据库的内容和用户的行为,您可能希望主要缓存文档,但不完全缓存。 如果您有一个特定的查询会提取大量数据,而又不想使高速缓存混乱(并使离线查询变慢)或从高速缓存中逐出其他更有用的数据,则可能需要在以下位置执行查询:一个从不缓存其结果的特殊上下文。 虽然SDK没有为此提供API,但您可以通过以下方法解决该问题:

  1. Programmatically initializing a new app instance using FirebaseApp.initializeApp()

    使用FirebaseApp.initializeApp()编程方式初始化新的应用程序实例

  2. Disabling that app’s Firestore cache (it will not affect other app instances)

    禁用该应用程序的Firestore缓存(不会影响其他应用程序实例)
  3. If necessary, sign in the user to that app instance using their existing credentials

    如有必要,请使用其现有凭据将用户登录到该应用程序实例
  4. Making never-cached queries through that app instance only

    仅通过该应用实例进行永不缓存的查询

Or, it might actually be easier to query the Firestore REST API directly.

或者,实际上直接查询Firestore REST API可能会更容易。

那么...缓存还是没有缓存? (So… cache or no cache?)

The Firestore client SDK cache is useful without knowing anything about it. But the more you know, the more robust your app can be, both online and offline.

Firestore客户端SDK缓存在不了解任何内容的情况下很有用。 但是您了解得越多,在线和离线应用程序的功能就越强大。

相关文章