【问题标题】:TransactionFailedError on GAE when no transaction没有交易时 GAE 上的 TransactionFailedError
【发布时间】:2017-09-22 14:50:07
【问题描述】:

我收到了这个错误:

TransactionFailedError: too much contention on these datastore entities. please try again.

即使我没有进行任何交易。导致错误的代码行是

ndb.put_multi(entity_list) # entity_list is a list of 100 entities

这个错误不会经常发生,所以没什么大不了的,但我很好奇为什么会收到这个错误。有什么想法吗?

这是大部分的回溯:

Traceback (most recent call last):
  ...
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 318, in post
    self.run_from_request()
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 313, in run_from_request
    run(self.request.body)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/deferred/deferred.py", line 155, in run
    return func(*args, **kwds)
  File "/base/data/home/apps/s~opavote/2017-09-15.404125237783169549/tasks.py", line 70, in start_election
    models.Voter.create(e.eid, chunk)
  File "/base/data/home/apps/s~opavote/2017-09-15.404125237783169549/models.py", line 2426, in create
    ndb.put_multi(voters + vbs)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 3958, in put_multi
    for future in put_multi_async(entities, **ctx_options)]
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 383, in get_result
    self.check_success()
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 427, in _help_tasklet_along
    value = gen.throw(exc.__class__, exc, tb)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 824, in put
    key = yield self._put_batcher.add(entity, options)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 427, in _help_tasklet_along
    value = gen.throw(exc.__class__, exc, tb)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 358, in _put_tasklet
    keys = yield self._conn.async_put(options, datastore_entities)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 513, in _on_rpc_completion
    result = rpc.get_result()
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 928, in get_result
    result = rpc.get_result()
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/api/apiproxy_stub_map.py", line 613, in get_result
    return self.__get_result_hook(self)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1893, in __put_hook
    self.check_rpc_success(rpc)
  File "/base/data/home/runtimes/python27_experiment/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1385, in check_rpc_success
    raise _ToDatastoreError(err)
TransactionFailedError: too much contention on these datastore entities. please try again.

【问题讨论】:

    标签: google-app-engine app-engine-ndb google-app-engine-python


    【解决方案1】:

    请注意,错误实际上是从数据存储本身收到的,在 RPC 响应中:self.check_rpc_success(rpc)

    这让我怀疑,在数据存储方面,为了确保支持它的冗余基础设施的操作一致性/可靠性,每个写入操作实际上都使用与事务操作相同/相似的机制。不同之处在于,那些在客户端也有一些事务检查,在 RPC 交换之前/之后,可能还有数据存储的显式 RPC 事务开始/结束触发器。

    来自Life of a Datastore Write 的引述表明,无论操作是否为事务性操作,都在使用一些通用机制(强调我的):

    如果提交阶段成功但应用阶段失败,则 数据存储区将前滚以将更改应用于两个以下的索引 情况:

    1. 下次您对该实体组执行读取或写入或启动事务时,数据存储将首先滚动 转发并完全应用此已提交但未应用的写入,基于 日志中的数据。

    失败的可能原因之一是对相同实体的并行访问太多,即使它们只是只读的。请参阅Contention problems in Google App Engine,尽管在这种情况下它们用于客户端的事务。

    请注意,这只是一个理论;)

    【讨论】:

    • 您链接的第一页指出“写入时存在预期的失败率,因为 Bigtable 平板电脑有时不可用。”我怀疑这就是这里发生的事情,并且失败是这样做的交易的一部分。
    【解决方案2】:

    可能值得重新查看transactions and entity groups,注意各种定义和限制。

    将“创建、更新或删除实体的每一次尝试都发生在事务的上下文中”以及“在单个实体组中的写入吞吐量限制约为每秒一个事务”,可能会这样说与您所看到的一样,特别是如果 entity_list 包含属于同一实体组的实体。

    【讨论】:

    • 这里的实体都是被创建的,我没有使用任何实体组。听起来像是在后台使用事务来创建实体。在创建实体时可能会发生争用,这对我来说似乎很奇怪。
    • 您是提供自己的密钥名称,还是让 ID 自动分配?如果键非常靠近(例如,顺序),前者可能会导致争用问题。自动分配的 ID 可以避免这种情况。
    • 我确实有自定义键名,这些键名是通过将不同实体的 ID 与用户的电子邮件地址连接起来创建的,例如 12345678_joe@example.com。在某些情况下,电子邮件地址都来自同一个域,因此键的唯一区别将在“_”和“@”之间。这是否会使密钥靠得足够近而可能导致争用问题?
    • 如果所有(或许多)实体共享entity_group 共享该密钥,我最好的办法就是足以引起争用。
    • @JeffO'Neill,如果前缀 ID(“_”之前)按顺序递增,可能会发生前缀 ID 非常接近它的实体也可能存储在同一个“平板电脑”中,这可以创建您体验过的热点。最终,为了自我优化,Datastore 会将实体序列拆分到一个平板电脑中,稍后将部分移动到不同的平板电脑中。但最好的方法是让 Datastore 阻止热点并使用自动分配的 ID(即使您为键附加字符串)。顺便说一句,类似的热点可能会出现在非常接近的索引时间戳上。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-17
    • 2015-08-27
    • 2016-01-12
    • 2015-04-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多