has_cache: cache no Rails de maneira simples
23/08/08
Há algum tempo atrás mostrei como utilizar as opções de cache disponíveis no Ruby on Rails 2.1. Embora esta tarefa tenha se tornado mais simples, ainda exige um processo um tanto quanto manual.
Pensando nisso, comecei a estudar formas mais automáticas de se fazer isso. Eu já simpatizava com a idéia implementada pelo Geoffrey Grosenbach, onde ele de mostrou uma forma muito inteligente de lidar com cache ou, melhor dizendo, com sua expiração.
A idéia consiste basicamente em utilizar uma data de atualização do objeto para manter o controle de cache. Desta forma, sempre que a data for atualizada, o cache irá expirar automaticamente. Esta abordagem é especialmente útil quando utilizada com o Memcache, já que ele irá utilizar uma quantidade pré-definida de memória, descartando os itens mais antigos quando ela se esgotar.
A solução que encontrei para este problema foi empacotada na forma de um plugin: has_cache.
Utilizando o plugin has_cache
Para usar este plugin, basta você instalá-lo através do comando abaixo:
script/plugin install git://github.com/fnando/has_cache.git
Vale lembrar que este plugin exige as versões 2.1 ou superior do Ruby on Rails.
O plugin possui funcionalidades específicas para models, actions e views, como você verá a seguir.
Modelos
Depois de instalado, basta adicionar a seguinte chamada ao seu modelo:
class Game < ActiveRecord::Base
has_many :comments
has_many :publishers
belongs_to :category
has_cache
def recent_comments
comments.recent :limit => 5
end
end
Apenas por adicionar a chamada ao método has_cache, todas as associações has_many e belongs_to irão ter uma versão com cache. Por exemplo, em vez de utilizar @game.comments, você pode utilizar @game.cached_comments. Simples assim!
Você também pode adicionar métodos que não são relacionamentos; basta utilizar a opção :include.
has_cache :include => :recent_comments
O exemplo acima irá adicionar o método de instância recent_comments. Se precisar adicionar métodos de classe, pode adicionar uma chamada como esta:
has_cache :include => {
:class_methods => %w(all find),
:instance_methods => :recent_comments
}
Os métodos de classe possuem um argumento obrigatório, que é a chave que irá identificar aquele cache.
Game.cached_all :sorted_by_title, :order => 'title asc'
Se você instalar o plugin has_paginate, poderá fazer consultas paginadas com cache.
Game.cached_paginate %w(all @page), :order => 'title asc', :page => @page
@game.cached_comments(:page => @page)
Se quiser evitar a paginação, basta passar a opção :paginate com o valor false.
@game.cached_comments(:paginate => false)
ATENÇÃO: Se o plugin has_paginate estiver instalado, todas as associações has_many serão paginadas por padrão.
Mais à frente você verá como o cache é expirado, e como definir novas chaves que serão expiradas.
Controllers
No controller, você pode utilizar o método cached_render:
class GamesController < ApplicationController
def index
@page = [params[:page].to_i, 1].max
cached_render :cache_name => %w(games index#{@page}) do
@games = Game.cached_paginate %w(all #{@page}), @page
end
end
end
Você pode especificar o tempo de vida do cache com a opção :expires_in.
cached_render :expires_in => 15.minutes do
# do something
end
Sempre que puder, utilize esta abordagem. Porém, se algum detalhe da tela é diferente para os usuários — um usuário logado tem um box com alguma identificação —, você não conseguirá fazer cache de toda a action. Mas poderá ter uma boa performance fazendo cache de pedaços da tela.
Views
Você pode fazer cache de fragmentos de um template. O plugin has_cache adiciona um método chamado cached_block.
<h1>Games</h1>
<% cached_block [:game_list, @page] do %>
<ul>
<% each_paginate @games do |game, i| %>
<li>
<%= game.title %>
</li>
<% end %>
</ul>
<%= paginate @games, url_for(:action => 'index') %>
<% end %>
Você também pode definir o tempo de vida do cache com a opção :expires_in.
<% cached_block [:game_list, @page], :expires_in => 1.hour do %>
<!-- do something -->
<% end %>
Como funciona a expiração do cache
Todo relacionamento has_many precisa de um campo com o nome do relacionamento, que servirá como o controle de expiração do cache. No nosso exemplo, nosso modelo deveria ser criado da seguinte forma:
class CreateGames < ActiveRecord::Migration
def self.up
create_table :games do |t|
t.references :category
t.datetime :comments_updated_at, :publishers_updated_at
t.string :title
t.timestamps
end
end
def self.down
drop_table :games
end
end
Toda vez que um comentário for criado, o plugin irá atualizar automaticamente o campo comments_updated_at, que é utilizado na composição da chave que irá identificar o cache. O mesmo irá acontecer quando um novo publisher for adicionado.
As seguintes estruturas de chave são expiradas quando um objeto é salvo ou destruído:
- próprio objeto
:table/:id:table/:id-:updated_at- associações
has_many :table/:id/:updated_at/:association/:association_updated_at- associações
belongs_to :table/:id- métodos de instância
:table/:id/:updated_at/:method_name- métodos de classe
- Não são expirados automaticamente.
Como os métodos de classe recebem uma chave na hora que são chamados, não podem ser expirados automaticamente. Neste caso, você pode deixar o método expirar por tempo ou forçar sua expiração.
has_cache :before_expire => proc {|game|
Game.has_cache_options[:to_expire] << "games/sorted_by_title"
}
E para finalizar…
Se você tiver alguma sugestão, faça um fork do has_cache e envie um patch. Dúvidas? Mande um comentário.
- Permalink
- Trackback
- Comentários (3)
- Ao som de: The Offspring – You're Gonna Go Far, Kid
Conhecendo as opções de cache do Rails 2.1
14/07/08
O Ruby on Rails 2.1, dentre outras novidades, trouxe suporte nativo a cache, com diversas opções de armazenamento.
O destaque vai para o suporte ao Memcache, de longe uma das opções mais utilizadas.
Todas essas novidades foram adicionadas ao módulo
ActiveSupport::Cache, que você confere neste artigo.
Configurando as opções de cache
Para definir qual o tipo de cache que você quer utilizar, altere a nova configuração cache_store, adicionada aos arquivos de ambiente de sua aplicação no diretório config/environments/*.rb. Você pode escolher entre :memory_store, :file_store, :mem_cache_store e drb.
config.cache_store = :memory_store
config.cache_store = :file_store, '/path/to/cache'
config.cache_store = :mem_cache_store
config.cache_store = :drb_store, "druby://localhost:2250"
config.cache_store = :mem_cache_store, '127.0.0.1:11211', '127.0.0.1:11212', {:namespace => 'myapp'}
A opção padrão de cache é :memory_store, a menos que o diretório tmp/cache exista; neste caso, a opção utilizada será :file_store.
Vale lembrar que se você utilizar uma opção cujo ambiente não está funcionando corretamente, sua aplicação não deixará de funcionar. Este comportamento é perfeito para trabalho em equipe, onde um desenvolvedor pode configurar seu ambiente para utilizar o Memcache enquanto os outros não precisam se importar com isso naquele momento.
Como funciona
Todas as funções de cache estão disponíveis no objeto Rails.cache.
Para gravar qualquer coisa no cache você deve utilizar o método write.
Rails.cache.write('some_identifier', 'some_value')
Você pode passar qualquer valor para ser gravado no cache, incluindo objetos de ActiveRecord.
@user = User.first
Rails.cache.write(@user.cache_key, @user)
O ActiveRecord adiciona um método que gera uma chave única para cada objeto, chamado cache_key.
Este método gera uma chave como "users/1-20080713185825", que você possa acessar e expirar o objeto sempre que precisar.
Por padrão, a chave gerada irá utilizar o nome do modelo, o id do objeto e a data de atualização, disponível através do atributo
updated_at.
Para ler um objeto do cache, você pode utilizar o método read.
Ele recebe um único argumento que identifica o objeto. Caso o objeto não seja encontrado, o valor nil
será retornado.
Rails.cache.read('some_identifier')
Você pode verificar se um item existe no cache com o método exist?.
Rails.cache.exist?('some_identifier')
Você pode estar se perguntando se você precisa utiliza o método exist? juntamente com read e write para acessar um objeto caso ele exista e gravar um novo item caso ele não seja encontrado. Na verdade, você pode utilizar o método fetch que tenta acessar um item no cache e, caso ele não exista, executa o bloco que é passado e faz a gravação em cache automaticamente.
@users = Rails.cache.fetch('users/all') { User.all }
O método fetch pode receber um hash de opções. No momento, a única opção disponível é :expires_in, que permite alterar o tempo de expiração do cache somente para aquele objeto.
@users = Rails.cache.fetch('users/all', :expires_in => 30.minutes) { User.all }
Para o caso de você estar acessando um único objeto, você pode sobrescrever o método cache_key de modo que ele não gere a chave com o a data de atualização.
class User < ActiveRecord::Base
def cache_key
"users/#{id}"
end
end
Assim, você pode acessar os objetos de maneira mais simples. Veja um exemplo de como isso funcionaria em uma action do controller users.
class UsersControllers < ApplicationController
def show
@user = Rails.cache.fetch("users/#{params[:id]}") { User.find(params[:id]) }
end
end
E se você segue a idéia de encapsular toda a lógica nos modelos, existe sempre a opção de cuidar do cache desta maneira.
class User < ActiveRecord::Base
def self.find_recent_users
Rails.cache.fetch('users/recent') { all :order => 'created_at desc', :limit => 10 }
end
end
Você também pode remover qualquer item do cache com o método delete, que retorna true or false.
Rails.cache.delete('users/1')
Finalizando
É isso! Acredito que este artigo cubra as principais funcionalidades de cache que foram adicionadas à versão 2.1. Dúvidas, sugestões ou correções, envie um comentário!
- Permalink
- Trackback
- Comentários (4)
- Ao som de: Kay Hanley – Mean Streak
Instalando o memcached no Mac OS X
10/07/08
O memcached é um sistema distribuído de cache em memória utilizado por muitos sites com tráfego intenso como Youtube, Wikipedia e Digg.
Para instalá-lo no Mac OS X Leopard, siga os passos abaixo, que seguem a velha receita de bolo configure-make-install.
Iniciando a instalação
Antes de instalar o memcached, é preciso instalar a biblioteca libevent. Acesse o site http://monkey.org/~provos/libevent/ e faça o download da última versão. Após extrair seus arquivos, execute os comandos abaixo para compilar e instalar esta biblioteca.
cd libevent-1.4.5-stable
./configure
make
sudo make install
Agora, você pode instalar o memcached propriamente dito! O código-fonte do memcached pode ser encontrado em http://www.danga.com/memcached/. Faça o download e, depois de extrair os arquivos, execute:
cd memcached-1.2.5
./configure
make
sudo make install
Se você pretende utilizar o memcached em uma aplicação Ruby on Rails, você precisa instalar a gem de mesmo nome. Antes, você precisa instalar a biblioteca libmemcached. Acesse o site http://download.tangent.org/ e faça o download da última versão. Extraia os arquivos e execute os comandos abaixo.
cd libmemcached-0.21
./configure
make
sudo make install
E para finalizar, instale a gem memcached.
sudo env ARCHFLAGS="-arch i386" gem install memcached - Permalink
- Trackback
- Comentários (0)
- Ao som de: Kay Hanley – Mean Streak
