Bester Weg, Fragment und Objekt zu kombinieren, das für Memcached und Rails zwischengespeichert wird
-
07-07-2019 - |
Frage
Nehmen wir an, Sie haben ein Fragment der Seite, auf dem die neuesten Beiträge angezeigt werden, und Sie verfallen es in 30 Minuten. Ich benutze hier Rails.
<% cache("recent_posts", :expires_in => 30.minutes) do %>
...
<% end %>
Offensichtlich müssen Sie die Datenbanksuche nicht durchführen, um die neuesten Beiträge zu erhalten, wenn das Fragment vorhanden ist. Sie sollten also auch diesen Overhead vermeiden können.
Was ich jetzt mache, ist so etwas im Controller, der zu funktionieren scheint:
unless Rails.cache.exist? "views/recent_posts"
@posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end
Ist das der beste Weg? Ist es sicher?
Eine Sache, die ich nicht verstehe, ist, warum der Schlüssel ist "recent_posts
"Für das Fragment und"views/recent_posts
"Beim später nachcheck memcached -vv
um zu sehen, was es benutzte. Außerdem mag ich die Duplizierung nicht manuell eindringen "recent_posts
"Es wäre besser, das an einem Ort zu behalten.
Ideen?
Lösung
Evan Weaver Interlock -Plugin Löst dieses Problem.
Sie können so etwas auch selbst leicht implementieren, wenn Sie ein anderes Verhalten benötigen, z. B. eine feinkörnigere Kontrolle. Die Grundidee besteht darin, Ihren Controller -Code in einen Block zu wickeln, der nur dann ausgeführt wird, wenn die Ansicht diese Daten benötigt:
# 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
Wenn Sie mit den Grundlagen des Ruby -Meta -Programmierens vertraut sind, ist es einfach genug, dies in einer saubereren API Ihres Geschmacks zu füllen.
Dies ist überlegen, den Findercode direkt in die Ansicht zu setzen:
- Hält den Findercode, in dem Entwickler ihn nach Übereinkommen erwarten
- Hält die Ansicht nicht über den Modellnamen/die Modellname/die Methode des Modells unwissend, und ermöglicht eine weitere Wiederverwendung von Ansicht
Ich denke, Cache_FU könnte eine ähnliche Funktionalität in einer seiner Versionen/Gabeln haben, kann sich aber nicht speziell erinnern.
Der Vorteil, den Sie von Memcached erhalten, hängt direkt mit Ihrem Cache -Hit -Rate zusammen. Achten Sie darauf, dass Sie Ihre Cache -Kapazität nicht verschwenden und unnötige Fehlschläge verursachen, indem Sie denselben Inhalt mehrmals zwischengespeichert. Speichern Sie beispielsweise keine Reihe von Datensatzobjekten sowie ihr HTML -Fragment gleichzeitig. Im Allgemeinen bietet Fragment -Caching die beste Leistung, hängt jedoch wirklich von den Besonderheiten Ihrer Anwendung ab.
Andere Tipps
Was passiert, wenn der Cache zwischen dem Zeitpunkt, an dem Sie im Controller darauf suchen, und dem Zeitpunkt, an dem er in der Ansichts -Rendering überprüft wird, abläuft?
Ich würde eine neue Methode im Modell erstellen:
class Post
def self.recent(count)
find(:all, :limit=> count, :order=>"updated_at DESC")
end
end
Verwenden Sie das dann in der Ansicht:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
<% Post.recent(20).each do |post| %>
...
<% end %>
<% end %>
Aus Gründen der Klarheit können Sie auch in Betracht ziehen, die Darstellung eines kürzlich veröffentlichten Beitrags in einen eigenen Teil zu verschieben:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
<%= render :partial => "recent_post", :collection => Post.recent(20) %>
<% end %>
Möglicherweise möchten Sie auch untersuchen
Das ermöglicht es Ihnen, dies zu tun:
<% cache("recent_posts", :expires_in => 30.minutes) do %>
...
<% end %>
Regler
unless fragment_exist?("recent_posts")
@posts = Post.find(:all, :limit=>20, :order=>"updated_at DESC")
end
Obwohl ich zu gebe, dass das Problem der Trocken immer noch den Kopf aufnimmt und den Namen des Schlüssels an zwei Stellen benötigt. Ich mache dies normalerweise ähnlich wie Lars vorschlug, aber es hängt wirklich vom Geschmack ab. Andere Entwickler, von denen ich kenne, dass es sich um das Überprüfen von Fragment handelt.
Aktualisieren:
Wenn Sie sich die Fragment -Dokumente ansehen, können Sie sehen, wie es das Präfix der Ansicht benötigt:
# 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 macht einen wirklich guten Punkt darüber, dass es eine leichte Chance auf Versagen gibt, mithilfe voneinander zu versagen:
unless fragment_exist?("recent_posts")
Denn eine Lücke zwischen dem Überprüfen des Cache und der Verwendung des Cache.
Das Plugin, das Jason erwähnt (Interlock), behandelt dies sehr anmutig, indem Sie davon ausgehen, dass Sie das Fragment wahrscheinlich auch verwenden, wenn Sie nach Existenz des Fragments prüfen und so den Inhalt lokal zwischengespeichert. Ich benutze Interlock aus genau diesen Gründen.
Genau wie ein Gedanke:
im Anwendungscontroller definieren
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
dann in jeder Aktion Verwendung
def index
when_fragment_expired "cache_key", 5.minutes
@object = YourObject.expensive_operations
end
end
im Hinblick auf
cache "cache_key" do
view_code
end