Firestore 事务产生控制台错误:FAILED_PRECONDITION:存储的版本与所需的基本版本不匹配 [英] Firestore transaction produces console error: FAILED_PRECONDITION: the stored version does not match the required base version

查看:29
本文介绍了Firestore 事务产生控制台错误:FAILED_PRECONDITION:存储的版本与所需的基本版本不匹配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一些代码,允许用户以类似于 Reddit 的方式对食谱投赞成票/反对票.

每个投票都存储在名为 votes 的 Firestore 集合中,其结构如下:

{username,recipeId,value}(其中值为 -1 或 1)

食谱存储在 recipes 集合中,其结构有点像这样:{title,username,ingredients,instructions,score}

每次用户对某个菜谱进行投票时,我都需要将他们的投票记录在投票集合中,并更新该菜谱的得分.我想使用事务作为原子操作来执行此操作,因此这两个值不可能不同步.

以下是我目前的代码.我使用的是 Angular 6,但是我找不到任何显示如何在单个事务中处理多个 get() 的 Typescript 示例,因此我最终调整了我找到的一些基于 Promise 的 JavaScript 代码.

代码似乎有效,但发生了一些令人担忧的事情.当我快速连续单击 upvote/downvote 按钮时,偶尔会出现一些控制台错误.这些读取 POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 ().当我查看来自服务器的实际响应时,我看到了:

<代码>{错误": {代码":400,"message": "存储的版本 (1534122723779132) 与所需的基本版本 (0) 不匹配","status": "FAILED_PRECONDITION"}}

请注意,缓慢单击按钮时不会出现错误.

我应该担心这个错误,还是只是事务重试的正常结果?如 Firestore 文档中所述,调用事务的函数(事务函数)可能运行超过如果并发编辑影响事务读取的文档,则为一次."

请注意,我已经尝试将 try/catch 块包装在下面的每个操作周围,并且没有抛出任何错误.为了让代码更容易理解,我在发布之前删除了它们.

非常有兴趣听取任何改进我的代码的建议,无论这些建议是否与 HTTP 400 错误有关.

异步投票(用户名,recipeId,方向){让价值;如果(方向=='向上'){值 = 1;}如果(方向=='向下'){值 = -1;}//组装投票对象以记录在投票集合中const voteObj: Vote = { username: username, recipeId: recipeId, value: value };//获取对投票和配方文档的引用const voteDocRef = this.afs.doc(`votes/${username}_${recipeId}`).ref;const recipeDocRef = this.afs.doc('recipes/' + recipeId).ref;等待 this.afs.firestore.runTransaction( async t => {const voteDoc = 等待 t.get(voteDocRef);const recipeDoc = await t.get(recipeDocRef);const currentRecipeScore = await recipeDoc.get('score');如果(!voteDoc.exists){//这是一个新的投票,所以将它添加到投票集合中//并将其值应用于配方的分数t.set(voteDocRef,voteObj);t.update(recipeDocRef, { score: (currentRecipeScore + value) });} 别的 {const voteData = voteDoc.data();if (voteData.value == value) {//现有投票与按下的按钮相同,因此删除//投票文件并从配方的分数中恢复投票t.delete(voteDocRef);t.update(recipeDocRef, { score: (currentRecipeScore - value) });} 别的 {//现有投票与按下的投票相反,因此更新//对文档进行投票,然后通过加倍将其应用于配方的分数.//例如,如果当前分数为 1,并且用户反转他们的//按 -1 进行 +1 投票,我们应用 -2,因此分数将变为 -1.t.set(voteDocRef,voteObj);t.update(recipeDocRef, { score: (currentRecipeScore + (value*2))});}}返回 Promise.resolve(true);});}

解决方案

据 Firebase 开发者介绍Nicolas Garnier,您在这里遇到的是 Firestore 中事务的工作方式:其中一个事务写入失败,因为同时数据发生了变化,在这种情况下,Firestore 再次重新运行事务,直到它成功.如果同时编写多个评论,其中一些评论可能需要在第一次事务后再次运行,因为数据已更改.这是预期行为,应将这些错误更多地视为警告."

换句话说,这是事务重试的正常结果.

我使用 RxJS ThrottleTime 来防止用户通过快速连续点击 upvote/downvote 按钮用事务淹没 Firestore 服务器,这大大减少了这个 400 错误的发生.在我的应用程序中,没有正当理由有人需要每秒剪辑 upvote/downvote 数十次.这不是电子游戏.

I have written a bit of code that allows a user to upvote / downvote recipes in a manner similar to Reddit.

Each individual vote is stored in a Firestore collection named votes, with a structure like this:

{username,recipeId,value} (where value is either -1 or 1)

The recipes are stored in the recipes collection, with a structure somewhat like this: {title,username,ingredients,instructions,score}

Each time a user votes on a recipe, I need to record their vote in the votes collection, and update the score on the recipe. I want to do this as an atomic operation using a transaction, so there is no chance the two values can ever become out of sync.

Following is the code I have so far. I am using Angular 6, however I couldn't find any Typescript examples showing how to handle multiple gets() in a single transaction, so I ended up adapting some Promise-based JavaScript code that I found.

The code seems to work, but there is something happening that is concerning. When I click the upvote/downvote buttons in rapid succession, some console errors occasionally appear. These read POST https://firestore.googleapis.com/v1beta1/projects/myprojectname/databases/(default)/documents:commit 400 (). When I look at the actual response from the server, I see this:

{
  "error": {
    "code": 400,
    "message": "the stored version (1534122723779132) does not match the required base version (0)",
    "status": "FAILED_PRECONDITION"
  }
}

Note that the errors do not appear when I click the buttons slowly.

Should I worry about this error, or is it just a normal result of the transaction retrying? As noted in the Firestore documentation, a "function calling a transaction (transaction function) might run more than once if a concurrent edit affects a document that the transaction reads."

Note that I have tried wrapping try/catch blocks around every single operation below, and there are no errors thrown. I removed them before posting for the sake of making the code easier to follow.

Very interested in hearing any suggestions for improving my code, regardless of whether they're related to the HTTP 400 error.

async vote(username, recipeId, direction) {

  let value;

  if ( direction == 'up' ) {
    value = 1;
  }

  if ( direction == 'down' ) {
    value = -1;
  }

  // assemble vote object to be recorded in votes collection
  const voteObj: Vote = { username: username, recipeId: recipeId , value: value };

  // get references to both vote and recipe documents
  const voteDocRef = this.afs.doc(`votes/${username}_${recipeId}`).ref;
  const recipeDocRef = this.afs.doc('recipes/' + recipeId).ref;

  await this.afs.firestore.runTransaction( async t => {

    const voteDoc = await t.get(voteDocRef);
    const recipeDoc = await t.get(recipeDocRef);
    const currentRecipeScore = await recipeDoc.get('score');

    if (!voteDoc.exists) {

      // This is a new vote, so add it to the votes collection
      // and apply its value to the recipe's score
      t.set(voteDocRef, voteObj);
      t.update(recipeDocRef, { score: (currentRecipeScore + value) });

    } else {

      const voteData = voteDoc.data();

      if ( voteData.value == value ) {

        // existing vote is the same as the button that was pressed, so delete
        // the vote document and revert the vote from the recipe's score
        t.delete(voteDocRef);
        t.update(recipeDocRef, { score: (currentRecipeScore - value) });

      } else {

        // existing vote is the opposite of the one pressed, so update the
        // vote doc, then apply it to the recipe's score by doubling it.
        // For example, if the current score is 1 and the user reverses their
        // +1 vote by pressing -1, we apply -2 so the score will become -1.
        t.set(voteDocRef, voteObj);
        t.update(recipeDocRef, { score: (currentRecipeScore + (value*2))});
      }

    }

    return Promise.resolve(true);

  });

}

解决方案

According to Firebase developer Nicolas Garnier, "What you are experiencing here is how Transactions work in Firestore: one of the transactions failed to write because the data has changed in the mean time, in this case Firestore re-runs the transaction again, until it succeeds. In the case of multiple Reviews being written at the same time some of them might need to be ran again after the first transaction because the data has changed. This is expected behavior and these errors should be taken more as warnings."

In other words, this is a normal result of the transaction retrying.

I used RxJS throttleTime to prevent the user from flooding the Firestore server with transactions by clicking the upvote/downvote buttons in rapid succession, and that greatly reduced the occurrences of this 400 error. In my app, there's no legitimate reason someone would need to clip upvote/downvote dozens of times per seconds. It's not a video game.

这篇关于Firestore 事务产生控制台错误:FAILED_PRECONDITION:存储的版本与所需的基本版本不匹配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆