Meilleure façon de combiner la mise en cache de fragments et d'objets pour memcached et Rails
-
07-07-2019 - |
Question
Disons que vous avez un fragment de la page qui affiche les publications les plus récentes et que vous l'expirez au bout de 30 minutes. J'utilise Rails ici.
<% cache("recent_posts", :expires_in => 30.minutes) do %>
...
<% end %>
Évidemment, vous n'avez pas besoin de consulter la base de données pour obtenir les publications les plus récentes si le fragment existe, vous devriez donc pouvoir éviter cette surcharge.
Ce que je suis en train de faire ressemble à ceci dans le contrôleur, ce qui semble fonctionner:
unless Rails.cache.exist? "views/recent_posts"
@posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end
Est-ce le meilleur moyen? Est-ce sécuritaire?
Une chose que je ne comprends pas, c'est pourquoi la clé est " Recent_posts
". pour le fragment et " views / recent_posts
". lors de la vérification ultérieure, mais je l’ai trouvée après avoir regardé memcached -vv
pour voir ce qu’il utilisait. De plus, je n'aime pas la duplication consistant à entrer manuellement le code < Recent_posts
", il serait préférable de le conserver au même endroit.
Des idées?
La solution
Vous pouvez également implémenter quelque chose comme cela vous-même facilement si vous avez besoin d'un comportement différent, tel qu'un contrôle plus fin. L'idée de base est d'envelopper le code de votre contrôleur dans un bloc qui n'est réellement exécuté que si la vue a besoin de ces données:
# 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 vous connaissez les bases de la méta-programmation Ruby, il est assez facile de l'intégrer dans une API plus propre à votre goût.
C’est mieux que de placer le code de recherche directement dans la vue:
- conserve le code de recherche là où les développeurs l'attendent par convention
- garde la vue ignorante du nom du modèle / de la méthode, permettant plus de réutilisation de la vue
Je pense que cache_fu pourrait avoir des fonctionnalités similaires dans l'une de ses versions / forks, mais ne peut pas en rappeler spécifiquement.
L'avantage que vous obtenez de memcached est directement lié à votre taux d'accès au cache. Veillez à ne pas gaspiller votre capacité de cache et à provoquer des ratés inutiles en mettant en cache le même contenu plusieurs fois. Par exemple, ne mettez pas en cache un ensemble d'objets record ainsi que leur fragment HTML en même temps. La mise en cache de fragments offre généralement les meilleures performances, mais cela dépend vraiment des spécificités de votre application.
Autres conseils
Que se passe-t-il si le cache expire entre le moment où vous le vérifiez dans le contrôleur? et le temps est-il vérifié dans le rendu de la vue?
Je ferais une nouvelle méthode dans le modèle:
class Post
def self.recent(count)
find(:all, :limit=> count, :order=>"updated_at DESC")
end
end
puis utilisez-le dans la vue:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
<% Post.recent(20).each do |post| %>
...
<% end %>
<% end %>
Par souci de clarté, vous pouvez également envisager de transférer le rendu d'un message récent dans son propre partiel:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
<%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>
Vous voudrez peut-être aussi vous pencher sur
Ce qui vous permet de faire ceci:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
...
<% end %>
Contrôleur
unless fragment_exist?("recent_posts")
@posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end
Même si j’admets que la question de DRY soulève toujours la question de la clé à deux endroits. Je fais habituellement la même chose que suggéré par Lars, mais cela dépend vraiment du goût. Il existe d’autres développeurs qui possèdent un fragment de vérification.
Mise à jour:
Si vous examinez la documentation des fragments, vous pouvez voir comment il est possible de se passer du préfixe de vue:
# 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 fait une très bonne remarque sur le fait qu’il existe un léger risque d’échec en utilisant:
unless fragment_exist?("recent_posts")
car il y a un intervalle entre le moment où vous vérifiez le cache et celui où vous utilisez le cache.
Le plugin que jason mentionne (Interlock) gère cela de façon très élégante en supposant que, si vous vérifiez l’existence du fragment, vous utiliserez probablement également le fragment et mettra donc le contenu en cache localement. J'utilise Interlock pour ces mêmes raisons.
juste comme une pensée:
dans le contrôleur d’application définir
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
puis dans toute action utiliser
def index
when_fragment_expired "cache_key", 5.minutes
@object = YourObject.expensive_operations
end
end
en vue
cache "cache_key" do
view_code
end