Google 使用游标的 App Engine 数据存储区查询不会迭代所有项目
Google app engine datastore query with cursor won't iterate all items
在我的应用程序中,我有一个带过滤器的数据存储区查询,例如:
datastore.NewQuery("sometype").Filter("SomeField<", 10)
我正在使用游标迭代结果的批次(例如,在不同的任务中)。如果 SomeField
的值在遍历它时发生变化,光标将不再在 google 应用引擎上工作(在 devappserver 上工作正常)。
我这里有一个测试项目:https://github.com/fredr/appenginetest
在我的测试中,我 运行 /db
将使用 10 个值设置为 0 的项目设置数据库,然后 运行 /run/2
将迭代值为的所有项目小于2个,5个为一批,更新每一项的值为2。
我本地 devappserver 上的结果(所有项目都已更新):
appengine上的结果(只更新了五项):
我做错了什么吗?这是一个错误吗?或者这是预期的结果?
在文档中指出:
Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values.
问题出在游标的性质和实现上。游标包含最后处理的实体(编码)的键,因此如果您在执行查询之前将游标设置为查询,数据存储将跳转到游标中编码的键指定的实体,并开始列出实体那一点。
让我们检查一下你的情况
您的查询过滤器是 Value<2
。您遍历查询结果的实体,并将 Value
属性 更改(并保存)为 2
。 注意Value=2
不满足过滤条件Value<2
.
在下一次迭代(下一批)中,存在您正确应用的游标。因此,当 Datastore 执行查询时,它会跳转到上一次迭代中处理的最后一个实体,并希望列出在此之后的实体。但是光标指向的实体可能已经不满足过滤器;因为其新值 2
的索引条目很可能已经更新(非确定性行为 - 请参阅 eventual consistency for more details which applies here because you did not use an Ancestor query which would guarantee strongly consistent 结果;time.Sleep()
延迟只会增加这种情况的可能性)。
所以数据存储看到最后处理的实体不满足过滤器并且不会再次搜索所有实体但报告没有更多实体匹配过滤器,因此不会更新更多实体(并且不会有错误)被举报)。
建议:不要使用游标并按您同时更新的 属性 进行筛选或排序。
顺便说一句:
您引用的 Appengine 文档部分:
Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values.
这不是你想的那样。这意味着:游标可能无法在具有多个值的 属性 上正常工作 并且相同的 属性 包含在不等式过滤器中 或者 用于对结果进行排序。
顺便说一句#2
在屏幕截图中,您使用的是 SDK 1.9.17。最新的 SDK 版本是 1.9.21。您应该更新它并始终使用最新的可用版本。
实现目标的备选方案
1) 不要使用游标
如果您有很多记录,您将无法一步(在一个循环中)更新所有实体,但假设您更新了 300 个实体。如果重复查询,已经更新的实体将不会出现在再次执行相同查询的结果中,因为更新的 Value=2
不满足过滤器 Value<2
。只需重做query+update,直到查询无结果。由于您的更改是 idempotent,因此如果实体的索引条目的更新延迟并且会被查询多次返回,也不会造成任何损害。最好延迟下一个查询的执行,以尽量减少发生这种情况的可能性(例如,在重做查询之间等待几秒钟)。
优点:简单。您已经有了解决方案,只需排除游标处理部分即可。
缺点: 某些实体可能会更新多次(因此更改必须是 idempotent)。此外,对实体执行的更改必须是将实体从下一个查询中排除的内容。
2) 使用任务队列
您可以先执行仅键查询并将更新推迟到使用任务。您可以创建任务,假设将 100 个键传递给每个任务,任务可以通过键加载实体并进行更新。这将确保每个实体只会更新一次。由于涉及到任务队列,该解决方案的延迟会稍大一些,但在大多数情况下这不是问题。
优点:没有重复的更新(因此变化可能是非idempotent)。即使要执行的更改不会从下一个查询中排除实体(更一般)。
缺点: 复杂性较高。更大 lag/delay.
3) 使用 Map-Reduce
您可以使用 map-reduce framework/utility 对许多实体进行大规模并行处理。不确定它是否已在 Go 中实现。
优点:并行执行,甚至可以处理数百万或数十亿个实体。在实体数量较大的情况下要快得多。加上 2) 使用任务队列中列出的优点。
缺点: 复杂性较高。 Go 中可能还不可用。
在我的应用程序中,我有一个带过滤器的数据存储区查询,例如:
datastore.NewQuery("sometype").Filter("SomeField<", 10)
我正在使用游标迭代结果的批次(例如,在不同的任务中)。如果 SomeField
的值在遍历它时发生变化,光标将不再在 google 应用引擎上工作(在 devappserver 上工作正常)。
我这里有一个测试项目:https://github.com/fredr/appenginetest
在我的测试中,我 运行 /db
将使用 10 个值设置为 0 的项目设置数据库,然后 运行 /run/2
将迭代值为的所有项目小于2个,5个为一批,更新每一项的值为2。
我本地 devappserver 上的结果(所有项目都已更新):
appengine上的结果(只更新了五项):
我做错了什么吗?这是一个错误吗?或者这是预期的结果? 在文档中指出:
Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values.
问题出在游标的性质和实现上。游标包含最后处理的实体(编码)的键,因此如果您在执行查询之前将游标设置为查询,数据存储将跳转到游标中编码的键指定的实体,并开始列出实体那一点。
让我们检查一下你的情况
您的查询过滤器是 Value<2
。您遍历查询结果的实体,并将 Value
属性 更改(并保存)为 2
。 注意Value=2
不满足过滤条件Value<2
.
在下一次迭代(下一批)中,存在您正确应用的游标。因此,当 Datastore 执行查询时,它会跳转到上一次迭代中处理的最后一个实体,并希望列出在此之后的实体。但是光标指向的实体可能已经不满足过滤器;因为其新值 2
的索引条目很可能已经更新(非确定性行为 - 请参阅 eventual consistency for more details which applies here because you did not use an Ancestor query which would guarantee strongly consistent 结果;time.Sleep()
延迟只会增加这种情况的可能性)。
所以数据存储看到最后处理的实体不满足过滤器并且不会再次搜索所有实体但报告没有更多实体匹配过滤器,因此不会更新更多实体(并且不会有错误)被举报)。
建议:不要使用游标并按您同时更新的 属性 进行筛选或排序。
顺便说一句:
您引用的 Appengine 文档部分:
Cursors don't always work as expected with a query that uses an inequality filter or a sort order on a property with multiple values.
这不是你想的那样。这意味着:游标可能无法在具有多个值的 属性 上正常工作 并且相同的 属性 包含在不等式过滤器中 或者 用于对结果进行排序。
顺便说一句#2
在屏幕截图中,您使用的是 SDK 1.9.17。最新的 SDK 版本是 1.9.21。您应该更新它并始终使用最新的可用版本。
实现目标的备选方案
1) 不要使用游标
如果您有很多记录,您将无法一步(在一个循环中)更新所有实体,但假设您更新了 300 个实体。如果重复查询,已经更新的实体将不会出现在再次执行相同查询的结果中,因为更新的 Value=2
不满足过滤器 Value<2
。只需重做query+update,直到查询无结果。由于您的更改是 idempotent,因此如果实体的索引条目的更新延迟并且会被查询多次返回,也不会造成任何损害。最好延迟下一个查询的执行,以尽量减少发生这种情况的可能性(例如,在重做查询之间等待几秒钟)。
优点:简单。您已经有了解决方案,只需排除游标处理部分即可。
缺点: 某些实体可能会更新多次(因此更改必须是 idempotent)。此外,对实体执行的更改必须是将实体从下一个查询中排除的内容。
2) 使用任务队列
您可以先执行仅键查询并将更新推迟到使用任务。您可以创建任务,假设将 100 个键传递给每个任务,任务可以通过键加载实体并进行更新。这将确保每个实体只会更新一次。由于涉及到任务队列,该解决方案的延迟会稍大一些,但在大多数情况下这不是问题。
优点:没有重复的更新(因此变化可能是非idempotent)。即使要执行的更改不会从下一个查询中排除实体(更一般)。
缺点: 复杂性较高。更大 lag/delay.
3) 使用 Map-Reduce
您可以使用 map-reduce framework/utility 对许多实体进行大规模并行处理。不确定它是否已在 Go 中实现。
优点:并行执行,甚至可以处理数百万或数十亿个实体。在实体数量较大的情况下要快得多。加上 2) 使用任务队列中列出的优点。
缺点: 复杂性较高。 Go 中可能还不可用。