google-app-engine – 提高大数据的ndb查询吞吐量

我试图在GAE应用程序中对存储在数据存储区中的数据执行一些数据处理.瓶颈点是查询返回实体的吞吐量,我想知道如何提高查询的性能.

我一般做什么:

>一切都在任务队列中工作,所以我们有足够的时间(10分钟的截止日期).
>我对ndb实体运行查询,以便选择需要处理的实体.
>当查询返回结果时,我将实体分组,比如1000,然后将它们发送到另一个任务队列进行进一步处理.
>存储的数据将会很大(比如500K-1M实体),并且10分钟的截止日期可能还不够.因此,当任务到达任务队列截止日期时,我会生成一个新任务.这意味着我需要一个ndb.Cursor才能从停止的位置继续查询.

问题是查询返回实体的速率.我尝试了几种方法并观察了以下性能(这对我的应用来说太慢了):

在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实例.当我为每个数据段排队任务时,我通过指定’target’选项明确指示哪个基本实例将处理每个任务.

通过这种设计,我能够使用5个B4实例在4-5秒内(而不是40′-60’!)处理总共20,000个实体.

现在,由于Bx实例,这会产生额外的成本.我们必须微调我们需要的基本实例的类型和数量.

点赞