错误:10 ABORTED:对这些文档的争用过多。请再试一次

TSR*_*TSR 10 javascript node.js firebase google-cloud-firestore

这个错误是什么意思?

特别是,它们是什么意思:请再试一次

这是否意味着交易失败我必须手动重新运行交易?根据我从文档中的理解,

事务读取了在事务之外修改的文档。在这种情况下,事务会自动再次运行。事务被重试有限次数。

如果是,在哪些文件上?该错误不表明它在谈论哪个文档。我只是得到这个堆栈:

{ 错误:10 中止:对这些文件的争用过多。请再试一次。在 Object.exports.createStatusErrornode_modules\grpc\src\common.js:87:15) 在 ClientReadableStream._emitStatusIfDone \node_modules\grpc\src\client.js:235:26) 在 ClientReadableStream._receiveStatus \node_modules\grpc\src\client .js:213:8) 在 Object.onReceiveStatus \node_modules\grpc\src\client_interceptors.js:1256:15) 在 InterceptingListener._callNext node_modules\grpc\src\client_interceptors.js:564:42) 在 InterceptingListener.onReceiveStatus\node_modules \grpc\src\client_interceptors.js:614:8) 在 C:\Users\Tolotra Samuel\PhpstormProjects\CryptOcean\node_modules\grpc\src\client_interceptors.js:1019:24 代码:10,元数据:元数据 { _internal_repr:{ } }, details: '对这些文件的争论太多了。请再试一次。

要重新创建此错误,只需按照文档中的说明在 db.runTransaction 方法上运行 for 循环

Tar*_*ber 8

我们在 Firebase Firestore 数据库中遇到了同样的问题。即使是少于 30 件商品的小柜台,也能找出遇到此问题的地方。

我们的解决方案不是分发计数器,而是增加事务的尝试次数并为这些重试添加延迟时间。

第一步是保存事务操作,因为 const 可以将其传递给另一个函数。

const taskCountTransaction = async transaction => {
  const taskDoc = await transaction.get(taskRef)

  if (taskDoc.exists) {
    let increment = 0
    if (change.after.exists && !change.before.exists) {
      increment = 1
    } else if (!change.after.exists && change.before.exists) {
      increment = -1
    }

    let newCount = (taskDoc.data()['itemsCount'] || 0) + increment
    return await transaction.update(taskRef, { itemsCount: newCount > 0 ? newCount : 0 })
  }

  return null
}
Run Code Online (Sandbox Code Playgroud)

第二步是创建两个辅助函数。一个用于等待特定的时间,另一个用于运行事务并捕获错误。如果出现代码 10 的中止错误,我们只需再次运行事务进行特定数量的重试。

const wait = ms => { return new Promise(resolve => setTimeout(resolve, ms))}


const runTransaction = async (taskCountTransaction, retry = 0) => {
  try {
    await fs.runTransaction(taskCountTransaction)
    return null
  } catch (e) {
    console.warn(e)
    if (e.code === 10) {
      console.log(`Transaction abort error! Runing it again after ${retry} retries.`)

      if (retry < 4) {
        await wait(1000)
        return runTransaction(taskCountTransaction, ++retry)
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

现在我们拥有了我们需要的一切,我们可以调用我们的辅助函数,await我们的事务调用将比默认调用运行更长时间,并且会及时延迟。

await runTransaction(taskCountTransaction)
Run Code Online (Sandbox Code Playgroud)

我喜欢这个解决方案的地方在于它并不意味着更多或复杂的代码,而且大多数已经编写的代码可以保持原样。仅当计数器达到必须计算更多项目的程度时,它才会使用更多时间和资源。否则,时间和资源与您拥有默认事务的时间和资源相同。

为了扩大大量项目,我们可以增加重试次数或等待时间。两者也在影响 Firebase 的成本。对于等待部分,我们还需要增加函数的超时时间。

免责声明:我没有用数千个或更多的项目对此代码进行压力测试。在我们的特定案例中,问题始于 20 多个项目,我们需要多达 50 个项目来完成任务。我用 200 个项目对其进行了测试,问题没有再次出现。

  • 谢谢@AndrewKvochick。最后,错误仍然时有发生。所以我们最终提出了一个队列概念,正如你所说,那些失败的事务正在停止运行队列的函数并保存错误的函数以进行调试。这种方法有一些缺点,比如复杂的代码和更高的计费,但在我们的例子中,交易需要以 100% 的准确性发生,因此更多的计费是可以的。我可能会为此写一篇中等文章来涵盖我们的整个故事。现在我们可以在没有 vory 的情况下触发大约 10k 次更改。没有测试更多。 (2认同)

Jen*_*son 5

如果需要,事务确实会运行多次,但如果在写入之前读取的值继续更新,则最终会失败,因此记录事务的文档会重试有限次数。如果您有一个像计数器一样频繁更新的值,请考虑其他解决方案,例如分布式计数器。如果您需要更具体的建议,我建议您在问题中包含交易代码以及有关您想要实现的目标的一些信息。


Tha*_*hai 5

Firestore 仅重新运行事务有限次数。截至撰写本文时,该数字已硬编码为 5,并且无法更改。为了避免许多用户使用同一文档时出现拥塞/争用,通常我们使用指数退避算法(但这将导致事务需要更长的时间才能完成,这在某些用例中可能是可以接受的)。

\n\n

然而,截至撰写本文时,这尚未在 Firebase SDK 中实现,但\xe2\x80\x94 事务会立即重试。幸运的是,我们可以在事务中实现我们自己的指数退避算法:

\n\n
const createTransactionCollisionAvoider = () => {\n  let attempts = 0\n  return {\n    async avoidCollision() {\n      attempts++\n      await require(\'delay\')(Math.pow(2, attempts) * 1000 * Math.random())\n    }\n  }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6 可以这样使用:

\n\n
// Each time we run a transaction, create a collision avoider.\nconst collisionAvoider = createTransactionCollisionAvoider()\ndb.runTransaction(async transaction => {\n  // At the very beginning of the transaction run,\n  // introduce a random delay. The delay increases each time\n  // the transaction has to be re-run.\n  await collisionAvoider.avoidCollision()\n\n  // The rest goes as normal.\n  const doc = await transaction.get(...)\n  // ...\n  transaction.set(...)\n})\n
Run Code Online (Sandbox Code Playgroud)\n\n

注意:以上示例可能会导致您的交易最多需要 1.5 分钟才能完成。这对于我的用例来说很好。您可能需要根据您的用例调整退避算法。

\n