Google app engine 没有事务时GAE上的TransactionFailedError

Google app engine 没有事务时GAE上的TransactionFailedError,google-app-engine,app-engine-ndb,google-app-engine-python,Google App Engine,App Engine Ndb,Google App Engine Python,我得到了这个错误: TransactionFailedError: too much contention on these datastore entities. please try again. 即使我没有做任何交易。导致错误的代码行是 ndb.put_multi(entity_list) # entity_list is a list of 100 entities 这个错误并不经常发生,所以也没什么大不了的,但我很好奇为什么会出现这个错误。有什么想法吗 以下是大部分回溯: Trac

我得到了这个错误:

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.

请注意,错误实际上是从RPC响应中的数据存储本身接收的:
self.check\u RPC\u success(RPC)

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

从中,引用了一段话,表明无论操作是否为事务性操作,都在使用一些通用机制(重点是我的):

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

  • 下次在此实体组上执行读写操作或启动事务时,数据存储将首先滚动 转发并完全应用此已提交但未应用的写入,基于 日志中的数据
  • 失败的可能原因之一就是对相同实体的并行访问太多,即使它们只是只读的。看,虽然在这种情况下,它们是用于客户端事务的


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

    请注意,错误实际上是从RPC响应中的数据存储本身接收的:
    self.check\u RPC\u success(RPC)

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

    从中,引用了一段话,表明无论操作是否为事务性操作,都在使用一些通用机制(重点是我的):

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

  • 下次在此实体组上执行读写操作或启动事务时,数据存储将首先滚动 转发并完全应用此已提交但未应用的写入,基于 日志中的数据
  • 失败的可能原因之一就是对相同实体的并行访问太多,即使它们只是只读的。看,虽然在这种情况下,它们是用于客户端事务的


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

    可能需要重新审查,注意各种定义和限制


    将“创建、更新或删除实体的每次尝试都发生在事务上下文中”,以及“在单个实体组中每秒有一个事务的写入吞吐量限制”,可能说明了您所看到的情况,特别是如果
    实体\u列表
    包含可能属于同一实体组的实体。

    可能需要重新审查,注意各种定义和限制


    将“创建、更新或删除实体的每次尝试都发生在事务上下文中”,以及“在单个实体组中每秒有一个事务的写入吞吐量限制”,可能说明了您所看到的情况,特别是如果
    实体\u列表
    包含属于同一实体组的实体。

    此处放置的实体都是创建的,我没有使用任何实体组。听起来像是在幕后使用事务来创建实体。我觉得奇怪的是,在创建实体时可能会发生争用。您是提供自己的密钥名,还是让ID自动分配?如果密钥非常接近(例如,顺序),则前者可能导致争用问题。自动分配的ID避免了这一点。我确实有自定义的密钥名,它们是通过将不同实体的ID与用户的电子邮件地址连接起来创建的,比如12345678_joe@example.com. 在某些情况下,电子邮件地址将全部来自同一个域,因此密钥的唯一区别是“u”和“@”。这会使密钥足够紧密,从而可能导致争用问题吗?如果
    entity\u组中的所有(或许多)实体共享该密钥,我的最佳选择是这足以导致争用。@JeffO'Neill,如果前缀ID(在“\u”之前)如果按顺序递增,则前缀ID非常接近的实体也可能存储在同一个“平板电脑”中,这可能会创建您体验过的热点。最终,在自我优化的努力下,数据存储将拆分平板电脑中的实体序列,并在以后将部分移动到不同的平板电脑中。但最好的方法是让数据存储阻止热点并使用自动分配的ID(即使在为键添加字符串时)。顺便说一句,相似的热点可能出现在非常接近的索引时间戳上。这里放置的实体都是创建的,我没有使用任何实体组。听起来像是在幕后使用事务来创建实体。我觉得奇怪的是,在创建实体时可能会发生争用。您是提供自己的密钥名,还是让ID自动分配?前者可以引起争论