2009-06-23 5 views
10

Powiedzmy, że masz fragment strony wyświetlający najnowsze posty, a wygaśniesz go za 30 minut. Używam tutaj Railsów.Najlepszy sposób łączenia pamięci podręcznej fragmentów i obiektów dla memcached i Railsów

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

Oczywiście nie trzeba robić przeszukiwania bazy danych, aby uzyskać najnowsze wiadomości czy fragment istnieje, więc powinieneś być w stanie uniknąć tego napowietrznych zbyt.

Co robię teraz jest coś takiego w kontrolerze, który wydaje się działać:

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

Jest to najlepszy sposób? Czy to jest bezpieczne?

Jedno nie rozumiem dlaczego jest kluczem jest „recent_posts” dla fragmentu i „views/recent_posts” podczas sprawdzania później, ale wpadłem na to po obejrzeniu memcached -vv aby zobaczyć co to wykorzystywał. Ponadto nie podoba mi się powielanie ręcznego wprowadzania "recent_posts", lepiej byłoby zachować to w jednym miejscu.

Pomysły?

Odpowiedz

12

Evan tkacki Interlock Plugin rozwiązuje ten problem.

Możesz także zaimplementować coś takiego samemu, jeśli potrzebujesz innego zachowania, na przykład bardziej szczegółowego sterowania. Podstawowym założeniem jest, by zakończyć swój kod kontrolera w bloku, który jest tylko faktycznie zrealizowanej jeśli widok potrzebuje tych danych:

# 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 

Jeśli jesteś zaznajomiony z podstawami ruby ​​programowanie meta jest to dość łatwe do zawijania to w górę w czystszym API twojego gustu.

To lepsze wprowadzenie kodu znajdź bezpośrednio w widoku:

  • utrzymuje kod znajdź gdzie deweloperzy oczekują go umownie
  • utrzymuje pogląd nieświadomi Nazwa modelu/metody, pozwalając na bardziej widok ponowne użycie

Myślę, że cache_fu może mieć podobną funkcjonalność w jednej z jego wersji/widelców, ale nie może przypominać sobie konkretnie.

Zaleta jaką daje memcached jest bezpośrednio związana z częstością trafień w pamięci podręcznej. Uważaj, aby nie marnować pojemności pamięci podręcznej i nie powodować niepotrzebnych błędów poprzez wielokrotne buforowanie tej samej zawartości.Na przykład nie należy buforować zestawu obiektów rekordów, a także ich fragmentów html w tym samym czasie. Ogólnie buforowanie fragmentów zapewnia najlepszą wydajność, ale to naprawdę zależy od specyfiki aplikacji.

+0

Chociaż myślę, że odpowiedź Lar'a może być nieco bardziej przejrzysta i unika używania meta-programowania, twoja odpowiedź jest lepsza, ponieważ reklama lepiej słyszy MVC. Przegłosowałem oba z nich jako ważne, ale pozostawiam to społeczności do podjęcia decyzji (nie wybiorę jednego jako zaakceptowanego :) Dzięki! –

+0

Zgadzam się i uważam to również za dobre rozwiązanie. Jeśli chodzi o ponowne wykorzystanie widoku, tak, ogólnie rzecz biorąc, jest to dobra zasada. Musiałbyś pozwolić kontrolerowi dostarczyć klucz pamięci podręcznej, żeby działał tutaj. BTW, co zabawne, dokumentacja API dotycząca buforowania fragmentów narusza zasadę MVC w pierwszym przykładzie: http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html –

3

Co się stanie, jeśli pamięć podręczna wygaśnie między momentem sprawdzenia w kontrolerze a czasem, w którym jest sprawdzana w renderowaniu widoku?

bym zrobić nową metodę w modelu:

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

następnie użyj w widoku:

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

Dla jasności, można również rozważyć przeniesienie renderowania ostatnim poście do własnej częściowy:

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

świetny punkt Lars! Bardzo dobre rzeczy. –

+0

Myślę, że twoje rozwiązanie jest jak dotąd najlepsze, chociaż technicznie może to naruszyć MVC, jest to całkiem jasne. Odpowiedź Jasona Watkina jest ważną alternatywą. –

1

Można również zajrzeć do

Fragment Cache Docs

które pozwalają to zrobić:

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

Controller

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

Chociaż muszę przyznać, problem suchej nadal szykuje swoją głowę wymagającą nazwy klucza w dwóch miejscach. Zazwyczaj robię to w podobny sposób, jak sugerował Lars, ale to naprawdę zależy od gustu. Inni programiści, których znam, trzymają się fragmentu sprawdzającego, istnieją.

Aktualizacja:

Jeśli spojrzeć na fragment docs, można zobaczyć jak to pozbywa potrzeby prefiks wyświetlania:

# 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, ma sens. Fragment_isto? metoda prawdopodobnie pozbędzie się tego przedrostka "widok /" na cache_key? –

1

Lars sprawia naprawdę dobry punkt o tam jest niewielka szansa awarii za pomocą:

unless fragment_exist?("recent_posts") 

, ponieważ występuje luka między sprawdzaniem pamięci podręcznej i użyciem pamięci podręcznej.

Wtyczka, o której wspomina Jason (Interlock), radzi sobie z tym z wdziękiem, zakładając, że jeśli sprawdzasz istnienie fragmentu, prawdopodobnie użyjesz fragmentu, a tym samym buforuje zawartość lokalnie. Używam blokady z tych właśnie powodów.

1

tylko jako element myśli:

w kontrolerze aplikacji zdefiniować

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 

następnie w dowolnym użytku działania

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

zdaniem

cache "cache_key" do 
view_code 
end 
Powiązane problemy