查询Appengine数据存储区中的N个随机记录

时间:2009-07-09 16:29:18

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

我正在尝试编写一个返回特定类型的N个随机记录的GQL查询。我当前的实现工作但需要N次调用数据存储区。如果可能的话,我想对数据存储区进行1次调用。

我目前为我放入数据存储区的每种类型分配一个随机数。当我查询随机记录时,我生成另一个随机数并查询记录>兰德ORDER BY asc LIMIT 1。

这样可行,但它只返回1条记录,所以我需要进行N次查询。关于如何进行这一查询的任何想法?感谢。

6 个答案:

答案 0 :(得分:5)

“引擎盖下”单个搜索查询调用只能从某个索引返回一组连续的行。这就是为什么一些GQL查询,包括任何使用!=,扩展到多个数据存储区调用。

N个独立的均匀随机选择在任何索引中都不是(通常)连续的。

QED。

您可以使用memcache来存储实体,并降低获取N个实体的成本。或者,如果您不介意索引中的“随机”选择在一起,请在一个查询中选择随机选择的(例如)100块,然后从中随机选择N.由于您有一个已经随机化的字段,因此对于局外人而言,N个项目是相关的并不是显而易见的。至少,直到他们查看大量样本并注意到项目A和Z从未出现在同一组中,因为它们在随机索引中相距超过100。如果性能允许,您可以不时地重新随机化您的实体。

答案 1 :(得分:5)

您在寻找什么样的权衡?如果您愿意在插入这些实体时遇到小小的性能影响,您可以创建一个解决方案来快速获得N个。

以下是您需要做的事情:

插入实体时,请指定密钥。您希望按顺序为实体提供密钥,从1开始,然后从那里开始。 (这需要一些努力,因为app引擎没有自动增量()所以你需要跟踪你在其他实体中使用的最后一个id,让我们称它为IdGenerator)

现在当你需要N个随机实体时,生成1个和你生成的最后一个id之间的N个随机数(你的IdGenerator会知道这个)。然后,您可以使用N键进行批量获取,这只需要一次数据存储,并且也比查询快,因为密钥获取通常比查询AFAIK更快。

这种方法确实需要处理一些恼人的细节:

  1. 如果您动态插入大量这些项目(超过几秒钟),您的IdGenerator可能会成为瓶颈,这需要某种分片的IdGenerator实现。如果所有这些数据都是预先加载的,或者数量不是很高,那么就可以轻松实现。
  2. 您可能会发现某些Id实际上并没有与之关联的实体,因为您删除了它或因为put()在某处失败了。如果发生这种情况,你必须抓住另一个随机实体。 (如果你想获得幻想并减少这种可能性,你可以让IdGenerator使用这个Id来重复使用“填补漏洞”)
  3. 所以问题归结为你需要多快这些N项与你添加和删除它们的频率,以及是否有一点额外的复杂性值得提升性能。

答案 2 :(得分:3)

看起来唯一的方法是将随机整数值存储在每个实体的特殊属性中并查询它。如果您只是添加一个自动初始化的属性,这可以非常自动地完成。

不幸的是,如果您的数据存储区已经填写,则需要处理所有实体。

这很奇怪,我知道。

答案 3 :(得分:1)

我同意Steve的回答,没有这种方法可以在一个查询中检索N个随机行。

然而,即使检索单个实体的方法通常也不起作用,使得返回结果的可能性均匀分布。返回给定实体的概率取决于它随机分配的数字和下一个更高的随机数的差距。例如。如果已经分配了随机数1,2和10(并且没有数字3-9),则算法将比“1”多8次返回“2”。

我已经以稍微宽松的方式解决了这个问题。如果有人有兴趣,我很乐意分享

答案 4 :(得分:0)

我遇到了同样的问题。我决定不将ID分配给我在数据存储区中现有的条目并执行此操作,因为我已经从分片计数器中获得了totalcount。

这会从“totalcount”条目中选择“count”条目,按排序。

    # select $count from the complete set
    numberlist = random.sample(range(0,totalcount),count)
    numberlist.sort()

    pagesize=1000

    #initbuckets
    buckets = [ [] for i in xrange(int(max(numberlist)/pagesize)+1) ]
    for k in numberlist:
        thisb = int(k/pagesize)
        buckets[thisb].append(k-(thisb*pagesize))
    logging.debug("Numbers: %s. Buckets %s",numberlist,buckets)

    #page through results.

    result = []
    baseq =  db.Query(MyEntries,keys_only=True).order("__key__")
    for b,l in enumerate(buckets):
        if len(l) > 0: 
            result += [ wq.fetch(limit=1,offset=e)[0] for e in l ]

        if b < len(buckets)-1: # not the last bucket
            lastkey  = wq.fetch(1,pagesize-1)[0]
            wq = baseq.filter("__key__ >",lastkey)

请注意,这对我来说有点复杂,而且我仍然不相信我没有一个一个或一个一个接一个的错误。

请注意,如果count接近totalcount,这可能会非常昂贵。 并且要注意,在数百万行中,可能无法在追加时间范围内进行。

答案 5 :(得分:-1)

如果我理解正确,你需要检索N个随机实例。

这很容易。只用密钥进行查询。并在键列表结果上执行 random.choice N次。然后通过获取键来获得结果。

keys = MyModel.all(keys_only=True)

n = 5 # 5 random instance

all_keys = list(keys)
result_keys = []

for _ in range(0,n) 
    key = random.choice(all_keys)
    all_keys.remove(key)
    result_keys.append(key)

# result_keys now contain 5 random keys.