Como funciona o cache baseado em chave?


10

Li recentemente um artigo no blog da 37Signals e fico me perguntando como é que eles obtêm a chave de cache.

É bom ter uma chave de cache que inclua o carimbo de data / hora do objeto (isso significa que quando você atualiza o objeto, o cache será invalidado); mas como você usa a chave de cache em um modelo sem causar um acerto no banco de dados para o mesmo objeto que você está tentando buscar no cache.

Especificamente, como isso afeta as relações Um para Muitos, nas quais você está processando os Comentários de uma Postagem, por exemplo.

Exemplo no Django:

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

O cache no Rails é diferente de apenas solicitações para o memcached, por exemplo (eu sei que eles convertem sua chave de cache em algo diferente). Eles também armazenam em cache a chave de cache?


Dê uma olhada em rossp.org/blog/2012/feb/29/fragment-caching para obter um exemplo do Django!
vdboor

Eu já dei uma olhada nisso e isso parece sofrer exatamente do mesmo problema. Os dados que ele está tentando armazenar em cache são necessários para acessar o cache. A única coisa em que ele parece estar economizando é na operação interna cara, diferente da maioria dos casos de uso para esse tipo de cache.
Dominic Santos

Isso é verdade, como também acontece com o código 37signals, ele é focado no código de renderização. O truque é armazenar também em cache a lista inteira em outro contêiner ou armazenar em cache a recuperação do objeto em outro local.
vdboor

Na verdade, sua estratégia de armazenamento em cache parece um pouco mais educada. Também recomendo este artigo: 37signals.com/svn/posts/…
JensG

Parece que seu snippet de código tem um erro de digitação - post.bodydeveria ser comment.body?
Izkata 16/05

Respostas:


3

Para armazenar em cache um despejo direto de um único objeto já carregado, sim, você ganha nada ou quase nada. Não é isso que esses exemplos estão descrevendo - eles estão descrevendo uma hierarquia, onde qualquer alteração em algo mais baixo também deve desencadear uma atualização para tudo o mais alto na hierarquia.

O primeiro exemplo, do blog 37signals, usa Project -> Todolist -> Todocomo hierarquia. Um exemplo preenchido pode ser assim:

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)

Então, digamos que Bang3foi atualizado. Todos os seus pais também são atualizados:

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

Então, quando chega a hora de renderizar, o carregamento Projectdo banco de dados é basicamente inevitável. Você precisa de um ponto para começar. No entanto, como last_modifiedé um indicador de todos os filhos , é isso que você usa como chave de cache antes de tentar carregar os filhos.


Enquanto as postagens do blog usam modelos separados, vou agrupá-los em um. Esperamos que a interação completa em um só lugar a torne um pouco mais clara.

Portanto, o modelo do Django pode ser algo como isto:

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

Digamos que passamos em um projeto que cache_keyainda existe no cache. Como propagamos alterações em todos os objetos relacionados ao pai, o fato de essa chave específica ainda existir significa que todo o conteúdo renderizado pode ser extraído do cache.

Se esse projeto em particular tivesse sido atualizado - por exemplo, como Fooacima -, ele precisará renderizar seus filhos e só então executará a consulta de todos os todolistas desse projeto. O mesmo vale para uma Todolist específica - se a cache_key dessa lista existir, todos os que estão nela não serão alterados e a coisa toda poderá ser extraída do cache.

Observe também como não estou usando todo.cache_keyneste modelo. Não vale a pena, pois, como você diz na pergunta, bodyjá foi extraído do banco de dados. No entanto, as ocorrências no banco de dados não são a única razão pela qual você pode armazenar algo em cache. Por exemplo, pegar texto de marcação bruto (como o que digitamos em caixas de perguntas / respostas no StackExchange) e convertê-lo em HTML pode levar tempo suficiente para que o armazenamento em cache do resultado seja mais eficiente.

Se assim fosse, o loop interno no modelo pode parecer mais com isso:

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

Então, para juntar tudo, vamos voltar aos meus dados originais na parte superior desta resposta. Se assumirmos:

  • Todos os objetos foram armazenados em cache em seu estado original
  • Bang3 foi atualizado
  • Estamos renderizando o modelo modificado (incluindo expensive_markup_parser)

Então é assim que tudo seria carregado:

  • Foo é recuperado do banco de dados
  • Foo.cache_key (16/05/2014) não existe no cache
  • Foo.todolists.all()é consultado: Bar1e Bar2é recuperado do banco de dados
  • Bar1.cache_key(10/05/2014) já existe no cache ; recupere e produza
  • Bar2.cache_key (16/05/2014) não existe no cache
  • Bar2.todos.all()é consultado: Bang3e Bang4é recuperado do banco de dados
  • Bang3.cache_key (16/05/2014) não existe no cache
  • {{ Bang3.body|expensive_markup_parser }} é processado
  • Bang4.cache_key(01/04/2014) já existe no cache ; recupere e produza

As economias do cache neste pequeno exemplo são:

  • Acerto no banco de dados evitado: Bar1.todos.all()
  • expensive_markup_parserevitados 3 vezes: Bang1, Bang2, eBang4

E, é claro, da próxima vez que for visualizado, Foo.cache_keyserá encontrado, portanto, o único custo para renderizar é recuperar Foosozinho do banco de dados e consultar o cache.


-2

Seu exemplo é bom se precisar de alguma recuperação ou processamento de dados para cada comentário. Se você pegar o corpo e exibi-lo, o cache será inútil. Mas você pode armazenar em cache todas as árvores de comentários (incluindo {% for%}). Nesse caso, é necessário invalidá-lo a cada comentário adicionado, para que você possa colocar o carimbo de data / hora do último comentário ou a contagem dos comentários em algum lugar no Post e criar a chave de cache de comentários com ele. Se você preferir dados mais normalizados e usar comentários em apenas uma página, basta limpar uma chave de cache ao salvar o comentário.

Para mim, salvar a contagem de comentários no Post parece bom o suficiente (se você não permitir excluir e editar comentários) - você tem um valor para mostrar em qualquer lugar com o Post e uma chave para o cache.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.