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

 火俊逸香嘉孝 发布于 2023-01-11 18:25

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

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

应用统计RPC统计

如您所见,所有数据都是从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 个回答
  • 我对此进行了详细研究,问题是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输出: 用于手动memcache获取和反序列化的app stats

    现在将其与使用ndb.get_multi(keys)以下内容 获取完全相同的实体进行比较ndb获取相同的项目

    差不多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可能会大大加快.

    2023-01-11 18:26 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有