Come funziona la memorizzazione nella cache basata su chiavi?


10

Di recente ho letto un articolo sul blog di 37Signals e mi chiedo come possano ottenere la chiave di cache.

Va bene avere una chiave cache che includa il timestamp dell'oggetto (questo significa che quando aggiorni l'oggetto la cache verrà invalidata); ma come si usa la chiave della cache in un modello senza causare un hit DB per l'oggetto che si sta tentando di recuperare dalla cache.

In particolare, in che modo ciò influisce sulle relazioni One to Many in cui, ad esempio, stai eseguendo il rendering dei commenti di un post.

Esempio in Django:

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

La memorizzazione nella cache in Rails è diversa, ad esempio, solo dalle richieste di memcached (so che convertono la chiave della cache in qualcosa di diverso). Inoltre memorizzano nella cache la chiave cache?


Dai un'occhiata a rossp.org/blog/2012/feb/29/fragment-caching per un esempio di Django!
vdboor

L'ho già visto e sembra che soffra esattamente dello stesso problema. I dati che sta cercando di memorizzare nella cache sono necessari per accedere alla cache. L'unica cosa su cui sembra risparmiare è nella costosa operazione interna che è diversa dalla maggior parte dei casi d'uso per questo tipo di memorizzazione nella cache.
Dominic Santos,

È vero, succede anche con il codice 37signals, è focalizzato sul codice di rendering. Il trucco è mettere in cache l'intero elenco anche in un altro contenitore o memorizzare nella cache il recupero dell'oggetto altrove.
vdboor

In realtà la loro strategia di memorizzazione nella cache sembra un po 'più istruita. Consiglio anche questo articolo: 37signals.com/svn/posts/…
JensG

Sembra che lo snippet di codice abbia un errore di battitura - doveva post.bodyessere comment.body?
Izkata,

Risposte:


3

Per memorizzare nella cache un dump diretto di un singolo oggetto già caricato, sì, non ottieni nulla o quasi nulla. Non è quello che descrivono quegli esempi: descrivono una gerarchia, in cui qualsiasi modifica a qualcosa di più basso dovrebbe anche innescare un aggiornamento a tutto ciò che è più in alto nella gerarchia.

Il primo esempio, dal blog 37signals, usa Project -> Todolist -> Todocome gerarchia. Un esempio popolato potrebbe apparire così:

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)

Quindi, supponiamo che sia Bang3stato aggiornato. Tutti i suoi genitori vengono anche aggiornati:

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

Quindi, quando arriva il momento del rendering, il caricamento Projectdal database è praticamente inevitabile. È necessario un punto per iniziare. Tuttavia, poiché last_modifiedè un indicatore di tutti i suoi figli , è quello che usi come chiave della cache prima di tentare di caricare i figli.


Mentre i post del blog utilizzano modelli separati, li raggrupperò in uno solo. Speriamo che vedere la completa interazione in un posto lo renderà un po 'più chiaro.

Quindi, il modello Django potrebbe assomigliare a questo:

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

Supponiamo di passare a un progetto la cui cache_keycache esiste ancora. Poiché propagiamo le modifiche a tutti gli oggetti correlati al genitore, il fatto che quella particolare chiave esista ancora significa che l' intero contenuto renderizzato può essere estratto dalla cache.

Se quel particolare Progetto fosse appena stato aggiornato - ad esempio, come Foosopra - allora dovrà renderizzare i suoi figli, e solo allora eseguirà la query per tutti i Todolisti per quel Progetto. Allo stesso modo per un Todolist specifico - se esiste la cache_key di quell'elenco, i todos al suo interno non sono cambiati e il tutto può essere estratto dalla cache.

Nota anche come non sto usando todo.cache_keyin questo modello. Non ne vale la pena, dal momento che come dici nella domanda, bodyè già stato estratto dal database. Tuttavia, gli hit del database non sono l'unico motivo per cui potresti memorizzare nella cache qualcosa. Ad esempio, prendere il testo di markup non elaborato (come quello che digitiamo nelle caselle di domanda / risposta su StackExchange) e convertirlo in HTML potrebbe richiedere del tempo sufficiente a rendere più efficiente la memorizzazione nella cache del risultato.

Se così fosse, il ciclo interno nel modello potrebbe assomigliare di più a questo:

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

Quindi, per mettere insieme tutto, torniamo ai miei dati originali nella parte superiore di questa risposta. Se assumiamo:

  • Tutti gli oggetti erano stati memorizzati nella cache nel loro stato originale
  • Bang3 è stato appena aggiornato
  • Stiamo eseguendo il rendering del modello modificato (incluso expensive_markup_parser)

Quindi è così che tutto sarebbe caricato:

  • Foo viene recuperato dal database
  • Foo.cache_key (2014-05-16) non esiste nella cache
  • Foo.todolists.all()viene richiesto: Bar1e Bar2vengono recuperati dal database
  • Bar1.cache_key(10-05-2014) esiste già nella cache ; recuperarlo ed emetterlo
  • Bar2.cache_key (2014-05-16) non esiste nella cache
  • Bar2.todos.all()viene richiesto: Bang3e Bang4vengono recuperati dal database
  • Bang3.cache_key (2014-05-16) non esiste nella cache
  • {{ Bang3.body|expensive_markup_parser }} è reso
  • Bang4.cache_key(01-04-2014) esiste già nella cache ; recuperarlo ed emetterlo

I risparmi dalla cache in questo piccolo esempio sono:

  • Hit del database evitato: Bar1.todos.all()
  • expensive_markup_parserevitate 3 volte: Bang1, Bang2, eBang4

E naturalmente, la prossima volta che Foo.cache_keyverrà visualizzato, verrebbe trovato, quindi l'unico costo per il rendering è recuperare Fooda solo dal database e interrogare la cache.


-2

Il tuo esempio è utile se ha bisogno di recupero o elaborazione dei dati per ciascun commento. Se prendi semplicemente body e lo visualizzi, la cache sarà inutile. Ma puoi memorizzare nella cache tutti gli alberi dei commenti (incluso {% for%}). In questo caso è necessario invalidarlo con ogni commento aggiunto, in modo da poter inserire il timestamp dell'ultimo commento o i conteggi dei commenti da qualche parte in Posta e creare la chiave cache dei commenti con esso. Se preferisci dati più normalizzati e usi i commenti su una sola pagina, puoi semplicemente cancellare una chiave di cache al salvataggio dei commenti.

Per me, salvare il conteggio dei commenti in Post sembra abbastanza buono (se non si consente di eliminare e modificare i commenti) - si ha un valore da mostrare ovunque con il Post e una chiave per la memorizzazione nella cache.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.