通过聚合 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.authorauthor.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 中的模板的主要内容,如果未能解决你的问题,请参考以下文章

SQL中的聚合——如何加速查询

RPC认证

加速自定义聚合函数

加速自定义聚合函数

Terra R - 使用自定义函数加速栅格数据的聚合()

Java中的类聚合关系是否只能通过实例对象来实现?