【问题标题】:TransactionFailedError (too much contention...) when reading (cross-group) entities from datastore从数据存储区读取(跨组)实体时出现 TransactionFailedError(争用过多...)
【发布时间】:2017-09-17 14:37:09
【问题描述】:

我正在再次调查TransactionFailedError (too much contention on these datastore entities... 意外发生的情况,其中代码仅读取因争用问题而受到指责的实体组。

设置

GAE 标准环境,带有 NDB 的 Python 2.7 (SDK 1.9.51)。我设法在一个隔离的应用程序(只有我作为用户)中观察到错误,其中相同的请求处理程序在任务队列中执行,并且对下面提到的实体组的读/写访问仅由该处理程序完成。

处理程序每​​秒执行几次,基本上是一个迁移/复制任务,将现有的OriginChild 实体从一个巨大的组中移动到单独的组中,作为新的Target 实体。这是每个OriginChild 实体的一项任务。

在一个跨组事务函数ndb.transaction(lambda: main_activity(), xg=True)内,每个请求处理程序...:

  • 确实使用 get_async NDB 小任务来检索两个实体:

    • Key(OriginGroup, 1)(所有请求都一样)

    • Key(OriginGroup, 1, OriginChild, Foo)(每个请求的唯一对象)

  • Key(TargetConfig, 1).get()

  • Key(Target, Foo).get()(真的没有父母!)

  • 如果 Key 不存在,在事务函数离开之前,Key(Target, Foo).put_async()get_result() 是否存在

所以,这些是事务中的只读实体:

  • Key(Origin, 1)

  • Key(Origin, 1, OriginChild, Foo)

  • Key(TargetConfig, 1)

代码不会进行任何更改,这些实体不会被删除或写回数据存储区。此外,没有其他正在运行的请求尝试写入这些实体组 - 这些组中几个月都没有写入操作)。

放入数据存储区的唯一实体是 Key(Target, Foo),其中 ID 对于每个请求都是唯一的。

错误

大约 60-70% 的请求将在没有错误的情况下运行。

当 TransactionFailedError 发生时,它将在事务函数内部,日志显示如下:

suspended generator get(context.py:758) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "e~my-test-app" name_space: "test" path < Element { type: "OriginGroup" id: 1 } > )

在大约 80% 的失败请求中,错误将与 Key(OriginGroup, 1) 相关(尽管整个组都是只读的)。

在大约 10% 的失败请求中,错误将显示 Key(TargetConfig, 1)(也是只读的)。

在剩下的约 10% 中,它将归咎于新实体,例如Key(Target, Foo),或者对于任何 TargetChild 的 ID,请求都会执行迁移,而且它似乎只发生在 put() 期间,而不是之前的 get() 尝试期间。

理论

我对事务和实体组的理解是,NDB 遵循乐观并发控制,因此来自同一实体组的大规模读取操作是可能的(因此可扩展性),并且由于技术原因仅用于事务性写入操作存在每个实体组每秒约 1 个写入操作的限制,并且每个事务不超过 25 个实体组。

但是,我的观察表明读取操作也可能导致too much contention 错误。但这个想法也让我感到困惑,因为如果你的目标是强一致性,它会使 GAE 与 Datastore 的可扩展性大大降低。所以也许这里还有其他事情发生。

我发现这个关于 SO 的评论声称我的假设是正确的:

"注意:如果与访问同一实体组的其他事务发生冲突,则 XG 事务中实体组的第一次读取可能会引发 TransactionFailedError 异常。这意味着即使仅执行读取的 XG 事务也可能会失败并发异常。”

来源:Contention problems in Google App Engine

我能够在新文档中找到引用,现在位于 Superseded Storage Solutions > DB Client Library for Cloud Datastore > Overview

问题

引用的语句是否仍然适用于 NDB(或仅适用于 DB 和/或版本冲突)?

如果是这样:建议使用哪种模式来避免跨实体组的事务性读取出现争用错误?

【问题讨论】:

    标签: python-2.7 google-app-engine google-cloud-datastore app-engine-ndb google-app-engine-python


    【解决方案1】:

    在至少有一次写入的事务中,在本例中为 Key(Target, Foo),Cloud Datastore 会将无操作标记写入已读取但未修改的实体组。这是为了确保可序列化。

    由于Key(OriginGroup, 1) 并且您在很长一段时间内执行 XG 事务的速度超过每秒 1 个,这就是我们争论的根源。

    要考虑的一种替代方法是一次写入 23 个Key(Target, Foo) 实体而不是一个的批处理策略。 Key(Origin, 1)Key(TargetConfig, 1) 占用另外 2 个实体组插槽。

    【讨论】:

    • 谢谢!我不知道“无操作标记”,我不确定我是否理解技术原因。但是了解这些标记会有很大帮助。是的,我可能会选择可以更好地控制请求频率的批处理策略(就像在这种情况下)。在其他情况下,也许在事务之外进行易竞争的读取可能是一个合理的选择。
    猜你喜欢
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-09
    • 2013-09-22
    • 2012-10-08
    相关资源
    最近更新 更多