Pergunta

Digamos que você tenha um fragmento da página que exibe as postagens mais recentes e você expira em 30 minutos. Estou usando trilhos aqui.

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

Obviamente, você não precisa fazer a pesquisa do banco de dados para obter as postagens mais recentes se o fragmento existir, portanto, você também poderá evitar essa sobrecarga.

O que estou fazendo agora é algo assim no controlador que parece funcionar:

unless Rails.cache.exist? "views/recent_posts"
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

É este o melhor caminho? É seguro?

Uma coisa que não entendo é por que a chave é "recent_posts"Para o fragmento e"views/recent_posts"Ao verificar mais tarde, mas eu inventei isso depois de assistir memcached -vv para ver o que estava usando. Além disso, não gosto da duplicação de entrar manualmente "recent_posts", Seria melhor manter isso em um só lugar.

Ideias?

Foi útil?

Solução

Evan Weaver's Plugin de intertravamento resolve esse problema.

Você também pode implementar algo assim facilmente, se precisar de um comportamento diferente, como controle de granulação mais fina. A idéia básica é envolver o código do seu controlador em um bloco que só é realmente executado se a visualização precisar desses dados:

# in FooController#show
@foo_finder = lambda{ Foo.find_slow_stuff }

# in foo/show.html.erb
cache 'foo_slow_stuff' do
  @foo_finder.call.each do 
    ...
  end
end

Se você está familiarizado com o básico da meta -programação rubi, é fácil encerrar isso em uma API mais limpa do seu gosto.

Isso é superior a colocar o código do localizador diretamente na visualização:

  • Mantém o código do localizador onde os desenvolvedores o esperam por convenção
  • Mantém a visão ignorante do nome/método do modelo, permitindo mais reutilização de visualização

Eu acho que o cache_fu pode ter funcionalidade semelhante em uma de suas versões/garfos, mas não consegue recordar especificamente.

A vantagem que você obtém do Memcached está diretamente relacionada à sua taxa de acerto de cache. Tome cuidado para não desperdiçar sua capacidade de cache e causar erros desnecessários ao armazenar em cache o mesmo conteúdo várias vezes. Por exemplo, não cache um conjunto de objetos de registro, bem como seu fragmento HTML ao mesmo tempo. Geralmente, o cache de fragmentos oferece o melhor desempenho, mas realmente depende das especificidades do seu aplicativo.

Outras dicas

O que acontece se o cache expirar entre o momento em que você o verifica no controlador e o tempo em que a que está sendo verificada na renderização da exibição?

Eu faria um novo método no modelo:

  class Post
    def self.recent(count)
      find(:all, :limit=> count, :order=>"updated_at DESC")
    end
  end

Em seguida, use isso na visualização:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <% Post.recent(20).each do |post| %>
     ...
  <% end %>
<% end %>

Para maior clareza, você também pode considerar a renderização de um post recente em seu próprio parcial:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  <%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>

Você também pode querer olhar para

Documentos de cache de fragmento

Que permitem que você faça isso:

<% cache("recent_posts", :expires_in => 30.minutes) do %>
  ...
<% end %>

Controlador

unless fragment_exist?("recent_posts")
  @posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end

Embora eu admita que a questão da seca ainda eleva a cabeça, precisando do nome da chave em dois lugares. Eu costumo fazer isso semelhante ao como Lars sugeriu, mas realmente depende do sabor. Outros desenvolvedores que conheço se mantêm com a verificação do fragmento.

Atualizar:

Se você olhar para os documentos do fragmento, poderá ver como ele se livra da necessidade do prefixo de exibição:

# File vendor/rails/actionpack/lib/action_controller/caching/fragments.rb, line 33
def fragment_cache_key(key)
  ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end

Lars faz um ponto muito bom sobre a have uma pequena chance de falha usando:

unless fragment_exist?("recent_posts")

Porque há uma lacuna entre quando você verifica o cache e quando usa o cache.

O plug -in que Jason menciona (Interlock) lida com isso muito graciosamente, assumindo que, se você estiver verificando a existência do fragmento, provavelmente também usará o fragmento e, portanto, armazenará o conteúdo localmente. Eu uso a interlock por esses mesmos motivos.

Apenas como um pensamento:

no controlador de aplicativos Definir

def when_fragment_expired( name, time_options = nil )
        # idea of avoiding race conditions
        # downside: needs 2 cache lookups
        # in view we actually cache indefinetely 
        # but we expire with a 2nd fragment in the controller which is expired time based
        return if ActionController::Base.cache_store.exist?( 'fragments/' + name ) && ActionController::Base.cache_store.exist?( fragment_cache_key( name ) )

        # the time_fraqgment_cache uses different time options
        time_options = time_options - Time.now if time_options.is_a?( Time )

        # set an artificial fragment which expires after given time
        ActionController::Base.cache_store.write("fragments/" + name, 1, :expires_in => time_options )

        ActionController::Base.cache_store.delete( "views/"+name )        
        yield    
  end

Então, em qualquer ação, use

    def index
when_fragment_expired "cache_key", 5.minutes
@object = YourObject.expensive_operations
end
end

em vista

cache "cache_key" do
view_code
end
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top