ndb.query.count() 在大实体上超时 60 秒

4 投票
3 回答
2741 浏览
提问于 2025-04-17 14:36

在谷歌的数据存储中,如果有超过10万个实体,使用ndb.query().count()来计算数量时,可能会因为超时而被取消,即使有索引也不行。我试过使用produce_cursors选项,但只有iter()或fetch_page()能返回游标,而count()却不能。

那么,怎么才能计算大量的实体呢?

3 个回答

1

使用谷歌应用引擎的后端服务会更好。后端服务不受用户请求的60秒限制,也没有任务的10分钟限制,可以一直运行下去。想了解更多,可以查看这里的文档:https://developers.google.com/appengine/docs/java/backends/overview

2

这确实是个让人头疼的问题。我最近在这个领域做了一些工作,主要是想获取一些统计数据——也就是满足某个查询条件的实体数量。count()这个功能听起来不错,但由于数据存储的远程调用超时,它的效果受到了限制。

如果count()能支持游标就好了,这样你就可以在结果集中逐个处理,简单地把结果加起来,而不是返回一大堆键然后又把它们丢掉。有了游标,你可以跨越所有1分钟或10分钟的时间段,采用“接力”的方式继续处理。使用count()(而不是fetch(keys_only=True))可以大大减少资源浪费,并希望能提高远程调用的速度。例如,使用fetch(keys_only=True)方法来统计到1,000,000的数量,所花费的时间是非常惊人的,这对后端来说是个很大的负担。

如果你只需要或想要定期的统计数据(比如每天统计一下系统中所有账户的数量,按国家分类),那么分片计数器就显得很繁琐。

2

如果你想做一些比较复杂的事情,可以看看 任务队列 Python API。基于这个任务队列 API,Google App Engine 提供了 延迟库,我们可以用它来简化后台任务的处理过程。

下面是一个如何在你的应用中使用延迟库的例子:

import logging

def count_large_query(query):
  total = query.count()
  logging.info('Total entities: %d' % total)

然后你可以在你的应用中这样调用上面的函数:

from google.appengine.ext import deferred

# Somewhere in your request:
deferred.defer(count_large_query, ndb.query())

虽然我还不确定 count() 在这么大的数据存储下会不会返回结果,但你可以试试这个 count_large_query() 函数,它使用了游标(未经测试):

LIMIT = 1024
def count_large_query(query):
  cursor = None
  more = True
  total = 0
  while more:
    ndbs, cursor, more = query.fetch_page(LIMIT, start_cursor=cursor, keys_only=True)
    total += len(ndbs)

  logging.info('Total entitites: %d' % total)

如果想在本地测试上面的代码,可以把 LIMIT 设置为 4,然后在你的控制台查看是否能看到 Total entities: ## 这一行。


正如 Guido 在评论中提到的,这种方法也无法扩展:

这仍然无法扩展(虽然可能会暂时缓解问题)。一个任务的时间限制是 10 分钟,而不是 1 分钟,所以你可能可以计算 10 倍的实体。但这代价不小!如果你想好好解决这个问题,可以搜索一下分片计数器(不幸的是,这需要很多工作)。

所以你可能想看看 编写可扩展应用的最佳实践,尤其是 分片计数器

撰写回答