使用Google App Engine的NDB数据存储区,如何确保在创建新实体后对实体列表进行高度一致的读取?
示例用例是我有Employee类的实体。
据我所知,下面的方法最终会对员工列表进行一致读取,这些员工可能包含也可能不包含新员工。这会导致后者的不良体验。
e = Employee(...)
e.put()
Employee.query().fetch(...)
现在我有一些我想过的选择:
重要资格
我只关心添加新员工的用户的一致列表。我不在乎其他用户是否有最终的一致性阅读。
我们假设我不想将所有员工置于祖先之下来启用强烈一致的祖先查询。在成千上万的员工实体中,5次写入/秒限制是不值得的。
我们还假设我希望写入和列表读取是两个单独的HTTP请求的结果。理论上,我可以将写入和读取放入单个事务(?)中,但那将是一个非RESTful API端点。
选项1
选项2
我不确定选项#2是否真的有效。
思考?选项#1似乎可行,但似乎需要做很多工作才能确保后续阅读的一致性。
答案 0 :(得分:1)
如果您不使用实体组,则可以执行key_only查询和get_multi(keys)查找实体一致性。对于新员工,您必须将新密钥传递给get_multi的密钥列表。
文档:仅使用键的全局查询与查找方法的组合将读取最新的实体值。但是应该注意,仅密钥全局查询不能排除在查询时索引尚未一致的可能性,这可能导致实体根本不被检索。可能基于过滤掉旧索引值来生成查询的结果。总之,只有当应用程序要求允许在查询时索引值不一致时,开发人员才可以使用仅键全局查询,然后按键查找。
此处有更多信息和魔力:Balancing Strong and Eventual Consistency with Google Cloud Datastore
答案 1 :(得分:0)
我遇到了同样的问题,选项#2并没有真正起作用:使用密钥进行读取会有效,但查询可能仍会错过新员工。
选项#1可以工作,但只能在同一个请求中工作。保存的memcache密钥可以随时消失,对同一个实例的后续查询或另一个可能在另一个hw上运行的实例上的查询仍然会遗漏新员工。
唯一的解决方案"对于一致的查询结果,我想到的是实际上不会试图强迫新员工进入结果,而是让事情自然流畅,直到结果为止。我只是添加一个警告,即创建新用户需要花费一段时间"。如果可以忍受,可以在原始请求中保持轮询/查询,直到它出现? - 这将是确定知道员工创建活动的唯一地方。
答案 2 :(得分:0)
这个问题在我写这篇文章时已经过时了。但是,这是一个很好的问题,并且具有长期相关性。
原始问题中的选项#2不起作用。
如果实体创建和后续查询是真正独立的,没有上下文链接它们,那么你真的只是卡住了 - 或者你不在乎。诀窍是几乎总是存在某种关系或必须涵盖的一些用例。换句话说,如果查询真的是某种,实质上是即席查询,那么你真的不在乎。在这种情况下,您只需引用CAP定理并提醒执行查询的客户端该系统可扩展的程度。但是,几乎总是,如果您担心最终的一致性,则必须处理一些用例或一组案例。例如,如果您有高分列表,则最高分必须位于列表顶部。最高得分可能刚刚由正在查看列表的用户实现。另一个例子可能是,当创建员工时,该员工必须在“新员工”列表中。 因此,您通常会利用这些已知案例来平衡所需的吞吐量和一致性。例如,对于高分示例,您可能能够保留作为高分列表的二级索引(实体)。你总是按键获得它,你可以根据需要经常写它,因为通常不会产生高分。对于新员工示例,您可以使用您开始建议的方法,方法是将最后一名员工的时间戳存储在memcache中。然后,当您查询时,检查以确保您的列表包含该员工......或者其他类似的内容。
在App Engine和类似系统上平衡写入吞吐量和一致性的代价总是相同的。它需要增加模型复杂性/代码复杂性以满足业务需求。