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 循环
我们在 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 个项目对其进行了测试,问题没有再次出现。
Firestore 仅重新运行事务有限次数。截至撰写本文时,该数字已硬编码为 5,并且无法更改。为了避免许多用户使用同一文档时出现拥塞/争用,通常我们使用指数退避算法(但这将导致事务需要更长的时间才能完成,这在某些用例中可能是可以接受的)。
\n\n然而,截至撰写本文时,这尚未在 Firebase SDK 中实现,但\xe2\x80\x94 事务会立即重试。幸运的是,我们可以在事务中实现我们自己的指数退避算法:
\n\nconst 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}\nRun 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})\nRun Code Online (Sandbox Code Playgroud)\n\n注意:以上示例可能会导致您的交易最多需要 1.5 分钟才能完成。这对于我的用例来说很好。您可能需要根据您的用例调整退避算法。
\n| 归档时间: |
|
| 查看次数: |
7217 次 |
| 最近记录: |