Acelerar las plantillas en GAE-Py mediante la agregación de llamadas RPC

Aquí está mi problema:

class City(Model): name = StringProperty() class Author(Model): name = StringProperty() city = ReferenceProperty(City) class Post(Model): author = ReferenceProperty(Author) content = StringProperty() 

El código no es importante … es esta plantilla django:

 {% for post in posts %} 
{{post.content}}
by {{post.author.name}} from {{post.author.city.name}}
{% endfor %}

Ahora digamos que obtengo las primeras 100 publicaciones usando Post.all().fetch(limit=100) , y paso esta lista a la plantilla, ¿qué sucede?

Hace 200 más almacenes de datos: 100 para obtener cada autor, 100 para obtener la ciudad de cada autor.

Esto es perfectamente comprensible, en realidad, ya que la publicación solo tiene una referencia al autor, y el autor solo tiene una referencia a la ciudad. El __get__ accessor en los objetos post.author y author.city transparente obtiene y recupera los datos (consulte esta pregunta).

Algunas formas de evitar esto son

  1. Use Post.author.get_value_for_datastore(post) para recostackr las claves de autor (vea el enlace anterior), y luego haga un lote para obtenerlas todas. El problema aquí es que necesitamos reconstruir un objeto de datos de plantilla … algo que necesita código adicional y mantenimiento para cada modelo y manejador.
  2. Escriba un cached_author acceso, digamos cached_author , que verifique primero el autor de memcache y lo devuelva. El problema aquí es que post.cached_author se llamará 100 veces, lo que probablemente podría significar 100 llamadas de memcache.
  3. Mantenga una clave estática para asignar el objeto (y actualícela tal vez una vez en cinco minutos) si los datos no tienen que estar muy actualizados. El cached_author puede simplemente referirse a este mapa.

Todas estas ideas necesitan código y mantenimiento adicionales, y no son muy transparentes. ¿Y si pudiéramos hacer?

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

Resulta que podemos … los ganchos y el módulo de instrumentación de Guido lo demuestran. Si el método @prefetch envuelve un procesamiento de plantilla al capturar qué claves se solicitan, podemos (al menos a un nivel de profundidad) capturar qué claves se están solicitando, devolver objetos simulados y obtener un lote de ellos. Esto podría repetirse para todos los niveles de profundidad, hasta que no se soliciten nuevas claves. El render final podría interceptar los objetos recibidos y devolverlos de un mapa.

Esto cambiaría un total de 200 puntos en 3 , de forma transparente y sin ningún código adicional. Por no hablar de reducir enormemente la necesidad de memcache y ayuda en situaciones donde no se puede usar memcache.

El problema es que no sé cómo hacerlo (todavía). Antes de empezar a intentarlo, ¿alguien más ha hecho esto? ¿O alguien quiere ayudar? ¿O ves una falla masiva en el plan?

He estado en una situación similar. En lugar de ReferenceProperty, tuve relaciones entre padres e hijos, pero los conceptos básicos son los mismos. Mi solución actual no está pulida, pero al menos es lo suficientemente eficiente para informes y cosas con 200-1,000 entidades, cada una con varias entidades secundarias subsiguientes que requieren recuperación.

Puede buscar manualmente los datos en lotes y configurarlos si lo desea.

 # 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 

Ahora, cuando renderiza la plantilla, no se realizarán consultas adicionales ni recuperaciones. Es áspero alrededor de los bordes pero no podría vivir sin este patrón que acabo de describir.

También puede considerar validar que ninguna de sus entidades es None porque db.get () devolverá Ninguna si la clave es incorrecta. Sin embargo, eso es entrar en la validación de datos básica. Del mismo modo, debe volver a intentar db.get () si hay un tiempo de espera, etc.

(Por último, no creo que memcache funcione como una solución primaria. Tal vez como una capa secundaria para acelerar las llamadas al almacén de datos, pero es necesario que funcione bien si memcache está vacío. Además, Memcache tiene varias cuotas, como las llamadas a memcache y Total de datos transferidos. El uso excesivo de memcache es una excelente manera de matar a tu aplicación.

Aquí hay algunos grandes ejemplos de pre-captura …

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