【发布时间】:2014-07-24 17:09:28
【问题描述】:
在 App Engine (Python) 中使用 ndb.get_multi() 从 Memcache 获取多个键时,我发现性能非常差。
我正在获取大约 500 个小对象,所有这些对象都在内存缓存中。如果我使用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 性能问题吗?与反序列化成本有关吗?还是内存缓存问题?
我发现如果我不是获取 500 个对象,而是将所有数据聚合到一个 blob 中,我的函数运行时间为 20 毫秒而不是 > 1500 毫秒:
# 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() 配置文件相匹配(因此获取 500 个小对象需要 500 毫秒而不是 1500 毫秒)。但是,它似乎非常缓慢。
【问题讨论】:
-
你是在测试生产环境还是SDK。
-
显示的所有测试均来自生产。
-
500 是很多对象。每个都需要反序列化和重组,这需要时间。单人获得那么糟糕,您是否可能在项目上获得非常好的缓存命中率。当您在 memcache 中没有全部 500 个但只有一半/季度时会发生什么。如果不是一组静态键,你如何识别这组 500 个键?
-
我相信这些对象是使用 protobufs 序列化/反序列化的,它们并不是特别快(至少以前是这样)。
-
多想想发生了什么。 ndb.get_multi 将尝试从潜在的实例级缓存 memcache 中获取每个键,然后失败并转到数据存储区。这不仅仅是用这个键列表获取一堆项目。对于 500 个密钥,需要进行大量工作才能保证您获得 500 个列表中的所有密钥
标签: python performance google-app-engine memcached