通过聚合 RPC 调用加速 GAE-Py 中的模板
Posted
技术标签:
【中文标题】通过聚合 RPC 调用加速 GAE-Py 中的模板【英文标题】:Speeding up templates in GAE-Py by aggregating RPC calls 【发布时间】:2011-01-05 19:06:45 【问题描述】:这是我的问题:
class City(Model):
name = StringProperty()
class Author(Model):
name = StringProperty()
city = ReferenceProperty(City)
class Post(Model):
author = ReferenceProperty(Author)
content = StringProperty()
代码并不重要……它的这个 django 模板:
% for post in posts %
<div>post.content</div>
<div>by post.author.name from post.author.city.name</div>
% endfor %
现在假设我使用 Post.all().fetch(limit=100)
获得了前 100 个帖子,然后将此列表传递给模板 - 会发生什么?
它使 200 更多数据存储获取 - 100 获取每个作者,100 获取每个作者的城市。
其实这是完全可以理解的,因为帖子里只提到了作者,而作者只提到了城市。 post.author
和 author.city
对象上的 __get__
访问器透明地执行获取并拉回数据(请参阅this 问题)。
解决这个问题的一些方法是
-
使用
Post.author.get_value_for_datastore(post)
收集作者密钥(参见上面的链接),然后执行批量获取以获取所有信息 - 这里的问题是我们需要重新构建一个模板数据对象......每个模型和处理程序都需要额外的代码和维护。
编写一个访问器,比如cached_author
,它首先检查memcache 中的作者并返回——这里的问题是post.cached_author 将被调用100 次,这可能意味着100 次memcache 调用。
如果数据不必是最新的,则保持对象映射的静态键(并可能在五分钟内刷新一次)。然后cached_author
访问器可以仅引用此映射。
所有这些想法都需要额外的代码和维护,而且它们不是很透明。如果我们能做到呢
@prefetch
def render_template(path, data)
template.render(path, data)
事实证明我们可以...hooks 和 Guido's instrumentation module 都证明了这一点。如果@prefetch
方法通过捕获请求的键来包装模板渲染,我们可以(至少到一个深度级别)捕获正在请求的键,返回模拟对象,并批量获取它们。这可以对所有深度级别重复,直到没有新的密钥被请求。最终渲染可以拦截获取并从地图返回对象。
这会将总共 200 个入口更改为 3,透明且无需任何额外代码。更不用说大大减少了对 memcache 的需求,并在无法使用 memcache 的情况下提供帮助。
问题是我不知道该怎么做(还)。在我开始尝试之前,有没有其他人这样做过?或者有人想帮忙吗?还是您认为该计划存在巨大缺陷?
【问题讨论】:
我在回答中遗漏了一些内容...您可能会或可能不会意识到 RPC 调用与您的 Python 代码相比非常慢,并且它会影响您的配额。 IIRC 数据存储区按键提取大约需要 100 毫秒,因此 200 次提取需要 20 秒。您实际上正在执行 400 次获取(一次从帖子到作者,一次从作者到城市),这样您的页面就会超时。 准确地说...我的实际页面可能只会显示十个帖子,而我正在记录 40 个电话。需要一秒钟多一点,拨打 100 秒内的号码很容易超时。 另外,时区用户万岁> = GMT+5(我假设)!我终于可以在所有西方人之前回答了! :p 大声笑...是的,我没有看到太多东半球人在这里做出贡献。会很好:) 【参考方案1】:我也遇到过类似的情况。而不是 ReferenceProperty,我有父/子关系,但基础是相同的。我目前的解决方案还不够完善,但至少对于具有 200-1,000 个实体的报告和事物来说足够高效,每个实体都有几个需要获取的后续子实体。
您可以手动批量搜索数据并根据需要进行设置。
# Given the posts, fetches all the data the template will need
# with just 2 key-only loads from the datastore.
posts = get_the_posts()
author_keys = [Post.author.get_value_for_datastore(x) for x in posts]
authors = db.get(author_keys)
city_keys = [Author.city.get_value_for_datastore(x) for x in authors]
cities = db.get(city_keys)
for post, author, city in zip(posts, authors, cities):
post.author = author
author.city = city
现在,当您呈现模板时,将不会进行额外的查询或提取。它的边缘很粗糙,但如果没有我刚才描述的这种模式,我就无法生存。
您还可以考虑验证您的所有实体都不是None
,因为如果密钥错误,db.get() 将返回 None。不过,这只是基本的数据验证。同样,如果出现超时等情况,需要重试db.get()。
(最后,我认为memcache不会作为主要解决方案。也许可以作为加速数据存储调用的辅助层,但是如果memcache为空,则需要很好地工作。此外,Memcache本身有几个配额,例如memcache 调用和传输的总数据量。过度使用 memcache 是杀死你的应用程序的好方法。)
【讨论】:
第二个代码块很有帮助...我没有想到 zip 并以这种方式使用它..但第一个块实际上是 sdk 的内置功能...参考是解决一次后就已经缓存了,所以绝对不需要那个代码.. 你是对的。我的代码是从不使用 ReferenceProperty 的类似情况中复制而来的。最好通过一些后门来填充属性,而不是仅仅吹走.city
属性。但我相信这会在紧要关头奏效。
嗯.. 我正在阅读 SDK 中 google/appengine/ext/db/__init__.py 中的代码。它看起来像一个简单的赋值工作正常,因为它会调用 ReferenceProperty 的 __set__() 方法。我会更新答案,使其更简短、更清晰。
Done :) 另外我忘了提一下,我在我的真实代码中使用了 itertools.izip,因为我曾经不时遇到 MemoryErrors。不过一般来说可能没有必要。
itertools 版本更快吗?为什么会有区别? Zip 对我来说似乎是一个非常简单的算法。【参考方案2】:
这里有一些很好的预取示例...
http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine
【讨论】:
虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。以上是关于通过聚合 RPC 调用加速 GAE-Py 中的模板的主要内容,如果未能解决你的问题,请参考以下文章