Ускорение шаблонов в GAE-Py путем объединения вызовов RPC - PullRequest
1 голос
/ 16 января 2010

Вот моя проблема:

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 %}

Теперь предположим, что я получаю первые 100 сообщений, используя Post.all().fetch(limit=100), и передаю этот список шаблону - что происходит?

Это дает 200 больше хранилище данных - 100, чтобы получить каждого автора, 100, чтобы получить город каждого автора.

Это вполне понятно, на самом деле, поскольку пост содержит только ссылку на автора, а автор имеет только ссылку на город. Аксессор __get__ на объектах post.author и author.city прозрачно выполняет получение и получение данных обратно (см. этот вопрос).

Некоторые способы обойти это

  1. Используйте Post.author.get_value_for_datastore(post), чтобы собрать ключи автора (см. Ссылку выше), а затем выполнить пакет, чтобы получить их все - проблема здесь в том, что нам нужно пересоздать объект данных шаблона ... что-то, что нуждается в дополнительном коде и обслуживании для каждой модели и обработчика.
  2. Напишите метод доступа, скажем, cached_author, который сначала проверяет memcache для автора и возвращает это - проблема здесь в том, что post.cached_author будет вызываться 100 раз, что, вероятно, может означать 100 вызовов memcache.
  3. Удерживайте статический ключ на карте объекта (и обновляйте его, возможно, раз в пять минут), если данные не должны быть очень актуальными. Аксессор cached_author может просто обратиться к этой карте.

Все эти идеи требуют дополнительного кода и обслуживания, и они не очень прозрачны. Что если бы мы могли сделать

@prefetch
def render_template(path, data)    
  template.render(path, data)

Оказывается, мы можем ... крючки и Модуль инструментов Гвидо оба доказывают это. Если метод @prefetch обертывает шаблонный рендер, фиксируя, какие ключи запрашиваются, мы можем (по крайней мере, до одного уровня глубины) захватить, какие ключи запрашиваются, вернуть фиктивные объекты и выполнить пакетное получение их. Это может повторяться для всех уровней глубины, пока новые ключи не запрашиваются. Финальный рендер может перехватывать объекты get и возвращать объекты с карты.

Это может изменить в общей сложности 200 на 3 , прозрачно и без дополнительного кода. Не говоря уже о значительном сокращении потребности в memcache и помощи в ситуациях, когда memcache не может быть использован.

Проблема в том, что я не знаю, как это сделать (пока). Прежде чем я попробую, кто-нибудь еще сделал это? Или кто-нибудь хочет помочь? Или вы видите огромный недостаток в плане?

Ответы [ 2 ]

1 голос
/ 16 января 2010

Я был в похожей ситуации. Вместо ReferenceProperty у меня были отношения родитель / потомок, но основы те же. Мое текущее решение не является идеальным, но, по крайней мере, оно достаточно эффективно для отчетов и вещей с 200-1000 сущностями, каждая с несколькими последующими дочерними сущностями, которые требуют выборки.

Вы можете вручную искать данные партиями и устанавливать их, если хотите.

# 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 - отличный способ убить ваше приложение мертвым.)

0 голосов
/ 21 апреля 2010

Вот несколько замечательных примеров предварительной выборки ...

http://blog.notdot.net/2010/01/ReferenceProperty-prefetching-in-App-Engine

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...