Firestore加载Bundle继续存储已删除的文档

huangapple go评论65阅读模式
英文:

firestore loadBundle continues to store deleted documents

问题

我创建了一个包含3个文档的捆绑包。然后我删除了其中的2个文档并重新创建了捆绑包。所以现在捆绑包中只包含一个文档!而应用程序缓存中包含3个文档。

然后我再次下载捆绑包:

const resp = await fetch(downloadUrl);
await loadBundle(db, resp.body); //{totalDocuments: 1} - as expected
const query = await namedQuery(db, `my-bundle-query`);
if (query) {
   const snap = await getDocsFromCache(query); //Nope! there should be one document, but there are three
}

看起来像是一个 bug。我认为 loadBundle 应该以某种方式跟踪已删除的文档。我该怎么办?

更新:

namedQuery - 查询整个缓存。但期望的行为是仅获取与捆绑包相关的文档。所以这根本不起作用 - 在我的 namedQuery 中,我限制了只获取 4 个文档,但从缓存中获取了更多的文档!!

因此,官方示例是错误的,因为它面临了我上面描述的相同问题。

英文:

I created a bundle of 3 documents. Then I deleted 2 of them and created the bundle again. So the bundle now contains one document! And the application cache contains 3

Then I download the bundle again:

const resp = await fetch(downloadUrl);
await loadBundle(db, resp.body); //{totalDocuments: 1} - as expected
const query = await namedQuery(db, `my-bundle-query`);
if (query) {
   const snap = await getDocsFromCache(query); //Nope! there should be one document, but there are three
}

Looks like a bug. I think loadBundle should somehow keep track of deleted documents. What should I do?

UPDATE:

namedQuery - query the entire cache. But the expected behavior is to get only the documents associated with the bundle. So this doesn't work at all - in my namedQuery I have a limit of 4 documents but got more from the cache!!

So the official example is wrong because it faces the same problem I described above

答案1

得分: 1

抱歉,以下是翻译的内容:

TL:DR;

很遗憾,这是当前捆绑包编码方式的限制。这也超出了捆绑包的范围,因为它们的设计初衷是用于大量重复数据的集合,用于页面的初始加载。

如果您认为loadBundle应该清除本地缓存中未在捆绑包中返回的项目,您应该提出一个功能请求来将此功能添加到loadBundle调用中(例如loadBundle(rawBundle,/* forceRefresh = */ true)),或提交一个错误报告


详细信息

例如,假设您的查询是"获取/collection/Posts中的前10篇文章"。

在请求此查询的捆绑包时,第一个捆绑包返回以下结果:

{
  "/Posts/D9p7MbcYCbTNzcXLrzfQ": { /*文章数据*/ },
  "/Posts/xz3eY1Gwsjl4tTxTjXyR": { /*文章数据*/ },
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /*文章数据*/ }
}

然后,您使用loadBundle加载它们。

接下来,您使用另一个客户端从服务器删除了其中两个文档(使用相同的客户端会从本地缓存中删除它们)。

现在,您重新请求捆绑包,返回如下:

{
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /*文章数据*/ }
}

在调用loadBundle时,库会遍历捆绑包中的文档集合,更新每个文档的本地缓存:

// 这是伪代码,不是真正的实现
function loadBundle(rawBundle) {
  decodedBundle = parseBundle(rawBundle);

  decodedBundle.docs.forEach((doc) => {
    cachedDocuments.set(doc.id, doc);
  })

  return { // 返回捆绑包加载进度
    totalDocuments: decodedBundle.docs.length,
    /* ...其他统计信息... */
  };
}

在上面的伪代码中,您可以看到只有包含在捆绑包中的文档才会在本地缓存中更新。不包括在捆绑包中的文档不会更新,返回的统计信息与刚刚解码的捆绑包中包含的文档相关,而不是与相关查询的结果相关。

当运行命名查询时,查询会被解码,与本地缓存进行比较,并执行。由于前面的文档当前根据缓存与查询匹配,它们被包括在结果中。

只有在以下情况下本地缓存中的文档才会被省略:

  • 解码查询返回超过10个结果,而且它们不符合查询的条件。
  • 查询针对实时数据库执行。
  • 如果下载的捆绑包包含指示文档已被删除的元数据。

因此,为了清除本地缓存,捆绑包必须包含以下内容:

{
  "/Posts/D9p7MbcYCbTNzcXLrzfQ": _DELETED,
  "/Posts/xz3eY1Gwsjl4tTxTjXyR": _DELETED,
  // ...对于每个已存在的已删除文档...
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /*文章数据*/ }
}

返回这样的捆绑包在技术上很复杂,效率极低。

在请求捆绑包时,您可以包括一个文档ID列表,如果给定的文档ID不存在,那么文档删除会包括在捆绑包的数据中。但这样做,您可能会选择使用getDocsonSnapshot向服务器发出普通的数据库请求,获得相同的结果,这将更快捷和经济。

捆绑包的设计初衷是用于包含大量重复数据的集合,通常只用于页面的初始加载。如果在前50个结果中删除了一篇文章,您将使缓存的结果无效并重新构建捆绑包。所有新用户将立即看到更改后的结果,只有那些具有本地副本的用户将看到它们。

如果您认为loadBundle应该清除本地缓存中未在捆绑包中返回的项目,您应该提出一个功能请求,以将此功能添加到loadBundle调用中(例如loadBundle(rawBundle,/* forceRefresh = */ true))。

英文:

TL:DR;

Unfortunately this is a limitation of the way that bundles are currently coded. It would also be out of scope for bundles as they are designed to be used against collections with large amounts of reused data for the initial load of a page.

If you think that loadBundle should purge items from the local cache that aren't returned in the bundle, you should file a feature request to add this feature to the loadBundle call (e.g. loadBundle(rawBundle, /* forceRefresh = */ true)) or file a bug report.


The Details

For example's sake, lets assume your query is "Get the first 10 posts in collection /Posts".

Upon requesting the bundle for this query, the first bundle returns the following results:

{
  "/Posts/D9p7MbcYCbTNzcXLrzfQ": { /* post data */ },
  "/Posts/xz3eY1Gwsjl4tTxTjXyR": { /* post data */ },
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /* post data */ }
}

which you then load using loadBundle.

Next, you delete two of these documents from the server using another client (using the same client would delete them from the local cache).

Now you re-request the bundle, which returns:

{
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /* post data */ }
}

Upon calling loadBundle, the library iterates through the collection of documents in the bundle, updating the local cache for each document:

// this is psuedo-code, not the true implementation
function loadBundle(rawBundle) {
  decodedBundle = parseBundle(rawBundle);

  decodedBundle.docs.forEach((doc) => {
    cachedDocuments.set(doc.id, doc);
  })

  return { // return bundle load progress
    totalDocuments: decodedBundle.docs.length,
    /* ... other stats ... */
  };
}

In the above psuedo-code, you can see that only the documents that are included in the bundle are updated in the local cache. Documents not included in the bundle are not updated and the stats returned are related to the documents included in the bundle that was just decoded - not the results of the relevant query.

When you run the named query, the query is decoded, compared and executed against the local cache. As the former documents currently match the query according to the cache, they are included in the results.

The documents in the local cache will only be omitted when:

  • The decoded query returns more than 10 results, and they don't meet the criteria of the query.
  • The query is executed against the live database.
  • If the downloaded bundle included metadata indicating that the documents were deleted.

So for the local cache to be purged, the bundle would have to contain:

{
  "/Posts/D9p7MbcYCbTNzcXLrzfQ": _DELETED,
  "/Posts/xz3eY1Gwsjl4tTxTjXyR": _DELETED,
  // ... for every deleted document that ever existed ...
  "/Posts/fIvk5LF2zj2xgpgWIv9h": { /* post data */ }
}

Returning such a bundle would be technically complex and incredibly inefficient.

When requesting the bundle, you could include a list of document IDs, where if a given document ID doesn't exist, a document deletion is included in the bundle's data. However, in doing so, you may as well just make a normal database request to the server using getDocs or onSnapshot for the same result which would be faster and cheaper.

Bundles are designed to be used against collections with large amounts of reused data and generally only on the initial load of a page. If a post is deleted in the top 50 results, you would invalidate the cached results and rebuild the bundle. All new users would see the changed results immediately and only those users with the local copy would see them.

If you think that loadBundle should purge items from the local cache that aren't returned in the bundle, you should file a feature request to add this feature to the loadBundle call (e.g. loadBundle(rawBundle, /* forceRefresh = */ true)).

huangapple
  • 本文由 发表于 2023年2月6日 14:22:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/75357964.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定