2

使用 Google App Engine 的 NDB 数据存储,如何确保在创建新实体后对实体列表进行高度一致的读取?

示例用例是我有 Employee 类型的实体。

  • 创建一个新的员工实体
  • 立即加载员工列表(包括添加的员工)

我了解以下方法将最终一致地读取可能包含或不包含新员工的员工列表。在后者的情况下,这会导致糟糕的体验。

e = Employee(...)
e.put()
Employee.query().fetch(...)

现在这里有几个我想过的选择:

重要的限定词

我只关心为添加新员工的用户读取的一致列表。我不在乎其他用户是否有最终一致的阅读。

假设我不想将所有员工都放在 Ancestor 下以启用强一致的祖先查询。在成千上万的员工实体的情况下,5 次写入/秒的限制是不值得的。

我们还假设我希望写入和读取列表是两个单独的 HTTP 请求的结果。从理论上讲,我可以将写入和读取都放在一个事务中(?),但那将是一个非常非 RESTful API 端点。

选项1

  • 在数据存储中创建一个新的员工实体
  • 此外,将新员工对象写入内存缓存、本地浏览器 cookie、本地移动存储。
  • 查询数据存储以获取员工列表(最终一致)
  • 如果新员工实体不在此列表中,请将其从 memcache / 本地内存添加到列表(在我的应用程序代码中)
  • 将结果呈现给用户。如果用户选择新的员工实体,则使用 key.get() 检索实体(强一致)。

选项 2

  • 使用事务创建新员工实体
  • 查询数据存储以获取事务中的员工列表

我不确定选项 #2 是否真的有效。

  • 从技术上讲,之前的写事务是否在该实体的读事务发生之前被写入所有服务器?或者这不是正确的行为?
  • 事务(包括 XG)对实体组的数量有限制,并且员工列表(每个都是其自己的实体组)可能会超过此限制。
  • 只读事务与普通读取相比有什么缺点?

想法?选项#1 似乎可行,但要确保后续阅读的一致性似乎需要做很多工作。

4

3 回答 3

1

如果您不使用实体组,则可以执行 key_only 查询和 get_multi(keys) 查找以实现实体一致性。对于新员工,您必须将新密钥传递给 get_multi 的密钥列表。

文档:仅键的全局查询与查找方法的组合将读取最新的实体值。但是需要注意的是,key-only全局查询不能排除在查询时索引还不一致的可能性,这可能导致实体根本没有被检索到。查询的结果可能是基于过滤掉旧的索引值而生成的。总之,仅当应用程序要求允许索引值在查询时还不一致时,开发人员才可以使用仅键全局查询,然后按键查找。

更多信息和魔法在这里:平衡强大和最终的一致性与谷歌云数据存储

于 2015-04-17T02:12:17.680 回答
0

我遇到了同样的问题,选项 #2 并没有真正起作用:使用密钥读取将起作用,但查询可能仍会错过新员工。

选项 #1 可以工作,但只能在同一个请求中。保存的 memcache 键可能随时消失,对同一实例的后续查询或对可能在另一块硬件上运行的另一个实例的查询仍然会错过新员工。

对于一致的查询结果,唯一想到的“解决方案”实际上是不要试图强迫新员工进入结果,而是让事情自然流动,直到它出现。我只是添加一个警告,即创建新用户将需要“一段时间”。如果可以容忍,可能会在原始请求中继续轮询/查询,直到它出现?- 那将是唯一可以确定员工创建事件的地方。

于 2015-04-17T02:12:10.573 回答
0

在我写这篇文章时,这个问题已经很老了。然而,这是一个很好的问题,并且将是长期相关的。

原始问题中的选项 #2 将不起作用。

如果实体创建和后续查询是真正独立的,没有上下文链接它们,那么你真的只是卡住了 - 或者你不在乎。诀窍是几乎总是有一些关系或一些用例必须被覆盖。换句话说,如果查询真的是某种,本质上,即席查询,那么你真的不在乎。在这种情况下,您只需引用 CAP 定理并提醒执行查询的客户端该系统可扩展性有多大。但是,几乎总是,如果您担心最终的一致性,则必须处理一些用例或一组用例。例如,如果您有一个高分列表,则最高分必须位于列表顶部。现在正在查看列表的用户可能刚刚获得最高分。另一个示例可能是,当创建员工时,该员工必须在“新员工”列表中。因此,您通常会利用这些已知案例来平衡所需的吞吐量和一致性。例如,对于高分示例,您可能有能力保留作为高分列表的二级索引(实体)。您总是通过密钥获取它,并且您可以根据需要频繁地对其进行写入,因为大概不会经常生成高分。对于新员工示例,您可以使用您开始建议的方法,将最后一个员工的时间戳存储在 memcache 中。然后,当您查询时,您检查以确保您的列表包括该员工......或类似的东西。因此,您通常会利用这些已知案例来平衡所需的吞吐量和一致性。例如,对于高分示例,您可能有能力保留作为高分列表的二级索引(实体)。您总是通过密钥获取它,并且您可以根据需要频繁地对其进行写入,因为大概不会经常生成高分。对于新员工示例,您可以使用您开始建议的方法,将最后一个员工的时间戳存储在 memcache 中。然后,当您查询时,您检查以确保您的列表包括该员工......或类似的东西。因此,您通常会利用这些已知案例来平衡所需的吞吐量和一致性。例如,对于高分示例,您可能有能力保留作为高分列表的二级索引(实体)。您总是通过密钥获取它,并且您可以根据需要频繁地对其进行写入,因为大概不会经常生成高分。对于新员工示例,您可以使用您开始建议的方法,将最后一个员工的时间戳存储在 memcache 中。然后,当您查询时,您检查以确保您的列表包括该员工......或类似的东西。您总是通过密钥获取它,并且您可以根据需要频繁地对其进行写入,因为大概不会经常生成高分。对于新员工示例,您可以使用您开始建议的方法,将最后一个员工的时间戳存储在 memcache 中。然后,当您查询时,您检查以确保您的列表包括该员工......或类似的东西。您总是通过密钥获取它,并且您可以根据需要频繁地对其进行写入,因为大概不会经常生成高分。对于新员工示例,您可以使用您开始建议的方法,将最后一个员工的时间戳存储在 memcache 中。然后,当您查询时,您检查以确保您的列表包括该员工......或类似的东西。

在 App Engine 和类似系统上平衡写入吞吐量和一致性的代价始终相同。它需要增加模型复杂性/代码复杂性来桥接业务需求。

于 2015-10-02T01:24:26.393 回答