应用程序引擎:从DB转换到NDB,并且在Query对象中进行迭代时遇到问题

时间:2014-05-21 15:13:09

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

我正在尝试迭代查询,我的代码是为DB设置的。现在我正在使用NDB,我对语法感到困惑。基本上在这段代码中,我正在尝试为Search API构建索引。我的数据库很大(约30000个实体),每个实体都将成为一个文档。我认为最好的方法就是使用任务队列分割工作。

无论如何这里是代码:

    q = Database.query()
    q.filter(Database.title > None)
    numEntities = q.count(limit=50000)
    logging.info(num)
    counter = 0 
    batchsize = 999

    while (counter<numEntities):
        logging.info(counter)
        if (counter == 0):
            resultsFetched = 0
            for p in q:
                resultsFetched+=1
                counter += 1
                if p.identifier:
                    # add code to call worker here
                    taskqueue.add(queue_name='buildSearchIndexWorker',target='buildsearch',url='/tasks/buildSearchIndexWorker',params={'ID':p.identifier})

                    if (resultsFetched == batchsize):
                        a, startCursor, more = q.fetch_page(999)
                        break
        else: 
            q.fetch_page(999,start_cursor = startCursor)
            resultsFetched = 0
            for p in q:
                resultsFetched+=1
                counter += 1
                if p.identifier:
                    # add code to call worker here
                    taskqueue.add(queue_name='buildSearchIndexWorker',target='buildsearch',url='/tasks/buildSearchIndexWorker',params={'ID':p.identifier})

                    if (resultsFetched == batchsize):
                        a, startCursor, more = q.fetch_page(999)
                        break

基本上从我使用数据库时,我认为每个查询有999次上限。此限制是否仍适用于NDB查询?如果是这样,我希望代码一次执行999次拉取,创建任务队列,然后通过使用游标,拉出下一个999个实体。我编写代码的方式是一种迭代查询对象的适当方式吗?谢谢。

2 个答案:

答案 0 :(得分:2)

关于您现有代码的一些评论:

最好避免运行q.count()。在尝试迭代大型查询时,无法确定在任务超时之前您将能够完成整个迭代。其次,q.count(),最后一次检查,仅限于1,000个实体。

我要解决这个问题的简单方法是(主要考虑这个伪代码):

def _iterate_and_index(cursor=None):
    query = MyModel.query(keys_only=True)  # you need to reconstruct your query every time
    results, cursor, more = query.fetch_page(BATCH_SIZE or 999, start_cursor=cursor)

    # Immediately fire this task to continue this query.
    if more:
        deferred.defer(_iterate_and_index, cursor)

    queue = taskqueue.Queue('buildSearchIndexWorker')
    tasks_to_add = []

    for entity_key in results:
        task = taskqueue.Task(target='buildsearch', 
            url='/tasks/buildSearchIndexWorker',
            params={'entity_key':entity_key.urlsafe()})
        tasks_to_add.append(task)

    # Add tasks to queue, 100 at a time (100 is the API limit)
    while tasks_to_add:
        queue.add(tasks_to_add[:100])
        tasks_to_add = tasks_to_add[100:]

有些事情需要注意:

  • 如果在添加所有任务之前终止运行时,则任务将再次运行。如果重要的是单个实体没有被索引两次,请使用任务名来创建幂等性。
  • 我正在以100个批量向队列中添加任务以减少API调用。 100是API限制。
  • 我没有将.identifier传递给任务,而是选择使查询使用keys_only来提高查询效率 - 因为在实际过程中您不需要任何关于实体的数据查询。这将节省您的时间和金钱。
  • 使用more fetch_page将无需使用q.count()
  • I deferred.defer在实际处理批次之前的下一次迭代。这可以保持并行运行并加速整个操作。它还可以防止你的游标变得陈旧。

详细了解deferred library here

我建议您查看this simple Mapper库,该库使用延迟任务(也称为taskqueue)来解决大批量迭代,并允许您通过易于实现的模式解决类似问题。

答案 1 :(得分:1)

您查询构造的问题是您还没有阅读有关ndb查询的文档,并假设多步构造与db相同。

q = Database.query()
q.filter(Database.title > None)
numEntities = q.count(limit=50000)

使用ndb,每次调用查询或过滤器都会创建一个新的查询对象,而不是添加到现有查询中。你的代码应该是

q = Database.query()
q = q.filter(Database.title > None)
numEntities = q.count(limit=50000)

其他一些观点

  1. 如果必须进行计数,请执行keys_only查询。
  2. 我真的不明白为什么要进行计数。我会在一个任务中运行查询,只监控消耗了多少时间,然后当你达到9分钟时,开始一个新的任务,从你到达的地方开始。
  3. fetch_page等仍有限制......
  4. 哦,我会在那里停下来,其他答案刚刚出现并覆盖其余部分。