我正在尝试在 GAE 应用程序中对存储在数据存储中的数据执行一些数据处理。瓶颈点是查询返回实体的吞吐量,我想知道如何提高查询的性能。
我一般做什么:
问题是查询返回实体的速率。我尝试了几种方法并观察到以下性能(这对我的应用程序来说太慢了):
在 while 循环中使用 fetch_page()。
代码很简单
while has_more and theres_more_time:
entities, cursor, more = query.fetch_page(1000, ...)
send_to_process_queue(entities)
has_more = more and cursor
使用这种方法,处理 10K 个实体需要 25-30 秒。粗略地说,即每分钟 20K 个实体。我尝试更改页面大小或前端实例的类;两者都没有对性能产生任何影响。
对数据进行分段并并行触发多个 fetch_page_async()。
This approach is taken from here (approach C)
整体性能与上述相同。我尝试了不同数量的段(从 2 到 10),以便有 2-10 个并行的 fetch_async() 调用。在所有情况下,总时间保持不变。调用的并行 fetch_page_async() 越多,完成每个操作所需的时间就越长。我还尝试了 20 次并行提取,但情况变得更糟。更改页面大小或前端实例类也没有影响。
使用单个 fetch() 调用获取所有内容。
现在这是最不合适的方法(如果不是不合适的话),因为实例可能会耗尽内存,而且我没有得到光标,以防我需要生成另一个任务(实际上我什至没有能力这样做,任务将简单地超过最后期限)。我出于好奇而尝试了这个,以了解它的性能,并且我观察到了最佳性能! 10K 个实体需要 8-10 秒,大约每分钟 60K 个实体。现在是大约。比 fetch_page() 快 3 倍。我想知道为什么会发生这种情况。
在单个循环中使用 query.iter()。
这与第一种方法类似。这将利用查询迭代器的底层生成器,另外我可以从迭代器获取一个游标,以防我需要生成一个新任务,所以它适合我。使用查询迭代器,它在 16-18 秒内获取了 10K 个实体,这大约是。每分钟 36-40K 个实体。迭代器比 fetch_page 快 30%,但比 fetch() 慢得多。
对于上述所有方法,我尝试了 F1 和 F4 前端实例,数据存储性能没有任何差异。我还尝试更改查询中的 batch_size 参数,但仍然没有任何更改。
第一个问题是为什么 fetch()、fetch_page() 和 iter() 的行为如此不同,以及如何使 fetch_page() 或 iter() 与 fetch() 一样好?然后另一个关键问题是这些吞吐量(每分钟 20-60K 个实体,取决于 api 调用)是否是我们在 GAE 中可以做到的最好的。
我知道 MapReduce API,但我认为它不适合我。 AFAIK,MapReduce API 不支持查询,我不想扫描所有数据存储实体(它成本太高且速度太慢 - 查询可能只返回几个结果)。最后但并非最不重要的是,我必须坚持使用 GAE。诉诸另一个平台对我来说不是一个选择。所以问题真的是如何优化 ndb 查询。
有什么建议么?
最佳答案
如果有人感兴趣,我可以通过重新设计组件来显着提高数据处理的吞吐量 - 有人建议我更改数据模型,但这是不可能的。
首先,我将数据分段,然后在单独的 taskqueue.Task 中处理每个数据段,而不是从单个任务中调用多个 fetch_page_async(如我在第一篇文章中所述)。最初,这些任务由 GAE 仅使用单个 Fx 实例按顺序处理。为了实现任务的并行化,我将组件移动到特定的 GAE 模块并使用基本缩放,即可寻址的 Bx 实例。当我将每个数据段的任务排入队列时,我通过指定“目标”选项明确指示哪个基本实例将处理每个任务。
通过这种设计,我能够使用 5 个 B4 实例在 4-5 秒内(而不是 40'-60'!)总共处理 20.000 个实体。
现在,由于 Bx 实例,这会产生额外的成本。我们必须微调我们需要的基本实例的类型和数量。
关于google-app-engine - 提高 ndb 查询大数据的吞吐量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21941954/