我试图使用python与ndb实现强一致性。 看起来我错过了一些东西,因为我的阅读表现得像他们并不强烈一致。
查询是:
links = Link.query(ancestor=lead_key).filter(Link.last_status ==
None).fetch(keys_only=True)
if links:
do_action()
关键结构是:
Lead root (generic key) -> Lead -> Website (one per lead) -> Link
我有许多使用TaskQueue并发执行的任务,并且在每个任务结束时执行此查询。有时我会得到太多的争用"更新last_status
字段时的异常,但我使用重试处理它。它会破坏强烈的一致性吗?
当没有do_action()
等于last_status
的链接时,预期的行为是None
。实际行为是不一致的:有时do_action()
被调用两次,有时根本不被调用。
答案 0 :(得分:1)
使用祖先密钥获得强一致性有一个限制:每个实体组每秒限制一次更新。解决此问题的一种方法是对实体组进行分片。 Sharding Counters描述了这项技术。这是一篇旧文章,但据我所知,这个建议仍然合理。
答案 1 :(得分:1)
添加Dave的答案,这是第一件要检查的事情。
有一件事没有很好地记录,可能有点令人惊讶的是,争用也可能是由读操作造成的,而不仅仅是写操作。
每当事务开始时,被访问的实体组(通过读或写操作,无关紧要)都被标记为这样。 too much contention
错误表示太多并行事务同时尝试访问同一实体组。即使没有任何事务真正尝试写,也会发生这种情况!
注意:此争用是由开发服务器模拟的 NOT ,只有在使用真实数据存储区在GAE上部署时才能看到它!
可以增加混淆的是自动重新尝试事务,这可能发生在实际写入冲突或只是普通访问争用之后。这些重试可能会对最终用户显示为可疑的重复执行某些代码路径 - 我怀疑这可能会解释您的do_action()
被调用两次的报告。
通常,当您遇到此类问题时,您必须重新访问您的数据结构和/或您访问它们的方式(您的交易)。除了保持强一致性(可能非常昂贵)的解决方案之外,您可能需要重新检查一致性是否真的必须。在某些情况下,它被添加为一揽子要求只是因为它似乎简化了事情。根据我的经验,它没有:)
答案 2 :(得分:0)
您的示例中没有任何内容可确保您的代码只被调用一次。
目前,我将假设你的" do_action"函数对链接实体执行某些操作,特别是它设置了" last_status"属性。
如果您没有在事务中执行查询和写入链接实体,那么两个不同的请求(任务队列任务)可以从查询中获取结果,然后将新值写入链接实体(最后一次写入覆盖前一个值)。
请记住,即使您确实使用了交易,在事务成功完成之前您也不知道其他人没有尝试过执行写操作。如果您尝试在数据存储区外部执行某些操作(例如,向外部系统发出http请求),这一点很重要,因为您可能会看到来自事务的http请求,这些请求最终会因并发修改异常而失败。