Pregunta

Digamos que tiene un fragmento de la página que muestra las publicaciones más recientes, y expira en 30 minutos. Estoy usando Rails aquí.

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

Obviamente, no necesita hacer la búsqueda en la base de datos para obtener las publicaciones más recientes si existe el fragmento, por lo que también debería poder evitar esa sobrecarga.

Lo que estoy haciendo ahora es algo así en el controlador que parece funcionar:

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

¿Es esta la mejor manera? ¿Es seguro?

Una cosa que no entiendo es por qué la clave es " Recent_posts " para el fragmento y " views / Recent_posts " cuando lo comprobé más tarde, pero se me ocurrió esto después de ver memcached -vv para ver qué estaba usando. Además, no me gusta la duplicación de ingresar manualmente " Recent_posts " ;, sería mejor mantener eso en un solo lugar.

Ideas?

¿Fue útil?

Solución

El Interlock Plugin de Evan Weaver resuelve este problema.

También puede implementar algo como esto fácilmente si necesita un comportamiento diferente, como un control más detallado. La idea básica es envolver el código de su controlador en un bloque que solo se ejecuta si la vista necesita esos datos:

# 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

Si está familiarizado con los conceptos básicos de la meta programación de ruby, es bastante fácil resumir esto en una API más limpia de su gusto.

Esto es superior a poner el código del buscador directamente en la vista:

  • mantiene el código del buscador donde los desarrolladores lo esperan por convención
  • mantiene la vista ignorante del nombre / método del modelo, permitiendo una mayor reutilización de la vista

Creo que cache_fu podría tener una funcionalidad similar en una de sus versiones / horquillas, pero no puedo recordar específicamente.

La ventaja que obtiene de memcached está directamente relacionada con su tasa de aciertos de caché. Tenga cuidado de no desperdiciar su capacidad de caché y causar errores innecesarios al almacenar en caché el mismo contenido varias veces. Por ejemplo, no guarde en caché un conjunto de objetos de registro, así como su fragmento html al mismo tiempo. En general, el almacenamiento en caché de fragmentos ofrecerá el mejor rendimiento, pero realmente depende de los detalles de su aplicación.

Otros consejos

¿Qué sucede si el caché caduca entre el momento en que lo verifica en el controlador? y el momento en que se verifica en el renderizado de la vista?

Crearía un nuevo método en el modelo:

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

luego use eso en la vista:

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

Para mayor claridad, también podría considerar mover el renderizado de una publicación reciente a su propio parcial:

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

También es posible que desee examinar

Fragment Cache Docs

Que le permiten hacer esto:

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

Aunque admito que el problema de DRY todavía se asoma y necesita el nombre de la clave en dos lugares. Por lo general, hago esto de manera similar a como Lars sugirió, pero realmente depende del gusto. Existen otros desarrolladores que conozco que siguen con la comprobación de fragmentos.

Actualización:

Si observa los fragmentos de documentos, puede ver cómo se deshace de necesitar el prefijo de vista:

# 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 señala muy bien que existe una pequeña posibilidad de falla al usar:

unless fragment_exist?("recent_posts")

porque hay una brecha entre cuando verifica el caché y cuando lo usa.

El complemento que menciona Jason (Interlock) maneja esto con mucha gracia al suponer que si está verificando la existencia del fragmento, entonces probablemente también usará el fragmento y, por lo tanto, almacenará en caché el contenido localmente. Uso Interlock por estas mismas razones.

solo como un pensamiento:

en el controlador de la aplicación define

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

luego, en cualquier acción, use

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

a la vista

cache "cache_key" do
view_code
end
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top