此错误是什么意思?
特别是什么意思:请重试
这是否意味着事务失败,我必须手动重新运行事务? 根据我对文档的了解,
该交易读取了在 交易。在这种情况下,交易自动再次运行。 重试该交易次数。
如果是,请问哪些文件? 该错误并不表示它正在谈论哪个文档。我刚得到这个堆栈:
{错误: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:{}},详细信息:“太 这些文件上有很多争论。请再试一遍。' }
要重新创建此错误,只需按照documentation
所示对db.runTransaction方法运行一个for循环。答案 0 :(得分:3)
我们遇到了与Firebase Firestore数据库相同的问题。即使是只有不到30件商品的小型柜台,也遇到了这个问题。
我们的解决方案不是分配计数器,而是增加交易的尝试次数,并为这些重试增加延迟时间。
第一步是保存事务操作,因为可以将const witch传递给另一个函数。
#!/usr/bin/env ipython
# ---------------------
l = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 2, 1, 1, 0, 1, 1, 1, 0],
[0, 1, 0, 1, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 0, 1, 0],
[0, 1, 0, 0, 0, 1, 0, 1, 0],
[0, 1, 1, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 1, 1, 0, 1, 0, 1, 0],
[0, 1, 0, 0, 0, 0, 0, 1, 0],
[0, 1, 0, 1, 1, 1, 0, 1, 0],
[0, 1, 0, 1, 0, 1, 0, 1, 0],
[0, 1, 1, 1, 0, 1, 1, 4, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
]
# ----------------------------------
def search(value,listin):
coords = [[ival,kkval] for ival,dd in enumerate(listin) for kkval,val in enumerate(dd) if val==value]
return coords
# ----------------------------------
result = search(4,l)
print result
第二步是创建两个辅助函数。一个用于等待指定的时间,另一个用于运行事务并捕获错误。如果发生代码10的中止错误,我们将再次运行该事务以进行特定的重试次数。
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<!-- Font for glyphicons -->
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Own CSS styles -->
<link rel="stylesheet" type="text/css" href="styles.css">
<nav class="navbar navbar-dark bg-dark navbar-expand-lg">
<a class="navbar-brand" href="#">Title</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse">
<ul class="navbar-nav mr-auto">
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button>
</form>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="#"><i class="fa fa-user-o" aria-hidden="true"></i></a>
</li>
</ul>
</div>
</nav>
<div class="container">
<h1>Content</h1>
</div>
<footer class="footer">
<div class="container">
<span class="text-muted">Title, 2018</span>
</div>
</footer>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
现在,我们有了所有需要的东西,我们可以使用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
}
来调用我们的辅助函数,并且事务调用将比默认调用运行更长的时间,并且时间会有所延迟。
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)
}
}
}
}
我对这种解决方案的满意之处在于,它并不意味着要编写更多或复杂的代码,并且大多数已编写的代码可以保持原样。仅当计数器达到必须计数更多项目的程度时,它才会使用更多时间和资源。其他时间和资源与您拥有默认事务的时间和资源相同。
要扩大规模,我们可以增加重试次数或等待时间。两者也都在影响Firebase的成本。对于等待的部分,我们还需要增加函数的超时时间。
免责声明:我没有对成千上万个项目进行压力测试。在我们的特定情况下,问题始于20多个项目,而一项任务最多需要50个项目。我用200个项目进行了测试,问题再没有出现。
答案 1 :(得分:0)
Firestore仅将事务重新运行有限次。在撰写本文时,this number is hard-coded as 5, and cannot be changed。为了避免在许多用户使用同一文档时出现拥塞/争用,通常我们使用指数退避算法(但这将导致事务处理花费更长的时间,在某些用例中可以接受)。
但是,截至撰写之时,this has not been implemented in the Firebase SDK yet-交易会立即重试。幸运的是,我们可以在事务中实现自己的指数补偿算法:
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分钟才能完成。这对我的用例来说很好。您可能需要针对用例调整退避算法。
答案 2 :(得分:0)
在runTransaction code中找到了maxAttempts
,应该修改5次默认尝试(但尚未测试)。
无论如何,我认为随机等待(最终加上队列)仍然是更好的选择。
答案 3 :(得分:0)
我实现了一个简单的退避解决方案,以共享:维护一个全局变量,该变量为每个失败的连接分配一个不同的“重试插槽”。例如,如果同时出现5个连接,而其中4个出现争用错误,则每个连接都会有500ms,1000ms,1500ms,2000ms的延迟,直到再次尝试。因此,它有可能全部同时解决,而不会引起更多争用。
我的交易是调用Firebase Functions的响应。每个功能计算机实例都可以具有一个全局变量nextRetrySlot
,该变量将一直保留到关闭为止。因此,如果error.code === 10
因争用问题而被捕获,则延迟时间可以为(nextRetrySlot + 1) * 500
,例如,您可以选择nextRetrySlot = (nextRetrySlot + 1) % 10
,以便下一个连接在500ms〜5000ms范围内获得不同的时间循环。 / p>
下面是一些基准测试:
我的情况是,我希望每个新的Firebase Auth注册都获得一个从唯一的Firebase UID派生的更短的ID,因此它有发生冲突的风险。
我的解决方案是简单地检查所有注册的短ID,如果查询返回什么,只需生成另一个,直到没有。然后,我们将此新的简短ID注册到数据库中。因此,该算法不能仅依赖Firebase UID,而是可以确定性的方式“移至下一个”。 (不仅是随机的)。
这是我的交易,它首先读取所有使用过的短ID的数据库,然后自动写入一个新ID,以防止极不可能的事件,即同时出现2个新寄存器,并且派生了另一个Firebase UID。相同的短ID,并且都看到该短ID同时是空的。
我运行了一个测试,该测试有意注册了20个不同的Firebase UID,这些UID都衍生为相同的短ID。 (极不可能的情况)所有这些同时运行。首先,我尝试在下一次重试中使用相同的延迟,因此我希望它会在缓慢解决一些连接问题时一次又一次地发生冲突。
然后在时隙中分配延迟时间:
确认不同的延迟时间肯定有帮助。
答案 4 :(得分:0)
Firestore现在支持服务器端increment()
和decrement()
原子操作。
您可以增加或减少任意数量。有关详细信息,请参见他们的blog post。在许多情况下,这将消除对客户端事务的需求。
示例:
document("fitness_teams/Team_1").
updateData(["step_counter" : FieldValue.increment(500)])
这仍然限于每个文档1 QPS的持续写入限制,因此,如果需要更高的吞吐量,请考虑使用distributed counters。这将增加您的读取成本(因为您需要阅读所有分片文档并计算总数),但允许您通过增加分片数量来扩展吞吐量。现在,如果您确实需要在事务中增加计数器,则由于更新争用而导致失败的可能性大大降低。