2010-01-16 3 views
1

Voici mon problème:Accélération des modèles dans GAE-Py en agrégeant appels RPC

class City(Model): 
    name = StringProperty() 

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

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

Le code n'a pas d'importance ... ce modèle son django:

{% for post in posts %} 
<div>{{post.content}}</div> 
<div>by {{post.author.name}} from {{post.author.city.name}}</div> 
{% endfor %} 

Maintenant, supposons que je obtenir les 100 premiers messages en utilisant Post.all().fetch(limit=100), et passer cette liste au modèle - que se passe-t-il?

200 plus banque de données obtient - 100 pour obtenir chaque auteur, 100 pour obtenir la ville de chaque auteur.

Ceci est parfaitement compréhensible, en fait, puisque le post a seulement une référence à l'auteur, et l'auteur a seulement une référence à la ville. L'accesseur __get__ sur les objets post.author et author.city permet de récupérer et de récupérer les données de manière transparente (voir la question this).

Quelques façons de contourner ce sont

  1. Utilisez Post.author.get_value_for_datastore(post) pour récupérer les clés de l'auteur (voir le lien ci-dessus), puis faire un lot apprendre à les obtenir tous - le problème est ici que nous devons re- Construire un objet de données de modèle ... quelque chose qui a besoin de code supplémentaire et de maintenance pour chaque modèle et gestionnaire.
  2. Ecrivez un accesseur, disons cached_author, qui vérifie d'abord memcache pour l'auteur et le renvoie - le problème ici est que post.cached_author va être appelé 100 fois, ce qui pourrait probablement signifier 100 appels memcache.
  3. Conservez une clé statique dans la mappe d'objet (et actualisez-la peut-être une fois toutes les cinq minutes) si les données ne doivent pas forcément être à jour. L'accesseur cached_author peut alors simplement se référer à cette carte.

Toutes ces idées nécessitent un code et une maintenance supplémentaires, et elles ne sont pas très transparentes. Et si nous pouvions faire

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

Il s'avère que nous pouvons ... hooks et Guido's instrumentation module à la fois prouver. Si la méthode @prefetch enveloppe un rendu de modèle en capturant quelles clés sont demandées, nous pouvons (au moins à un niveau de profondeur) capturer quelles clés sont demandées, renvoyer des objets simulés, et faire un batch sur eux. Cela pourrait être répété pour tous les niveaux de profondeur, jusqu'à ce qu'aucune nouvelle clé ne soit demandée. Le rendu final pourrait intercepter les get et retourner les objets d'une carte.

Cela changerait un total de 200 pénètre dans , de manière transparente et sans code supplémentaire. Sans oublier de réduire considérablement le besoin de memcache et d'aider dans les situations où memcache ne peut pas être utilisé.

Problème, je ne sais pas comment le faire (encore). Avant que je commence à essayer, quelqu'un d'autre a-t-il fait cela? Ou est-ce que quelqu'un veut aider? Ou voyez-vous une faille massive dans le plan?

+0

Quelque chose que j'omis de ma réponse ... vous pouvez ou ne peut pas se rendre compte que les appels RPC une re * extrêmement * lent par rapport à votre code Python, et il compte contre votre quota. Le magasin de données de l'IIRC récupère par clé environ 100ms, donc 200 fetch prendrait 20 secondes. Vous êtes en train de faire 400 fetchs (un de poste à auteur, un de l'auteur à la ville), ce qui va expirer votre page. – JasonSmith

+0

Précisément ... ma page actuelle ne montrera probablement que 10 messages, et j'enregistre 40 appels. Prend un peu plus d'une seconde, appelle ce nombre dans les années 100 pourrait facilement délai. –

+0

Aussi, hooray pour un utilisateur dans le fuseau horaire> = GMT + 5 (je suppose)! Enfin, je peux répondre avant que tous les Occidentaux le fassent! : p – JasonSmith

Répondre

1

J'ai été dans une situation similaire. Au lieu de ReferenceProperty, j'ai eu des relations parent/enfant mais les bases sont les mêmes. Ma solution actuelle n'est pas polie mais au moins elle est assez efficace pour les rapports et les objets avec 200-1000 entités, chacune avec plusieurs entités enfants qui nécessitent une extraction.

Vous pouvez rechercher manuellement des données par lots et les définir si vous le souhaitez.

# 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 

Maintenant, lorsque vous rendez le modèle, aucune requête ou extraction supplémentaire ne sera effectuée. C'est rude sur les bords mais je ne pourrais pas vivre sans ce modèle que je viens de décrire.

Vous pouvez également envisager de valider qu'aucune de vos entités n'est None car db.get() retournera None si la clé est incorrecte. Cela ne concerne que la validation des données de base. De même, vous devez réessayer db.get() s'il y a un timeout, etc.

(Enfin, je ne pense pas que memcache fonctionnera comme une solution primaire, peut-être en tant que couche secondaire pour accélérer les appels de datastore, Memcache a plusieurs quotas lui-même tels que les appels memcache et le transfert total de données.Utiliser Memcache est un excellent moyen de tuer votre application morte.)

+0

Le deuxième bloc de code est très utile ... Je n'ai pas pensé à zip et à l'utiliser de cette façon .. mais le premier bloc est en fait une fonctionnalité intégrée au sdk ... les références sont déjà en cache après ils sont résolus une fois, donc il n'y a absolument pas besoin de ce code .. –

+0

Vous avez raison. Mon code a été copié à partir d'une situation similaire sans utiliser ReferenceProperty. Ce serait bien de remplir la propriété via une porte dérobée plutôt que de simplement balayer l'attribut '.city'. Mais je crois que cela fonctionnerait à la rigueur. – JasonSmith

+0

Hmm .. Je lis le code dans google/appengine/ext/db/__ init__.py dans le SDK. Il semble qu'une simple affectation fonctionne correctement car elle appellera la méthode __set __() de ReferenceProperty. Je vais mettre à jour la réponse pour être plus court et plus clair. – JasonSmith

Questions connexes