英文:
GAE Go - how to handle ErrConcurrentTransaction Datastore Transaction error
问题
我正在写一个使用Google App Engine的Golang应用程序。在Datastore事务文档中,有一个注意事项:
注意:如果您的应用程序在提交事务时收到错误,这并不总是意味着事务失败。在某些情况下,您可能会收到ErrConcurrentTransaction,这意味着事务已经被提交,并最终将成功应用。尽可能使您的Datastore事务具有幂等性,这样如果您重复执行事务,最终结果将相同。
这让我相信,如果一个事务返回ErrConcurrentTransaction,那么它意味着Datastore最终会完成该事务。然而,在阅读RunInTransaction的文档时,我们可以看到一个注意事项:
如果f返回nil,RunInTransaction尝试提交事务,如果成功则返回nil。如果由于冲突事务而导致提交失败,RunInTransaction会重试f,每次都使用新的事务上下文。在三次失败尝试后,它会放弃并返回ErrConcurrentTransaction。
看起来ErrConcurrentTransaction是RunInTransaction函数的失败状态,这意味着事务将永远不会提交。
那么,到底是哪种情况呢?如果RunInTransaction返回ErrConcurrentTransaction,我的代码应该做出什么假设?事务成功了吗?它将来会成功吗?还是失败了?
英文:
I am writing a Google App Engine Golang app. In the Datastore Transaction documentation, there is a note:
> Note: If your app receives an error when submitting a transaction, it does not always mean that the transaction failed. You can receive ErrConcurrentTransaction in cases where transactions have been committed and eventually will be applied successfully. Whenever possible, make your Datastore transactions idempotent so that if you repeat a transaction, the end result will be the same.
Which makes me believe that if a transaction returns ErrConcurrentTransaction, it means the Datastore will eventually complete the transaction. However, reading up on RunInTransaction we can see a note:
> If f returns nil, RunInTransaction attempts to commit the transaction, returning nil if it succeeds. If the commit fails due to a conflicting transaction, RunInTransaction retries f, each time with a new transaction context. It gives up and returns ErrConcurrentTransaction after three failed attempts.
It looks like ErrConcurrentTransaction is a fail state for the RunInTransaction function which means the Transaction will never commit.
So, which is it? If RunInTransaction returns ErrConcurrentTransaction, what should my code assume? Did the Transaction succeed, will it succeed in the future, or did it fail?
答案1
得分: 1
具体情景。考虑以下代码片段:
err := datastore.RunInTransaction(c, func(c appengine.Context) error {
var err1 error
count, err1 = inc(c, datastore.NewKey(c, "Counter", "singleton", 0, nil))
return err1
}, nil)
// 在这里,如果 err 不是 nil,表示数据存储操作没有提交到数据存储中。
这是运行此代码片段时可能发生的一种情况:
- RunInTransaction 开始。它调用提供的函数。
- 在该函数内部,inc() 操作返回 nil。
- RunInTransaction 接收到该结果,并尝试完成提交。但由于某种原因,提交失败(可能的失败原因)。因此,RunInTransaction 从数据存储接收到一个 ErrConcurrentTransaction。
- RunInTransaction 再次尝试。它调用提供的函数。
- 在该函数内部,inc() 操作返回 nil。
- RunInTransaction 接收到该结果,并尝试完成提交。但由于某种原因,再次失败。也许是因为数据存储繁忙。
- RunInTransaction 耐心地再次尝试该函数。
- 在该函数内部,inc() 操作返回 nil。
- RunInTransaction 接收到该结果,并尝试完成提交。这次,底层数据存储允许提交。太好了!
- RunInTransaction 将 nil 返回给其调用者。
因此,在这种情况下,你的应用程序会遇到 ErrConcurrentTransactions。你读到的第一个注释是关于整个系统的一般性评论:作为一个整体,你的程序可能会遇到 ErrConcurrentTransactions。但这并不意味着你编写的代码会直接触及 ErrConcurrentTransaction。你的代码可能根本不会看到此错误。然而,RunInTransaction 代表你的代码运行,并且 RunInTransaction 可能会遇到该错误。但是事务仍然可以继续进行,因为 RunInTransaction 将重放该函数,直到成功或数据存储繁忙而放弃。
如果从 RunInTransaction 得到的最终返回值是 nil,则表示数据存储操作已成功。但如果得到非 nil 值,则表示操作未成功。
请注意,在上述情景中,由 RunInTransaction 调用的函数作为重试协议的一部分被多次调用。因此,你必须确保在传递给 RunInTransaction 的函数中处理好这一点,因为当数据存储繁忙时,它将尝试使用重试。
英文:
Concrete scenario. Consider the following snippet:
err := datastore.RunInTransaction(c, func(c appengine.Context) error {
var err1 error
count, err1 = inc(c, datastore.NewKey(c, "Counter", "singleton", 0, nil))
return err1
}, nil)
// Here, if err is anything other than nil, the datastore-specific
// operations didn't commit to the datastore.
Here's one possible scenario when we run this snippet:
- The RunInTransaction starts. It calls the provided function.
- Within that function, the inc() operation returns nil.
- The RunInTransaction receives that result, and tries to finish the commit. But for some reason, it fails. (Where it can fail.) So RunInTransaction receives a ErrConcurrentTransaction from the datastore.
- The RunInTransaction tries again. It calls the provided function.
- Within that function, the inc() operation returns nil.
- The RunInTransaction receives that result, and tries to finish the commit. But for some reason, it fails yet again. Hey, maybe it's busy.
- RunInTransaction patiently tries the function one more time.
- Within that function, the inc() operation returns nil.
- The RunInTransaction receives that result, and tries to finish the commit. This time, the underlying datastore allows the commit. Hurrah!
- RunInTransaction returns nil to its caller.
So in this scenario, your application observes ErrConcurrentTransactions. The first note you read is a general comment about the system as a whole: as a whole, your program may encounter ErrConcurrentTransactions. But that doesn't mean that the code you write will touch ErrConcurrentTransaction directly. Your code may not see this error at all. Yet RunInTransaction is running on behalf of your code, and RunInTransaction might see that error. But the transaction can still go forward because RunInTransaction will replay the function until it either succeeds, or the datastore is busy enough that it gives up.
If you get nil as the final return value from RunInTransaction, the datastore operations went through. But if you get non-nil, they didn't.
Note that in the scenario above, the function called by RunInTransaction is called multiple times as part of the retry protocol. So you've got to make sure that's ok in the functions you pass to RunInTransaction, because it will try using retry when the datastore is busy.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论