others - Javascript - Error: 10 ABORTED: Too much contention on these documents. Please try again

这个错误意味着什么?

这是否意味着事务失败,我不得不手动运行事务? 根据我在文档中所理解

在这种情况下,事务自动再次运行,事务在有限的次数内重试。

我只是得到了这个堆栈:

Error : 10 ABORTED : too much contention on these documents. please try again. at Object.exports.createStatusErrornode_modulesgrpcsrccommon.js:87:15) at ClientReadableStream._emitStatusIfDone node_modulesgrpcsrcclient.js:235:26) at ClientReadableStream._receiveStatus node_modulesgrpcsrcclient.js:213:8) at Object.onReceiveStatus node_modulesgrpcsrcclient_interceptors.js:1256:15) at InterceptingListener._callNext node_modulesgrpcsrcclient_interceptors.js:564:42) at InterceptingListener.onReceiveStatusnode_modulesgrpcsrcclient_interceptors.js:614:8) at C:UsersTolotra SamuelPhpstormProjectsCryptOceannode_modulesgrpcsrcclient_interceptors.js:1019:24 code : 10, metadata : Metadata } _internal_repr : {},details :'Too much contention on these documents. please try again.'}

要重新创建此错误,只需在db.runTransaction方法上运行for循环

时间:

如果需要在写入或写入之前更新数据,那么这个事务将会重试一次,如果有像计数器一样频繁更新的值,请考虑分布式计数器之类的其他解决方案,如果你想要更具体的建议,建议你在问题中包含你的事务代码和一些关于你想要实现的信息的信息。

我们在FirebaseFirestore数据库中遇到了同样的问题,

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

第一步是将事务操作保存为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


}



第二步是创建两个helper函数,一个用于等待specifix的时间量,另一个用于运行事务和捕获错误,如果代码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)


 }


 }


 }


}



现在我们所需要的就是,我们可以用调用helper函数,事务调用将会更长时间运行。

 

await runTransaction(taskCountTransaction)



我喜欢这个解决方案是,它并不需要更多或复杂的代码,而且大多数已经编写的代码,它还使用更多的时间和资源,只有计数器获得更多的项目时才会使用它,其他时间和资源与您拥有默认事务相同。

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

免责声明:我没有对数千或更多项目进行压力测试。在我们的具体情况下,问题从20项开始,我们需要多达50项任务。我测试了200件物品,问题再次没有出现。

Firestore运行事务的次数只有有限的次数,编写时,这个编号硬编码为5,不能更改,在很多用户使用相同的文档时,为了避免拥塞/争用,通常我们使用 back-off算法(但是,这将导致事务花费更多的时间完成,这在某些用例中是可以接受的)。

幸好,我们可以在事务中实现我们自己的 back-off算法:

 

const createTransactionCollisionAvoider = () => {


 let attempts = 0


 return {


 async avoidCollision() {


 attempts++


 await require('delay')(Math.pow(2, attempts) * 1000 * Math.random())


 }


 }


}



:可以像这样使用

 

//Each time we run a transaction, create a collision avoider.


const collisionAvoider = createTransactionCollisionAvoider()


db.runTransaction(async transaction => {


 //At the very beginning of the transaction run,


 //introduce a random delay. The delay increases each time


 //the transaction has to be re-run.


 await collisionAvoider.avoidCollision()



 //The rest goes as normal.


 const doc = await transaction.get(...)


 //...


 transaction.set(...)


})



注意:上述示例可能会导致您的交易最多需要1.5分钟才能完成。这对我的用例很好。您可能需要为您的用例调整back-off算法。

...