首页 文章

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

提问于
浏览
9

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

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

App Stats

RPC 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 回答

  • 10

    我对此进行了详细研究,问题是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输出:
    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调用小对象的用例,具有较高的预期内存缓存命中率 .

    它似乎也表明,如果谷歌将ndb和/或反序列化的一些关键部分实现为C模块,那么Python App Engine可能会大大加快 .

相关问题