在使用ndb.get_multi()
App Engine(Python)从Memcache中获取多个密钥时,我发现性能非常差.
我正在获取~500个小对象,所有这些对象都在memcache中.如果我这样做ndb.get_multi(keys)
,需要1500毫秒或更长时间.以下是App Stats的典型输出:
和
如您所见,所有数据都是从memcache提供的.据报道,大部分时间都在RPC调用之外.但是,我的代码尽可能少,所以如果花费在CPU上的时间必须在ndb内部:
# Get set of keys for items. This runs very quickly. item_keys = memcache.get(items_memcache_key) # Get ~500 small items from memcache. This is very slow (~1500ms). items = ndb.get_multi(item_keys)
您在App Stats中看到的第一个memcache.get是获取一组键的单次提取.第二个memcache.get是ndb.get_multi
调用.
我提取的项目非常简单:
class Item(ndb.Model): name = ndb.StringProperty(indexed=False) image_url = ndb.StringProperty(indexed=False) image_width = ndb.IntegerProperty(indexed=False) image_height = ndb.IntegerProperty(indexed=False)
这是某种已知的ndb性能问题吗?与反序列化成本有关吗?或者它是一个memcache问题?
我发现如果不是取出500个对象,而是将所有数据聚合成一个blob,我的函数在20ms而不是> 1500ms运行:
# Get set of keys for items. This runs very quickly. item_keys = memcache.get(items_memcache_key) # Get individual item data. # If we get all the data from memcache as a single blob it is very fast (~20ms). item_data = memcache.get(items_data_key) if not item_data: items = ndb.get_multi(item_keys) flat_data = json.dumps([{'name': item.name} for item in items]) memcache.add(items_data_key, flat_data)
这很有趣,但对我来说并不是真正的解决方案,因为我需要获取的项目集不是静态的.
我看到的表现是典型的还是预期的?所有这些测量都在默认的App Engine生产配置(F1实例,共享内存缓存)上.是否反序列化成本?或者由于从memcache中获取多个键可能?我不认为问题是实例加速时间.我使用time.clock()调用逐行分析代码,我看到大致相似的数字(比我在AppStats中看到的快3倍,但仍然非常慢).这是一个典型的配置文件:
# Fetch keys: 20 ms # ndb.get_multi: 500 ms # Number of keys is 521, fetch time per key is 0.96 ms
更新:出于兴趣,我还对此进行了分析,将所有应用引擎性能设置增加到最大值(F4实例,2400Mhz,专用内存缓存).表现并没有好多少.在更快的实例上,App Stats时序现在与我的time.clock()配置文件匹配(所以500ms来获取500个小对象而不是1500ms).但是,它看起来似乎非常缓慢.
我对此进行了详细研究,问题是ndb和Python,而不是memcache.事情如此缓慢的原因部分是反序列化(大约30%的时间解释),其余的似乎是ndb的任务队列实现的开销.
这意味着,如果你真的想要,你可以避免使用ndb,而是直接从memcache中获取和反序列化.在我的500个小实体的测试用例中,这提供了2.5倍的加速(在生产中的F1实例上为650ms vs 1600ms,在F4实例上为200ms vs 500ms).这个要点显示了如何做到这一点:https: //gist.github.com/mcummins/600fa8852b4741fb2bb1
以下是手动memcache获取和反序列化的appstats输出:
现在将其与使用ndb.get_multi(keys)
以下内容
获取完全相同的实体进行比较
差不多3倍!!
每个步骤的分析如下所示.请注意,时序与appstats不匹配,因为它们在F1实例上运行,所以实时是3倍时钟时间.
手动版:
# memcache.get_multi: 50.0 ms # Deserialization: 140.0 ms # Number of keys is 521, fetch time per key is 0.364683301344 ms
vs ndb版本:
# ndb.get_multi: 500 ms # Number of keys is 521, fetch time per key is 0.96 ms
因此,即使实体具有单个属性并且在memcache中,ndb每个实体获取1ms也需要1ms.这是在F4实例上.在F1实例上需要3ms.这是一个严重的实际限制:如果您希望保持合理的延迟,则在处理F1实例上的用户请求时,您无法获取超过约100个任何类型的实体.
很明显,ndb正在做一些非常昂贵的事情(至少在这种情况下)是不必要的.我认为这与它的任务队列及其设置的所有未来有关.是否值得绕过ndb并手动操作取决于您的应用程序.如果你有一些memcache未命中,那么你将不得不去做数据存储提取.所以你最终部分重新实现了ndb.但是,由于ndb似乎有如此巨大的开销,这可能值得做.至少它看起来是基于我的大量get_multi调用小对象的用例,具有较高的预期内存缓存命中率.
它似乎也表明,如果谷歌将ndb和/或反序列化的一些关键部分实现为C模块,那么Python App Engine可能会大大加快.