Comment fonctionne la mise en cache basée sur les clés?

10

J'ai récemment lu un article sur le blog 37Signals et je me demande comment il obtient la clé de cache.

C'est bien d'avoir une clé de cache qui inclut l'horodatage de l'objet (cela signifie que lorsque vous mettez à jour l'objet, le cache sera invalidé); mais comment utilisez-vous ensuite la clé de cache dans un modèle sans provoquer un hit de base de données pour l'objet même que vous essayez de récupérer dans le cache.

Plus précisément, comment cela affecte-t-il les relations un à plusieurs dans lesquelles vous affichez les commentaires d'un message, par exemple.

Exemple dans Django:

{% for comment in post.comments.all %}
   {% cache comment.pk comment.modified %}
     <p>{{ post.body }}</p>
   {% endcache %}
{% endfor %}

La mise en cache dans Rails est-elle différente des simples demandes de memcached par exemple (je sais qu'ils convertissent votre clé de cache en quelque chose de différent). Cache-t-il également la clé de cache?

Dominic Santos
la source
Jetez un œil à rossp.org/blog/2012/feb/29/fragment-caching pour un exemple Django!
vdboor
J'ai déjà jeté un œil à cela et cela semble souffrir exactement du même problème. Les données qu'il essaie de mettre en cache sont nécessaires pour accéder au cache. La seule chose sur laquelle il semble faire des économies réside dans l'opération interne coûteuse, contrairement à la plupart des cas d'utilisation pour ce type de mise en cache.
Dominic Santos
C'est vrai, cela arrive aussi avec le code 37signals, il se concentre sur le code de rendu. L'astuce consiste à mettre en cache toute la liste dans un autre conteneur ou à mettre en cache la récupération de l'objet ailleurs.
vdboor
En fait, leur stratégie de mise en cache semble un peu plus instruite. Je recommande également cet article: 37signals.com/svn/posts/…
JensG
Il semble que votre extrait de code comporte une faute de frappe - était-il post.bodycensé l'être comment.body?
Izkata

Réponses:

3

Pour mettre en cache un vidage direct d'un seul objet déjà chargé, oui, vous ne gagnez rien ou presque rien. Ce n'est pas ce que ces exemples décrivent - ils décrivent une hiérarchie, où tout changement vers quelque chose de plus bas devrait également déclencher une mise à jour de tout ce qui est plus haut dans la hiérarchie.

Le premier exemple, du blog 37signals, utilise Project -> Todolist -> Todocomme hiérarchie. Un exemple rempli pourrait ressembler à ceci:

Project: Foo (last_modified: 2014-05-10)
   Todolist:  Bar1 (last_modified: 2014-05-10)
       Todo:  Bang1 (last_modified: 2014-05-09)
       Todo:  Bang2 (last_modified: 2014-05-09)

   Todolist:  Bar2 (last_modified: 2014-04-01)
       Todo:  Bang3 (last_modified: 2014-04-01)
       Todo:  Bang4 (last_modified: 2014-04-01)

Donc, disons a Bang3été mis à jour. Tous ses parents sont également mis à jour:

Project: Foo (last_modified: 2014-05-16)
   Todolist:  Bar2 (last_modified: 2014-05-16)
       Todo:  Bang3 (last_modified: 2014-05-16)

Ensuite, quand vient le temps de rendre, le chargement à Projectpartir de la base de données est fondamentalement inévitable. Vous avez besoin d'un point pour commencer. Cependant, comme last_modifiedc'est un indicateur de tous ses enfants , c'est ce que vous utilisez comme clé de cache avant de tenter de charger les enfants.


Bien que les articles de blog utilisent des modèles distincts, je vais les regrouper en un seul. Espérons que voir l'interaction complète en un seul endroit le rendra un peu plus clair.

Ainsi, le modèle Django pourrait ressembler à ceci:

{% cache 9999 project project.cache_key %}
<h2>{{ project.name }}<h2>
<div>
   {% for list in project.todolist.all %}
   {% cache 9999 todolist list.cache_key %}
      <ul>
         {% for todo in list.todos.all %}
            <li>{{ todo.body }}</li>
         {% endfor %}
      </ul>
   {% endcache %}
   {% endfor %}
</div>
{% endcache %}

Supposons que nous transmettions un projet qui cache_keyexiste toujours dans le cache. Étant donné que nous propagons les modifications de tous les objets associés au parent, le fait que cette clé particulière existe toujours signifie que tout le contenu rendu peut être extrait du cache.

Si ce projet particulier venait d'être mis à jour - par exemple, comme avec Fooci-dessus - alors il devra rendre ses enfants, et alors seulement il exécutera la requête pour tous les Todolists pour ce projet. De même pour une Todolist spécifique - si la clé cache_key de cette liste existe, alors les todos qu'elle contient n'ont pas changé, et le tout peut être extrait du cache.

Notez également comment je n'utilise pas todo.cache_keydans ce modèle. Cela ne vaut pas la peine, car comme vous le dites dans la question, bodya déjà été retiré de la base de données. Cependant, les hits de base de données ne sont pas la seule raison pour laquelle vous pouvez mettre en cache quelque chose. Par exemple, prendre du texte de balisage brut (tel que ce que nous tapons dans des boîtes de questions / réponses sur StackExchange) et le convertir en HTML peut prendre suffisamment de temps pour que la mise en cache du résultat soit plus efficace.

Si tel était le cas, la boucle interne du modèle pourrait ressembler davantage à ceci:

         {% for todo in list.todos.all %}
            {% cache 9999 todo todo.cache_key %}
               <li>{{ todo.body|expensive_markup_parser }}</li>
            {% endcache %}
         {% endfor %}

Donc, pour tout rassembler, revenons à mes données d'origine en haut de cette réponse. Si nous supposons:

  • Tous les objets avaient été mis en cache dans leur état d'origine
  • Bang3 vient d'être mis à jour
  • Nous rendons le modèle modifié (y compris expensive_markup_parser)

Ensuite, voici comment tout serait chargé:

  • Foo est récupéré de la base de données
  • Foo.cache_key (2014-05-16) n'existe pas dans le cache
  • Foo.todolists.all()est interrogé: Bar1et Bar2sont récupérés de la base de données
  • Bar1.cache_key(2014-05-10) existe déjà dans le cache ; récupérer et sortir
  • Bar2.cache_key (2014-05-16) n'existe pas dans le cache
  • Bar2.todos.all()est interrogé: Bang3et Bang4sont récupérés de la base de données
  • Bang3.cache_key (2014-05-16) n'existe pas dans le cache
  • {{ Bang3.body|expensive_markup_parser }} est rendu
  • Bang4.cache_key(2014-04-01) existe déjà dans le cache ; récupérer et sortir

Les économies du cache dans ce petit exemple sont:

  • Coup de base de données évité: Bar1.todos.all()
  • expensive_markup_parser3 fois évité: Bang1, Bang2etBang4

Et bien sûr, la prochaine fois qu'il sera visualisé, Foo.cache_keyil sera trouvé, donc le seul coût du rendu est la récupération Fooseule de la base de données et l'interrogation du cache.

Izkata
la source
-2

Votre exemple est bon s'il nécessite une récupération ou un traitement des données pour chaque commentaire. Si vous prenez simplement le corps et l'affichez - le cache sera inutile. Mais vous pouvez mettre en cache tous les arbres de commentaires (y compris {% pour%}). Dans ce cas, vous devez l'invalider avec chaque commentaire ajouté, vous pouvez donc placer l'horodatage du dernier commentaire ou le nombre de commentaires quelque part dans Post et créer une clé de cache de commentaires avec. Si vous préférez des données plus normalisées et utilisez des commentaires sur une seule page, vous pouvez simplement effacer une clé de cache lors de l'enregistrement du commentaire.

Pour moi, enregistrer le nombre de commentaires dans Post semble assez bon (si vous ne permettez pas de supprimer et de modifier les commentaires) - vous avez une valeur à afficher n'importe où avec la publication et une clé pour la mise en cache.

ilvar
la source