July 12, 2011
Sim! Os “caches” são mágicos!

O problema

O que fazer quando nossa aplicação começa a demorar a responder?

  1. Otimizar o código?
  2. Rever os índices do banco?
  3. Aumentar a memória? 
  4. Contratar um novo servidor?
  5. Escalar minha aplicação?

Logicamente todas as opções são válidas, porém existem uma que pode nos surpreender, o uso de caches! O rails possui nativamente esse recurso, porque não usa-lo?

Tipos de caches

Podemos trabalhar com três tipos de caches:

  1. Page cache
  2. Action cache
  3. Fragment cache

Porém hoje irei abordar somente o fragment cache, acredito ser o mais utilizado. Mas aconselho a conferirem os outros tipos também na documentação do rails.

Para quem não sabe, fragment cache nada mais é que guardar um pedaço de html, para que quando alguém acessar sua página, a aplicação não tenha que ir novamente ao banco de dados, coletar os dados e renderizar-lo em html. Funciona assim:

Vamos pegar a página do twitter como exemplo:

Imagine se toda hora que alguém visitasse essa página, a aplicação do twitter tivesse que construir esse bloco de usuários? Seria muito custoso!

Com o uso do fragment cache, a primeira vez que alguém acessa essa página é gerado um arquivo html, salvo em memória ou arquivo físico ( falarei mais sobre isso a baixo). Então quando os próximos usuários acessarem essa página novamente, não será mais necessário passar por todo processo para montar esse bloco, mas sim renderizar o html armazenado anteriormente, bem legal né?

Não parece mas isso faz muita diferença, ainda mais se você levar em conta que você pode criar vários fragment caches para suas páginas.

Então quer dizer que após gerado esse arquivo html, nunca mais mudará? Não, se fosse assim não seria nem um pouco útil. Pra isso existe o método expire_cache! Vamos ver um exemplo?

Mãos a obra

Vamos tentar simular o caso do twitter, onde teremos apenas uma tabela de usuários e uma tabela de amizades, responsável pela associação entre os usuários:

users_controller.rb:

...
def show
  @user = User.find(params[:id])
  unless fragment_exist?("users_show_friendships_#{@user.id}")
    @friendships = @user.friendships
  end
  respond_to do |format|
    format.html
  end
end
...

O método fragment_exist?(“users_show_friendships_#{@user.id}”) verifica a existência do nosso cache de amizades do usuário, caso exista, não será necessário carregar a lista de amizades do usuário novamente.

Repare que foi concatenado o id do usuário ao nome do cache, isso porque o nome e logicamente seu conteúdo deve ser único para cada usuário, não queremos acessar a página de um usuário e visualizar a lista de amizades de outro usuário.

Aqui já podemos ver uma significante redução de recurso, pois não será carregado a lista de amizades a toda requisição.

friendships_controller.rb:

class FriendshipsController < ApplicationController
  cache_sweeper :friendship_sweeper, :only => [:create, :destroy]
  ...
end

Para expirarmos o nosso cache, vamos definir o momento ideal, que no nosso caso será ao criar ou remover uma nova amizade, certo?

Vamos utilizar pra isso o sweeper. Pense no sweeper como um observer, ele irá observar nosso modelo de friendship nas ações :create, :destroy do controle de friendships.

application.rb

...
module Api
  class Application < Rails::Application
    config.autoload_paths += %W(#{config.root}/app/sweepers)
...

Vamos criar uma pasta app/sweepers para colocarmos nosso friendship_sweeper.rb. Não se esqueça de defini-la no application.rb, para que seja carregada no boot da nossa aplicação.

friendship_sweeper.rb

class FriendshipSweeper < ActionController::Caching::Sweeper
  observe Friendship

  def after_create(friendship)
    expire_cache_for(friendship)
  end
  
  def after_destroy(friendship)
    expire_cache_for(friendship)
  end

  def expire_cache_for(friendship)
    expire_fragment("users_show_friendships_#{friendship.user_id}")
    expire_fragment("users_show_friendships_#{friendship.friend_id}")
  end
end

Precisamos criar nosso friendship_sweeper.rb na pasta app/sweepers. Note que a sua estrutura é semelhante ao de um observer. Após a criação ou exclusão de uma amizade, será chamado o método expire_cache_for que o mesmo irá destruir os caches.

Repare que estamos expirando dois caches, pois queremos que tanto a lista de amizades do usuário seguidor como a do usuário seguido seja atualizada, certo?

show.html.erb:

...
<%- cache("users_show_friendships_#{@user.id}") -%>
  <%- @friendships.each do |friend| -%>
    <%= friend.name %>
  <%- end -%>
<%- end -%>
...

Por último, basta definirmos o bloco que conterá nosso cache.

É isso ai pessoal, explorem bastante o uso de caches, eles são seus amigos e podem fazer a diferença!

ATENÇÃO: Só tome cuidado onde irá colocar cache, pois se o bloco tiver atualizações constantes pode ser mais custoso para a aplicação ficar criando e destruindo caches do que simplesmente não te-los.

Abraços e até o próximo post!


  1. diegoalvareznogueira posted this
Blog comments powered by Disqus