GAE 30子查询限制的解决方法

时间:2015-02-15 00:18:45

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

我正在编写新闻应用程序,我希望让我的用户从包含数十(约60)个来源(Guardian,Times,...)的列表中选择他们喜欢的新闻来源。我有一个新闻实体,其中包含一个索引属性" source"而且我正在寻找一种方法,让我绕过App Engine强加的30个子查询的限制,阻止我使用IN和EQUALS过滤器来获取属于大量来源的所有新闻。

是否有针对此限制的解决方法?

由于

2 个答案:

答案 0 :(得分:2)

请记住,索引很昂贵 - 它们会占用大量空间并增加写入成本。

我会使用不同的设计。而不是60个子查询(如果您的源列表增长到500,会发生什么?)我会使源属性无索引。然后我会加载所有最新消息的列表并将其保存在Memcache中。如果丢失了,可以随时重新加载。当新闻进来时,您还可以轻松地将更多项目添加到此列表中。您还可以根据时间将此列表拆分为块。

现在,当用户拨打电话时,您可以轻松地在内存中过滤此列表。根据您的使用量,这种设计将便宜几十倍或几千倍,并且工作速度更快。最大的区别在于,不是一遍又一遍地为每个用户请求读取相同的实体,而是在需要再次阅读之前阅读它们并提供数千个请求。

答案 1 :(得分:0)

有60个来源,如果用户想要N > 30个,你可以将交互分成2个查询(一个用于30个,第二个用于其余查询)并自行合并结果。当然,有一些实际的限制,因为你不希望最终得到无数量的查询,但这应该可以扩展到远高于你当前的60个来源,而且问题太多了。

例如,在Python中,生成一个查询列表:

def many_source_queries(sources):
    queries = []
    for i in range(0, len(sources), 30):
        queries.append(News.source.IN(sources[i:i+30]))
    return queries

然后合并多个查询的结果当然有很多方法,但是只需要将所有内容提取到列表中的简单方法就相当简单:

def fetch_many_queries(queries):
    return [x for q in queries for x in q.fetch()]

当然,您可以添加过滤器,排序(并对流进行heapq.merge以保持结果顺序)等等。我只是解决了“30子查询限制”。

补充:这只能扩展到一个点(特别是在查询中所需的源数量)。例如,如果实体可以拥有(比方说)600个不同的来源,并且查询需要300个,那么您将获得大约一半的数据存储返回给您(如果每个源的新闻数量大致相同),并且为此目的制作10个单独的查询毫无意义。

IOW,正如我所写,上面“有实际的限制,因为你不想最终得到无数的查询”。在检测到超过某个阈值N的源(商店内容的很大一部分)的查询时,我宁愿用源过滤器创建单个查询,并选择性地忽略在应用级别具有“错误源”的实体。

所以在这种情况下,我会采取不同的方法,例如......:

import itertools as it

def supermany_source_queries(sources):
    return News.query(), set(sources)

def next_up_to_n(n, news_query, sources_set):
    def goodnews(news): return news.source in sources_set
    while True:
        page = list(it.islice(it.ifilter(goodnews, news_query), n))
        if not page: break
        yield page

这里,主线代码首先调用

q, ss = supermany_source_queries(sources)

然后准备来自eq的确切查询q,可能需要.filter和/或.order,然后循环,例如

for onepage in next_up_to_n(pagesize, eq, ss):
    deal_with_page(onepage)

当然,这可以通过几种不同的方式进行考虑(可能最适合自定义类,能够根据所要求的来源数量采取不同的方法),但我再一次尝试强调一般想法,即..:而不是对数百个源使用大量单独的查询,当你因此得到大部分数据存储区时,使用单个查询(所以请自己辞职以适应所有数据存储区的数量,而不是,例如,它的一半,当然取决于其他过滤器和deal_with_page中可能的提前终止),并在应用程序级别使用迭代和选择(使用itertools& c)忽略实际上不感兴趣的实体。