RoR na sterydach, czyli rzecz o buforowaniu
Zaczynając pisać aplikację w RoR możemy szybko zobaczyć efekty naszej pracy. Jest to właśnie jedna z zalet tego framework’u. Niestety szybko okazuje się, że problemem jest wydajność. Nawet po rezygnacji z automatycznego przeładowywania prawie wszystkich komponentów (czyli odpaleniu aplikacji w trybie production a nie development) przy każdym wywołaniu, okazuje się że aplikacja nie działa tak szybko jak byśmy chcieli. Co zrobić?
Pierwszym krokiem powinno być przemyślenie strategii buforowania (caching). Co w tym zakresie Railsy mogą zaoferować?
Są dostępne trzy rodzaje buforowania:
- page
- action
- fragment
page - cała wygenerowana strona jest buforowana i serwowana jest na poziomie serwera WWW. RoR drugiego żądania o ten URL nie dostaje (włącza się to dla określonych urli, nie całej aplikacji od razu). Oznacza to, że jeśli na stronie są jakieś treści dynamiczne, każde żądanie będzie zwracało tę samą treść. Buforowanie takie dobre jest jak strona ma stały kontent zależny tylko od URL (nie używa treści zależnych np od stanu sesji w cookie albo czasu). Oznacza to, że ten tryb buforowania zupełnie nie nadaje się dla stron z autoryzacją – całość zostanie utrwalona tak jak za pierwszym razem czyli jeżeli treści na stronie wymagają autoryzacji, po włączeniu tego buforowania całość treści będzie dostępna dla każdego, bez żadnych ograniczeń.
Całość buforowana jest na dysku w katalogu public. Są tam tworzone statyczne pliki HTML odpowiadające wygenerowanym stronom. ‘Expirowanie’ cache polega na kasowaniu tej zawartości.
Warto zauważyć, że w ten sposób Railsy mogą zostać wykorzystane do wygenerowania statycznej strony WWW – wystarczy włączyć ten tryb buforowania i przejść jakimś crawlerem przez wszystkie linki aby w katalogu public mieć zrzut gotowy do publikacji jako statyczny HTML.
Aby włączyć ten tryb w kontrolerze należy użyć dyrektywy caches_page podając które akcje mają być buforowane:
class ArticleController < ApplicationController
caches_page :list
A usuwamy z bufora przez:
def save
[kod zapisujący nowy artykuł]
expire_page(:controller => :article, :action => :list)
end
action - wynik wywołania poszczególnej akcji jest buforowany przez RoR. Oznacza to, że kolejne wywołania metody zwracają zawartość z bufora. Różni się to od trybu page tym, że wywoływane są filtry before_filter – oznacza to, że można np zrobić weryfikację użytkowników. Ale treść raz wygenerowana jest serwowana z bufora dopóki nie zostanie wprost ‘wyekspirowana’
class ArticleController < ApplicationController
caches_action :show
A usuwamy z bufora przez:
def save
[kod zapisujący edytowany artykuł]
expire_action(:action => :show, :id => params[:id])
end
Niestety obie metody są „do bani” jeśli kontent jest nie oparty o URL (czy raczej nie tylko o URL), jak np sesje w cookies. Wtedy zostaje fragment caching. Czyli na poziomie szablonów RHTML (lub innych) określasz, którą cześć rezultatu należy zbuforować a potem się w odpowiednich momentach martwisz o czyszczenie bufora. Np w Run-N-Share:
app/views/route/show_route.ejs:
[cut]
<%
cache( :action => ’show_route’,:part => @pid) do
Point.find(:all, :conditions => [ "route_id = ?", @pid]).each {|x|
%>pt = new GLatLng(<%=x.lat.to_s%>,<%=x.lng.to_s%>)
[cut]
<% end #cache %>
Czyli kod JavaScript generujący przez Google Maps API trasę renderuje się raz (wszystko co w obrębie pętli cache() do ... end). Jeśli trasa ulega zmianie albo jest usuwana trzeba powiadomić RoR że dane w buforze są już passe:
app/controllers/route_controller.rb:
def update
[cut kod robiący zmiany]
expire_fragment(:action => ’show_route’, :part => params[:id])
end
Domyślnie bufor dla fragment i action cache znajduje się w katalogu aplikacji w tmp/cache.
Jest to tylko dotknięcie tematu buforowania w RoR, gdyż dla większych aplikacji niezwykle istotny jest wybór formy bufora – ma to ścisły związek z wydajnością i skalowalnością. Dostępne są następujące formy: domyślny system plików, serwer DRb lub memcached.
Tagi: rails