Ndb 强一致性和频繁写入

Ndb strong consistency and frequent writes

我正在尝试使用 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 字段时遇到 "too much contention" 异常,但我使用重试来处理它。能不能打破强一致性?

last_status 等于 None 的链接已不存在时,预期的行为是调用 do_action()。实际行为是不一致的:有时 do_action() 被调用两次,有时根本不被调用。

使用祖先键获得强一致性有一个限制:每个实体组每秒只能更新一次。解决此问题的一种方法是对实体组进行分片。 Sharding Counters 描述了该技术。这是一篇旧文章,但据我所知,这个建议仍然有效。

添加到 Dave 的回答中,这是第一个要检查的内容。

有一件事没有得到很好的记录并且可能有点令人惊讶,即争用也可能由读取操作引起,而不仅仅是写入操作。

每当事务开始时,被访问的实体组(通过读或写操作,无关紧要)被标记为这样。 too much contention 错误表示太多并行事务同时尝试访问同一实体组。即使 none 个事务实际尝试写入,它也会发生!

注意:这个争用不是开发服务器模拟的,只有部署在GAE上才能看到,真正数据存储!

事务的自动重试可能会增加混乱,这可能发生在实际写入冲突或只是简单的访问争用之后。这些重试在最终用户看来可能是某些代码路径的可疑重复执行 - 我怀疑这可以解释您关于 do_action() 被调用两次的报告。

通常当您 运行 遇到此类问题时,您必须重新访问您的数据结构 and/or 您访问它们的方式(您的事务)。除了保持强一致性(这可能非常昂贵)的解决方案之外,您可能需要重新检查一致性是否真的是必须的。在某些情况下,它被添加为一揽子要求只是因为似乎可以简化事情。根据我的经验,它不会:)

您的示例中没有任何内容可以确保您的代码仅被调用一次。

目前,我假设您的 "do_action" 函数对 Link 实体做了一些事情,特别是它设置了 "last_status" 属性。

如果您不在事务中执行查询和写入 Link 实体,那么两个不同的请求(任务队列任务)可能会从查询中获取结果,然后两者将它们的新值写入 Link 实体(最后一次写入会覆盖先前的值)。

请记住,即使您确实使用了事务,在事务成功完成之前您也不知道没有其他人尝试执行写入。如果您尝试在数据存储外部执行某些操作(例如,向外部系统发出 http 请求),这一点很重要,因为您可能会看到来自事务的 http 请求最终会因并发修改异常而失败。