App Engine memcache / ndb.get_multi的性能问题

时间:2014-06-04 06:50:53

标签: python performance google-app-engine memcached

在使用App Engine(Python)中的ndb.get_multi()从Memcache中获取多个密钥时,我发现性能非常差。

我正在获取~500个小对象,所有这些对象都在memcache中。如果我使用ndb.get_multi(keys)执行此操作,则需要1500毫秒或更长时间。以下是App Stats的典型输出:

App StatsRPC 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)。但是,它看起来似乎非常缓慢。

1 个答案:

答案 0 :(得分:10)

我对此进行了详细研究,问题是ndb和Python,而不是memcache。事情如此缓慢的原因部分是反序列化(大约30%的时间解释),其余的似乎是ndb的任务队列实现的开销。

这意味着,如果你真的想要,你可以避免使用ndb,而是直接从memcache中获取和反序列化。在我的500个小实体的测试用例中,这提供了2.5倍的加速(在生产中的F1实例上为650ms对比1600ms,对于F4实例为200ms对比500ms)。 这个要点显示了如何做到这一点: https://gist.github.com/mcummins/600fa8852b4741fb2bb1

以下是手动memcache获取和反序列化的appstats输出: app stats for manual memcache fetch and deserialization

现在将其与使用ndb.get_multi(keys)获取完全相同的实体进行比较: ndb fetch of same items

几乎差不多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调用小对象的用例,具有较高的预期memcache命中率。

似乎也有人建议,如果谷歌要将ndb和/或反序列化的一些关键位实现为C模块,那么Python App Engine可能会大大加快。