我在Google App Engine中遇到了争用问题,并试图了解正在发生的事情。
我有一个请求处理程序注释:
@ndb.transactional(xg=True, retries=5)
..并且在那段代码中我获取了一些东西,更新了其他一些东西等等。但有时在请求期间会出现像这样的错误:
16:06:20.930 suspended generator _get_tasklet(context.py:329) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
Element {
type: "PlayerGameStates"
name: "hannes2"
}
>
)
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
Element {
type: "PlayerGameStates"
name: "hannes2"
}
>
)
16:06:20.930 suspended generator get(context.py:744) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
Element {
type: "PlayerGameStates"
name: "hannes2"
}
>
)
16:06:20.936 suspended generator transaction(context.py:1004) raised TransactionFailedError(too much contention on these datastore entities. please try again. entity group key: app: "s~my-appname"
path <
Element {
type: "PlayerGameStates"
name: "hannes2"
}
>
)
..然后是堆栈跟踪。如果需要,我可以使用整个堆栈跟踪进行更新,但它有点长。
我不明白为什么会这样。查看我的代码中的行,异常来了,我在一个完全不同的实体(Round)上运行get_by_id
。 &#34; PlayerGameStates&#34;,name&#34; hannes2&#34;错误消息中提到的是另一个实体GameState的父节点,它已经是get_async
:来自数据库之前的几行;
# GameState is read by get_async
gamestate_future = GameState.get_by_id_async(id, ndb.Key('PlayerGameStates', player_key))
...
gamestate = gamestate_future.get_result()
...
很奇怪(?)的事情是,没有对该实体发生的数据存储的写入。我的理解是,如果相同的实体同时更新,则会出现争用错误。或者,如果在短时间内发生了太多写入,则可能会出现争用错误。
但也可以在阅读实体时发生吗? (&#34;暂停生成器获取..&#34; ??)并且,这是在5 ndb.transaction重试之后发生的吗?我无法在日志中看到任何表明已经重试的内容。
非常感谢任何帮助。
答案 0 :(得分:5)
是的,读取和写入操作都会发生争用。
事务开始后 - 在您调用带有@ndb.transactional()
注释的处理程序的情况下 - 任何访问的实体组(通过读或写操作,无关紧要)都会立即标记为这样。在那一刻,不知道在交易结束时是否会有写操作 - 它甚至不重要。
过多的争用错误(与冲突错误不同!)表示太多并行事务同时尝试访问同一实体组。即使没有任何事务真正尝试写,也会发生这种情况!
注意:此争用是由开发服务器模拟的 NOT ,只有在使用真实数据存储区在GAE上部署时才能看到它!
可以增加混淆的是自动重新尝试事务,这可能发生在实际写入冲突或只是普通访问争用之后。这些重试可能对最终用户显示为可疑的重复执行某些代码路径 - 在您的情况下处理程序。
重试实际上可以使事情变得更糟(在短时间内) - 在已经被大量访问的实体组中投入更多访问 - 我已经看到这样的模式,只有在指数退避延迟变得足够大以使事情变得冷却之后,事务才能正常工作通过允许正在进行的事务完成,稍微(如果重试次数足够大)。
我的方法是将大部分事务处理事务移动到推送队列任务上,在事务和任务级别禁用重试,而是完全重新排队任务 - 更少的重试但间隔更远。
通常当您遇到此类问题时,您必须重新访问您的数据结构和/或您访问它们的方式(您的交易)。除了保持强一致性(可能非常昂贵)的解决方案之外,您可能需要重新检查一致性是否真的必须。在某些情况下,它被添加为一揽子要求只是为了简化事情。根据我的经验,它不会:)
另一件事可以帮助(但只是一点点)使用更快(也更昂贵)的实例类型 - 更短的执行时间转化为稍低的交易重叠风险。我注意到这一点,因为我需要一个具有更多内存的实例,这也恰好更快:)