Posted by Piotr Sarnacki
Thu, 31 Jan 2008 09:09:00 GMT
Jakiś czas temu pisałem ,że mogą szykować się zmiany w deploymencie railsów. Dzisiaj przeczytałem, że Ezra Zygmuntowicz zatrudnił szóstego programistę, którego zadaniem będzie praca nad mod_rubinius dla serwerów Nginx i Apache. Więcej w podcaście z udziałem Ezry
Mały cytat:
More interesting things from the podcast:
- In like one night Evan Phoenix implemented a multiple Rubinius VM running in single OS process in native threads and passing each other messages like it happens in Erlang). This can lead to a great solution to shared hostings and Ruby deployment problem David Hansson wrote about recently.
- Rubinius may support native code compilation along side with bytecode that Rubinius VM can run. Sounds interesting. Ryan Davis is working on Rubinius at Engine Yard and his Ruby2C experiments may be useful.
Posted in Ruby on Rails | Tags deployment, ezra, mod_rubinius, on, rails, rubinius, ruby, zygmuntowicz | 2 comments | 1 trackback
Posted by Piotr Sarnacki
Fri, 22 Dec 2006 13:30:00 GMT
Pastwiłem się ostatnio nad pewną aplikacją wykonując proces zwany, jakże trafnie, optymalizacją. Prawdopodobnie możnaby jeszcze dużo z niej wycisnąć, ale nie lubię się za bardzo przemęczać, a nie ma już teraz części, która jakoś znacznie wyróżniałaby się czasem wykonywania.
Ruby on Rails daje nam bardzo proste mechanizmy obsługujące cachowanie treści (page, action i fragment caching). W kilku słowach:
- page caching - zapisywanie całych stron - podczas pobierania serwer nawet nie tyka railsów, serwowany jest statyczny plik
- action caching - podobne do page caching, z tym że framework jest wywoływany - przydatne jeżeli chcemy mieć dostęp do filtrów (autentykacja, autoryzacja, pewnie jakieśinne acje ;-) )
- fragment caching - zapisywane są fragmenty stron - najmniejszy skok szybkości, ale może się przydać przy dynamicznych stronach, możemy wtedy skeszować tylko te fragmenty, które nie podlegają częstym zmianom
Postanowiłem zacząć od fragment cachingu. Elementem, który generował się najdłużej, nadal było menu. Rekurancja i bardzo dużo iteracji nie jest tym, co tygryski lubią najbardziej ;-)
Menu tworzy metodarender_menu(). Zcachujmy ją więc:
<% cache do %>
<%= render_menu(3, nil, nil) %>
<% end %>
W katalogu tmp/cache (w naszej apliakcji) stworzył się plik: localhost.3000/zycie.cache (zycie to jedna z sekcji). Fajnie. Powstaje tylko jeden mały problem - fragment cachowany jest w pliku z layoutem, więc nie jest przypisany do żadnej akcji. Co jeżeli będziemy chcieli zcachować inny fragment? Z pomocą przychodzi możliwość określenia miejsca przechowywania:
<% cache(:controller => 'abstract', :action => 'menu', :part => 'zycie') do %>
<%= render_menu(3, nil, nil) %>
<% end %>
Kontroler i akcja określane w tym miejscu nie są w żaden sposób powiązane z kontrolerami w naszej aplikacji. Dzięki temu możemy dowolnie określić miejsce zapisania fragmentu (co później się jeszcze przyda). Po odświeżeniu strony pojawił się plik:
tmp/cache/localhost.3006/zycie/abstract/menu.part=zycie.cache
Przed kontrolerem widać jeszcze swoisty namespace określany w routes.rb na podstawie argumentu section w adresie (w tym wypadku jest to sekcja “zycie”). Jeżeli nie ma w aplikacji podobnych myków, w ścieżce nie będzie w ogóle części zycie.
A teraz czas ładowania się strony:
- z generowaniem się menu:
Completed in 1.52972 (0 reqs/sec) | Rendering: 1.50603 (98%) | DB: 0.01315 (0%) | 200 OK (czas jest dość długi, bo moja machina nie jest najwyższej klasy, a odpalonych jest parę aplikacji)
- z menu odczytanym z fragmentu:
Completed in 0.04565 (21 reqs/sec) | Rendering: 0.01936 (42%) | DB: 0.01816 (39%) | 200 OK
Skok szybkości jest ogromny. Jest to też największy dział w serwisie. Dla innej “sekcji” różnica jest mniejsza, ale cały czas bardzo duża:
- z generowanym menu:
Completed in 0.27923 (3 reqs/sec) | Rendering: 0.21727 (77%) | DB: 0.03056 (10%) | 200 OK
- z pamięci cache:
Completed in 0.03424 (29 reqs/sec) | Rendering: 0.01521 (44%) | DB: 0.01059 (30%) | 200 OK
Bingo! :)
Teraz wystarczy tylko zapewnić czyszczenie pamięci podczas modyfikacji. Przeznaczona jest do tego funkcja expire_fragment. W kontrolerze modyfikującym menu dodałem prywatną metodę expire_menu, w której czyszczę fragment:
def expire_menu
expire_fragment(%r{/#{section_name(params[:section])}/abstract/menu/*})
end
Skorzystałem tutaj z możliwości użycia wyrażeń regularnych. Przy określeniu położenia pliku (expire_fragment(:controller => 'abstract', :action => 'menu', :part => 'zycie')) coś się gryzło z owymi sekcjami, o których pisałem na początku - fragment nie chciał się usuwać. Prawdopodobnie w większości przypadków nie trzeba uciekać do takich rozwiązań. Wyrażenia regularne mają jeszcze tą zaletę, że możemy w razie potrzeby wyczyścić wiele fragmentów na raz.
Teraz wystarczy wywołać metodę dla akcji, które modyfikują menu:
after_filter :expire_menu, :only => ['create', 'update', 'destroy', 'move_up', 'move_down']
W tym wypadku sprawa była ułatwiona, gdyż treść, którą chciałem skeszować była generowana w helperze. Co jeżeli chcemy zapisać na przykład listę artykułów? Mamy zapewne jakąś pętlę for article in @articles, którą opatulimy metodą cache. I ok. Ale w kontrolerze nadal pobiera się lista artykułów, która przy odczytywaniu skeszowanej treści nie będzie do niczego potrzebna. W takich wypadkach powinno się używać metody read_fragment. Pobiera ona te same argumenty co expire_fragment i zwraca true jeżeli fragment istnieje. Czyli wszystko można załatwić jest krótkim kodem:
@articles = Article.find(:all) unless read_fragment(:controller => 'articles', :action => 'list')
Jest coraz lepiej, ale moja sadystyczna natura nie pozwala mi przedwcześnie zakończyć znęcania sie nad aplikacją ;-)
Dodałem jeszcze page cahing - daje on największy wzrost prędkości. Dlaczego nie zrobiłem tylko page cachingu olewając fragment caching? Przecież na skeszowanej stronie i tak menu nie będzie się generowało. Odpowiedź jest prosta. Moje menu zapisze się teraz do pliku przy pierwszym wywołaniu dowolnej strony z layoutem default. A całe strony będą się cachowały tylko z danymi argumentami - czyli dla każdego z 250 artykułów i kilkudziesięciu newsów oddzielnie. Ale o tym już w następnej notce :)
Update: Uważajcie na niektóre teksty w sieci ;) Nie twierdzę, że moje rozwiązanie jest jedyne i słuszne, ale wpadłem dzisiaj przypdakiem na posta, w którym autor twierdzi, że trzeba wrzucić do cron’a skrypt, który będzie przeładowywał fragment cache co kilka minut i raczej nie keszować akcji z dużą ilością pobieranych danych. Czyli po prostu nie wie do czego służą metody read_fragment i expire_fragment. I jeszcze taki śmieszny cytacik z końca owego posta :)
Conclusion
Rails has a bunch of built-in page caching mechanisms, but they aren’t THAT useful out of the box. You need to tweak and play around to get what you need, and most of the time you will NOT use the simple solutions.
For our large scale sites we still love the event-driven memcached approaches.
Zastanawiam się co to za large scale sites :)
Posted in Ruby on Rails | Tags caching, on, optymalizacja, rails, ruby | no comments