【问题标题】:Strongly consistent queries for root entities in GAE?GAE 中对根实体的强一致性查询?
【发布时间】:2014-04-22 05:39:41
【问题描述】:

我想要一些关于在 Google App Engine 中执行强一致性读/写的最佳方法的建议。

我的数据存储在这样的类中。

class UserGroupData(ndb.Model):
  users_in_group = ndb.StringProperty(repeated=True)
  data = ndb.StringProperty(repeated=True)

我想为此数据编写一个安全的更新方法。据我了解,我需要在这里避免最终一致的读取,因为它们有数据丢失的风险。例如,下面的代码是不安全的,因为它使用了一个最终一致的普通查询:

def update_data(user_id, additional_data):
  entity = UserGroupData.query(UserGroupData.users_in_group==user_id).get()
  entity.data.append(additional_data)
  entity.put()

如果查询返回的实体是陈旧的,数据就会丢失。

为了实现强一致性,我似乎有几个不同的选择。我想知道哪个选项最好:

选项 1:

使用get_by_id(),它始终是强一致的。但是,这里似乎没有一种巧妙的方法来做到这一点。没有直接从user_id 派生UserGroupData 的密钥的干净方法,因为关系是多对一的。要求我的外部客户端存储和发送 UserGroupData 的密钥似乎也有点脆弱和冒险。

选项 2: 将我的实体放在祖先组中,然后执行祖先查询。比如:

def update_data(user_id, additional_data):
  entity = UserGroupData.query(UserGroupData.users_in_group==user_id,
                               ancestor=ancestor_for_all_ugd_entities()).get()
  entity.data.append(additional_data)
  entity.put()

我认为这应该可行,但是将所有 UserGroupData 实体放入一个祖先组似乎是一件极端的事情。它导致写入被限制为 ~1/秒。这似乎是错误的方法,因为每个UserGroupData 实际上在逻辑上是独立的。 我真正想做的是对根实体执行高度一致的查询。有没有办法做到这一点?我注意到一个建议in another answer 基本上对祖先组进行分片。这是可以做到的最好的吗?

选项 3:

第三种选择是执行keys_only 查询,后跟get_by_id(),如下所示:

def update_data(user_id, additional_data):
  entity_key = UserGroupData.query(UserGroupData.users_in_group==user_id,
                                   ).get(keys_only=True)
  entity = entity_key.get()
  entity.data.append(additional_data)
  entity.put()

据我所知,这种方法不会丢失数据,因为我的密钥没有改变,get() 给出了非常一致的结果。但是,我还没有看到任何地方提到过这种方法。这是合理的做法吗?我需要了解它有什么缺点吗?

【问题讨论】:

  • 如果索引尚未使用新实体或删除的实体更新,则选项 3 仍可能丢失数据。您获得的键将是一致的,但索引可能不会。

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


【解决方案1】:

我认为您还将不一致的查询问题与数据的安全更新混为一谈。

如果 user_id 在组中,像示例中的查询 UserGroupData.query(UserGroupData.users_in_group==user_id).get() 将始终只返回一个实体。

如果它只是刚刚添加并且索引不是最新的,那么您将不会获得记录,因此您不会更新记录。

无论获取实体的方法如何,任何更新都应在确保更新一致性的事务内执行。

关于改进查询一致性的祖先,如果您计划拥有多个 UserGroupData 实体,这并不明显。在这种情况下,您为什么要执行 get()。

所以选项 3,可能是您最好的选择,只查询密钥,然后在事务中执行 Key.get() 并更新。请记住,跨组事务仅限于 5 个实体组。

考虑到这种方法,如果查询所基于的索引已过期,则可能会发生 3 件事中的 1 件,

  1. 找不到您想要的记录,因为新添加的用户 ID 未反映在索引中。
  2. 你想要的记录找到了,get() 会一直获取它
  3. 找到了你要的记录,但是userid实际上已经被删除了,索引已经过期了。 get() 将一致地检索索引,并且用户 ID 不存在。

然后您的代码可以决定采取什么行动。

查询特定用户所属且需要更新的所有 UserGroupData 实体的用例是什么?

【讨论】:

  • 谢谢。我担心的情况是找到了我想要的记录,但是返回了旧版本的记录。所以从你说的,这是不可能的? app engine docs 似乎表明它是,或者至少它们非常模棱两可:“最终一致的查询可能偶尔会返回陈旧的结果。非祖先查询总是最终一致的。”
  • 如果您使用查询检索实体,您可能会得到陈旧的结果。按键获取始终是一致的。只是您可能找不到带有查询的项目或找到不再是最新的项目。最终,您如何获得密钥(由查询组成或来自查询)并不重要,key.get() 始终是一致的。
  • 还说“请记住,如果您执行 get、祖先查询或事务中的任何操作,您将始终看到最近写入的数据。”见developers.google.com/appengine/docs/python/datastore/…最后一段
  • 对!那么这段代码可能会给出陈旧的结果:MyModel.query(MyModel.foo==bar).get()(因为这里的get()对查询进行操作,它相当于query.fetch(1)[0],参见here)而这段代码应该保证非陈旧的结果,因为它运行一个键-only 查询后跟一个 get 键:key = MyModel.query(MyModel.foo==bar).get(keys_only=True) 后跟 key.get() 正确吗?
  • 是的,尽管正如我指出的,对于刚刚添加或删除的 foo 值,foo 属性上的索引可能已经过时。 get 也应该在事务中。
猜你喜欢
  • 2019-08-03
  • 1970-01-01
  • 2013-07-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多