2009-06-23 15 views
10

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í.La mejor manera de combinar el almacenamiento en memoria caché de fragmentos y objetos para memcached y Rails

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

Es obvio que no es necesario hacer la consulta de base de datos para obtener los mensajes más recientes si existe el fragmento, por lo que debería ser capaz de evitar que los gastos generales también.

Lo que estoy haciendo ahora es algo como esto 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" al comprobar más tarde, pero se le ocurrió esta después de ver memcached -vv para ver lo que 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?

Respuesta

12

Evan Weaver Interlock Plugin resuelve este problema.

También puede implementar algo como esto usted mismo fácilmente si necesita un comportamiento diferente, como un control de grano más fino. La idea básica consiste en envolver el código del controlador en un bloque que sólo se ejecuta realmente si la vista es necesario que los 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 programación meta rubí Es bastante fácil de terminar con esto en una API más limpia de tu gusto.

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

  • mantiene el código del buscador donde los desarrolladores esperan que por convención
  • mantiene el punto de vista ignora el nombre del modelo/método, lo que permite más vistas reutilizar

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

La ventaja que obtienes de memcached está directamente relacionada con tu tasa de aciertos de caché. Tenga cuidado de no desperdiciar su capacidad de caché y causar errores innecesarios guardando en caché el mismo contenido varias veces.Por ejemplo, no almacene en caché un conjunto de objetos de registro ni su fragmento de html al mismo tiempo. Generalmente, el almacenamiento en caché de fragmentos ofrecerá el mejor rendimiento, pero realmente depende de las características específicas de su aplicación.

+0

Aunque creo que la respuesta de Lar podría ser un poco más clara, y evita el uso de la meta programación, tu respuesta es mejor ya que ad está escuchando más de cerca a MVC. Los he votado como válidos, pero dejaré que la comunidad decida (no elegirá uno como aceptado :) ¡Gracias! –

+0

Estoy de acuerdo, y creo que esta es una buena solución también. En cuanto a reutilizar la vista, sí, en general, ese es un buen principio. Tendría que dejar que el controlador también le proporcione la clave de caché para que funcione aquí. Por cierto, curiosamente, la documentación de la API sobre el almacenamiento en caché de fragmentos viola el principio de MVC en el primer ejemplo: http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html –

3

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

que serí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 usar esa en la vista:

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

Para mayor claridad, también se puede considerar mover la prestación de un post reciente en su propio parcial: de

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

Gran punto de Lars! Muy buenas cosas –

+0

Creo que la suya es la mejor solución hasta el momento, aunque técnicamente esto puede violar MVC es bastante claro. La respuesta de Jason Watkin es una alternativa válida. –

1

También puede que desee ver en

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 el tema de la SECO todavía levanta la cabeza necesitando el nombre de la llave en dos lugares. Normalmente hago esto de manera similar a como sugirió Lars, pero realmente depende del gusto. Otros desarrolladores que conozco siguen con la comprobación de fragmentos existen.

Actualización:

Si nos fijamos en los documentos de fragmentos, se puede ver cómo se deshace de la necesidad de la vista prefijo:

# 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 
+0

ahh, tiene sentido. El fragment_exist? método probablemente se deshace de la necesidad de ese prefijo "ver /" en la clave cache_? –

1

Lars hace un muy buen punto acerca de que hay una ligera posibilidad de la falla al usar:

unless fragment_exist?("recent_posts") 

porque hay un espacio entre la verificación de la memoria caché y el uso de la memoria caché.

El plugin que Jason menciona (enclavamiento) se encarga de esta con mucha gracia al suponer que si usted está mirando para la existencia del fragmento, entonces es probable que también utilizar el fragmento y por lo tanto almacena en caché el contenido de forma local. Yo uso Interlock por estos mismos motivos.

1

simplemente como una pieza de pensamiento:

en controlador de aplicación definen

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 

continuación, en cualquier uso acción

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

en vista

cache "cache_key" do 
view_code 
end 
Cuestiones relacionadas