Posted by Piotr Sarnacki
Sun, 03 May 2009 18:10:00 GMT
Ponad 4 miesiące temu światem Ruby’iego zatrzęsła wiadomość o połączeniu się 2 popularnych frameworków – Ruby on Rails i Merba. Dzisiaj, tylko kilka dni dzieli nas od ujrzenia pierwszych aplikacji, które będą napisane w Rails 3. Carl Lerche (twórca między innymi routera do merba) oraz Yehuda Katz na tegorocznym RailsConf będą mówić o Rails3 i mountable apps.
Na stabilną wersję będzie trzeba jeszcze trochę poczekać, ale trzymam kciuki, żeby na RailsConf rzeczywiście udało im się pokazać działającą aplikację.
W tym poście chciałbym krótko opisać to co ma się znaleźć w Rails 3. Z niemałą niecierpliwością śledzę newsy i doniesienia o nowych railsach i nie mogę się już doczekać niektórych usprawnień.
ActionORM (wcześniej ActiveORM)
Zwolennicy Merba bardzo podkreślają w swoim frameworku niezależność od konkretnych technologii. Było to reklamowane głównie jako “ORM agnosticism” czyli możliwość podpięcia do Merba jednego z wielu “ORMów” (DataMapper, ActiveRecord, Sequel) w porównaniu do Railsowej monolityczności w postaci jednego słusznego ActiveRecorda.
Jaki właściwie jest problem w używaniu innego ORMa w naszej aplikacji? Na poziomie modeli i kontrolerów nie ma to z reguły większego znaczenia. Przecież Railsów nie obchodzi czy klasa w katalogu app/models dziedziczy po ActiveRecord::Base, czy dołącza moduł: include DataMapper::Resource
Kłopot zaczyna się kiedy chce się korzystać z wielu helperów, które jako argument przyjmują obiekt ActiveRecord. Najprostszy przykład:
<% form_for @article do |f| %>
<%= f.text_field :title %>
<% end %>
Jeżeli @article nie jest obiektem AR, to poprawność działania tej metody zależy tylko od tego czy interfejs danego ORMa tutaj używany jest taki sam. W tym akurat przypadku problemem może być sprawdzanie błędów w walidacji. Railsy domyślnie dodają wrapper do pól z błędami, więc jeżeli ORM ma inny interfejs do pobrania informacji o błędach, to metoda po prostu nie zadziała.
W tym momencie jest to w Railsach zrobione z użyciem alias_method_chain i jeżeli obiekt nie ma zaimplementowanej metody errors, to tag pozostaje niezmieniony, więc można samemu dołączyć swoją własną implementację lub dodać potrzebne metody do modelu. Jednak wszyscy chyba się zgodzą, że nie jest to najbardziej elegancki sposób.
Jeżeli chodzi o Merba, to sprawa wygląda tylko trochę lepiej niż w railsach. Jest co prawda wsparcie dla 3 najpopularniejszych ORMów, ale implementacja nie pozwala na łatwe dodanie kolejnych ORMów bez grzebania w kodzie. Jeżeli ORM, którego chcesz używać ma interfejs zgodny z DataMapperem, Sequelem lub ActiveRecordem, to wszystko jest ok, ale jeżeli jest to coś innego, to masz problem.
Odpowiedzią na to ma być ActionORM. Czym jest ActionORM? Jest to proste proxy dla obiektów. ActionORM udostępnia API takie jak ActiveRecord (dzięki temu w kodzie railsów nie będzie trzeba wiele zmieniać). W prostych słowach ActionORM ma sprawić, że obiekty innych ORMów będą wyglądały jak obiekty ActiveRecord (czyli klasyczne wykorzystanie duck typing)
Tutaj jest przykład proxy dla Datamappera: http://github.com/lancecarlson/rails/blob/0faae5b971c3dbf3b1c4ead19504580233bbc7fa/activeorm/lib/active_orm/proxies/datamapper_proxy.rb
Różnice są jak widać niewielkie, ale podejrzewam, że będzie można dodać więcej metod, które się różnią pomiędzy implementacjami (oczywiście w granicach rozsądku, sprowadzanie całego interfejsu do jednego przypadku było by pozbawione sensu, chodzi o proste operacje, które każdy ORM ma rozwiązane podobnie, ale różni się API).
Rack Middleware
Railsy od jakiegoś czasu wykorzystują w pełni dobrodziejstwa Rack. Co więcej, różne części frameworka są zaimplementowane jako rack middlewares. Kontroler, router, obsługa sesji i wiele innych rzeczy to teraz po prostu kolejne middlewares (przepraszam za te anglojęzyczne wstawki, ale naprawdę nie mam pojęcia jak to ładnie przetłumaczyć). Dzięki temu i dodaniu Rails metal aplikacje są coraz bardziej modularne.
Programiści railsów dążą w tej chwili do jeszcze szerszego wykorzystania racka. Wszystko to idzie w stronę jak najłatwiejszego połączenia komponentów różnego typu. Na przykład możliwość podłączenia do routera kontrolerów railsowych, aplikacji sintatry i aplikacji merba (jako jedną aplikację oczywiście).
Optymalizacja i refaktoryzacja
Największą i najlepszą według mnie zmianą w railsach jest refaktoryzacja kodu ActionPack. Na czym ona polega? W bardzo prostych słowach: stworzony jest AbstractController, z którego dziedziczyć będą inne kontrolery, a w samym kodzie jest zastosowana dużo większa enkapsulacja. Jakie to ma znaczenie dla użytkowników railsów? Będzie istniał jeden interfejs dla wszystkich kontrolerów, dzięki czemu na przykład ActionController i ActionMailer będą mogły współdzielić funkcjonalność (żeby nie iść daleko, dopiero niedawno do mailera dodana została obsługa layoutów). Bardzo łatwe będzie też dodanie Parts znanych z merba (PartsController również dziedziczy w Merbie z AbstractControllera).
W chwili obecnej podobną funkcjonalność do Merb Parts udostępniają Rails Cells, ale nie jest to rozwiązanie idealne (implementacja jest zagmatwana, w cellach widoczne są zmienne instancji z kontrolerów i innych celli, podejrzewam, że jest to wolniejsze niż parts).
Co jeszcze? Yehuda Katz wziął się między innymi za optymalizację callbacków, czy bloku respond_to. Polecam również inne artykuły na temat refaktoryzacji na jego blogu.
Na pewno szybszy będzie też router. W chwili obecnej największym problemem jest to, że router generuje bardzo dużo metod (wszystkie metody _url i _path), co w rezultacie przyczynia się do bardzo wolnego startu aplikacji. W czasach mongreli nie miało to dużego znaczenia, bo aplikacje były odpalane raz i tylko sporadycznie restartowane. Teraz kiedy na wielu serwerach zainstalowany jest mod_passenger, czas startu aplikacji jest dużo ważniejszy – instancje, które nie dostają requestów przez 5 minut (domyślne ustawienie) są wyłączane dzięki czemu nie zajmują zasobów.
Nowy router można przetestować już teraz. Po więcej odsyłam na the merbist
Mountable apps
Jest to jedna z tych rzeczy, na które czekam w Railsach 3 najbardziej. Dosłownie liczę godziny do rails conf i prezentacji na ich temat (mam nadzieję, że będą nagrywane, bo niestety nie miałem wolnych kilku tysięcy złotych, żeby się pojawić osobiście na konferencji ;-).
Jakieś namiastki mountable apps są już dostępne w postaci Rails Engines i Merb Slices. Jest to jakieś rozwiązanie, ale na pewno dalekie od idealnego. Żeby skorzystać z którejś z tych opcji trzeba napisać specyficzny rodzaj aplikacji, a każde z tych rozwiązań jest w niektórych miejscach niedopracowane. Celem mountable apps będzie możliwość łatwego połączenia 2 aplikacji bez żadnych zmian w kodzie.
API dla pluginów
Jedną z rzeczy, którą wypromował Merb jest publiczne i prywatne api. Dzięki temu pisanie pluginów nie wiąże się tam z używaniem alias_method_chain i ogólnie pojętym monkey patchingiem na każdym kroku. A jeżeli czegoś nie da się zrobić używając tylko publicznego api, to jest to bug. Jest to chyba jedna z trudniejszych rzeczy w całej refaktoryzacji. Nie wiadomo dokładnie czego będą potrzebować twórcy pluginów, niektóre metody trzeba będzie dopiero stworzyć.
Ogromnym plusem takiego podejścia jest to, że twórcy frameworka nie muszą się martwić o prywatne metody, ważne żeby publiczne API się nie zmieniało. Dzięki temu pluginy napisane zgodnie z wytycznymi, czyli nie używając prywatnego API nie będą się wysypywały na każdej kolejnej wersji (co bardzo często można zaobserwować w tym momencie), a jeżeli coś się zmieni w publicznym API przy okazji większych zmian, to twórcy pluginów będą mogli się o tym łatwo dowiedzieć bez śledzenia commitów na githubie.
Podsumowanie
Jestem zdania, że połączenie merba i railsów to jedna z najlepszych rzeczy, która mogła się przytrafić programistom webowym. Railsy będą szybsze, bardziej modularne i łatwiejsze w rozszerzaniu. Tylko czekać na wyniki refaktoryzacji :)
Postaram się w najbliższych tygodniach pisać o tym co dzieje się z Railsami3, jak idą prace i co jeszcze ciekawego będzie można zobaczyć.
Posted in Ruby on Rails | Tags merb, rails, rails3, refaktoryzacja, ruby, rubyonrails | 7 comments | no trackbacks
Posted by Piotr Sarnacki
Sat, 03 Jan 2009 20:45:00 GMT
Niestety nie będzie to wpis o tym jak refaktoryzować swoją aplikację, ale raczej post o refaktoryzacji samego frameworka w ramach przygotowań do Rails 3.
Railsy pożarły mózg Merba i posiądą jego tajemne moce (zachęcam do wysłuchania tego podcastu, chłopaki od railsenvy genialnie mówią o tym co się dzieje w świecie railsów) – o tym już każdy na pewno wie. Czy to dobrze? Oczywiście, że tak :)
Jeżeli ktoś dalej ma problemy z tą decyzją (podejrzewam, że może tak być w wypadku fanów merba), polecam prezentacją Living with legacy software wygłoszoną przez Davida Heinemeiera Hanssona – nie jest w żaden sposób bezpośrednio związana z połączeniem, ale zmienia podejście do aplikacji i ich kodu.
Ale nie o to, nie o to, nie o to
Yehuda Katz ostro wziął się do pracy jeszcze podczas świąt i co jakiś czas wrzuca na bloga informacje o tym jak refaktoryzuje i jakie ma plany wobec Railsów. Informacje traktujące o projektowaniu, refaktoryzacji i architekturze ciężko jest znaleźć w sieci, więc gorąco polecam śledzenie tego co pisze Yehuda – do tej pory można przeczytać między innymi o enkapsulacji ActionView i ActionController czy o optymalizacji respond_to. Dodajcie sobie tego bloga do czytnika, naprawdę warto.
Co najlepsze, wiele z tych zmian dotyka tylko wewnętrznej implementacji railsów, więc można liczyć, że będą one dostępne dla ruby on rails 2.3.0.
Posted in Ruby on Rails, Programowanie | Tags katz, merb, rails, rails3, refaktoryzacja, rubyonrails, yehudakatz | no comments | no trackbacks
Posted by Piotr Sarnacki
Fri, 03 Oct 2008 10:37:00 GMT
W railsach od jakiegoś czasu można używać named_scope. Jest to bardzo fajny mechanizm umożliwiający łatwiejsze skonstruowanie zapytania używając wcześniej zdefiniowanych metod. Wcześniej był dostępny jako plugin has_finder.
Wygląda to mniej więcej tak:
named_scope :active, :conditions => { :active => true }
named_scope :paid, :conditions => { :paid => true }
named_scope :recent, lambda { { :conditions => ['created_at >= ?', 1.week.ago] } }
Po zdefiniowaniu takich sope’ów (macie pomysł jak to spolszczyć?) można ich używać w ten sposób (zakładam, że scope’y zostały zadeklarowane w modelu Product):
Product.active.paid.recent
Jak można łatwo zauważyć, scope’y można łączyć w łańcuchy. Dzięki takiej konstrukcji otrzymujemy prosty i treściwy kod, który łatwo zrozumieć. Teraz coś lekko trudniejszego:
named_scope :in_category, lambda { |category| { :conditions => { :category_id => category } } }
# i użycie
Product.active.recent.in_category(category)
O co chodzi? Jako 2 argument można przekazać lambdę, która zostanie wywołana przy użyciu scope’a i powinna zwrócić Hash.
Ale to nie są jedyne zastosowania scope’ów. Można do nich przekazać też inne parametry, które może przyjąć metoda find.
Na przykład order, offset i limit:
named_scope :order, lambda { |*args| { :order => args.first || "created_at DESC" } }
named_scope :offset lambda { |offset| { :offset => offset } }
named_scope :limit, lambda { |*args| { :limit => args.first || 10 } }
# i użycie:
Product.active.order("id DESC").offset(10).limit
W pierwszym i trzecim scope’ie zastosowałem trick, który pozwala na użycie zdefiniowanej wartości domyślnej w razie gdy żadna nie zostanie podana. `*args` pakuje argumenty do tablicy. Jeżeli args.first zwróci nil, to znaczy, że tablica jest pusta, czyli nie zostały podane żadne argumenty i trzeba zaaplikować wartość domyślną.
Coś jeszcze? Fajnie by było, żeby można było sortować nie tylko po wartościach z jednej tablicy… a do tego wypadałoby użyć joins/include. Nic prostszego.
named_scope :joins, lambda { |joins| { :joins => joins } }
# a teraz można tak:
Product.active.joins(:user).order('users.email')
How cool is that?
No i na koniec coś co ostatecznie przekonało mnie, że named_scope jest genialnym wynalazkiem. Jeżeli ktoś robił kiedyś formularze, w których można wybrać kilka opcji i na ich podstawie trzeba zbudować zapytanie, zapewne wie, że jest to nieco upierdliwe. Używając `named_scopes` można to zrobić bardzo łatwo.
Ryan Bates napisał plugin scope_builder, dzięki któremu można to zrobić jeszcze łatwiej. Przypuśćmy, że mamy formę, w której można zaznaczyć kilka pól i po ich wysłaniu należy na ich podstawie pobrać odpowiednie rekordy z bazy.
# przygotowujemy listę parametrów, które użytkownik może ustawić
parameters = [:active, :paid, :recent, :title]
# Tworzymy scope
@products = Product.scope_builder
# teraz można sprawdzić, które pola zostały zazanczone
parameters.each do |param|
@products.send(param) if params[param]
end
# na koniec można na przykład dodać paginację
@products = @products.paginate(:per_page => 10, :page => params[:id])
Dla mnie genialne. :) Można dzięki temu łatwiej tworzyć zaawansowane wyszukiwanie, sortowanie i inne tego typu rzeczy.
Oczywiście najlepiej jest zamknąć taki kawałek kodu jako metodę modelu, zgodnie ze skinny controller, fat model, ale to pozostawię jako ćwiczenie ;-)
Posted in Ruby on Rails, Programowanie | Tags named_scope, rails, ruby | no comments | no trackbacks
Posted by Piotr Sarnacki
Tue, 30 Sep 2008 14:38:00 GMT
Przez jakiś czas nie pisałem nic na blogu. Długo zbierałem się do napisania czegokolwiek, od dawien dawna miałem przetłumaczyć wpisy na moim angielskojęzycznym blogusiu. Tak bardzo mi się nie chciało, że jak tylko siadałem do kompa z zamiarem przetłumaczenia czy napisania czegoś podobnego coś mnie odrzucało.
Dlatego, żeby więcej się nie męczyć podsumuję ten temat i podam linki – większość ludzi, którzy tutaj trafią i tak na pewno zna angielski.
Napisałem apache upload progress module do apacha. Moduł, dzięki któremu można pobrać dane o wysyłanych plikach. Format odpowiedzi oparłem o moduły tego typu dla nginxa i lighttpd dlatego jeżeli wcześniej ktoś używał któregoś z nich, przesiadka będzie całkowicie bezbolesna. Sam używałem wcześniej nginx upload progress (głównie dlatego napisałem moduł do apacha) i po przerzuceniu aplikacji na apacha z nowym modułem nie trzea było zmieniać ani jednej linijki. Na angielskim blogu zamieściłem opis instalacji i konfiguracji modułu.
Żeby można było w miarę łatwo używać modułu napisałem także pluginy do “prototype’a”http://github.com/drogus/prototype-upload-progress/tree/master i jquery, które obsługują pasek postępu. Przykłady są w repozytoriach, umieściłem je także na serwerze dla obczajenia w akcji. Popełniłem także tekst o tym jak hackowałem plugin, żeby działał w safari – safari w tym momencie nie było wcale lepsze od IE… nawet powiedziałbym, że gorsze. Swoją drogą plugin nie działa w najnowszej operze (działał w 9.5 zdaje się), więc jeżeli ktoś ma chwile czasu i mógłby sprawdzić dlaczego i co można zrobić, żeby działał, to może wnieść swój wkład w rozwój (dodam, że nie jest to ta sama kwestia co dla safari, bo to już sprawdziłem) – nie sądzę żebym miał teraz czas sam na tym siedzieć.
Dodatkowo napisałem jeszcze tekst o tym jak można fajnie uatrakcyjnić aplikację używając jQuery. Część pierwsza. Część druga będzie jak będę miał więcej czasu, czyli pewnie niezbyt szybko.
Pozdrowienia ze słonecznego Wrocławia, gdzie mam zamiar mieszkać przez najbliższy rok (miła odmiana od Pruszkowa :).
Posted in Javascript, Ruby on Rails, Programowanie | Tags apache, javascript, progress, rails, upload | no comments | no trackbacks
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 | no comments | no trackbacks
Posted by Piotr Sarnacki
Sat, 01 Sep 2007 12:40:00 GMT
Zdarza mi się ostatnimi czasy przeczytać komentarz, że książki programistyczne są już niepotrzebne, bo i tak wszystko jest w necie. A nawet, cytuję: “w książkach to jest naćkane, jak chcesz konkretów to nie kupuj książek”.
Całkiem głupie to ;-)
Dla mnie książki programistyczne są niezastąpione. I najchętniej kupiłbym sobie połowę pozycji dostępnych na rynku. Po czym prawdopodobnie 1/3 bym nie przeczytał, ale to już inna kwestia. Ogólnie rzecz biorąc większości rzeczy zawartych w książkach można pewnie nauczyć się samemu, z lekką pomocą wujka Googla. Ale o ile przyjemniej przeczytać jak to robią profesjonaliści (o ile nikt nie da się nabrać niektórym profesjonalistom inaczej)
Po takim wstępie nie mogę się nie pochwalić, że jakiś czas temu kupiłem wspólnie z ludźmi od nowego projektu (na razie nie zapeszam. za jakiś czas pewnie coś napiszę) parę bardzo fajnych książek.
- Don’t make me think – genialna książka o usability. Bardzo krótka i treściwa. Autor starał się napisać książkę, którą będzie się dało przeczytać w parę godzin. I muszę przyznać, że bardzo dobrze mu to wyszło. Idealna dla ludzi, którzy nie są specami w tej kwestii, a chcą poprawić użyteczność swoich stron.
- Agile development with Rails – Pozycja obowiązkowa dla uczących się RoR. Co prawda teraz niewiele mi się przyda, ale czułbym się źle jakby jej nie kupił. Korzystałem dość sporo z ebooka z amule’a. No i przyda się bliźniemu, który właśnie zaczyna się uczyć RoR.
- Ruby for Rails – jeszcze nie przeczytałem, ale po przekartkowaniu zapowiada się bardzo ciekawie. Napiszę coś więcej jak wreszcie się za nią wezmę ;-)
- The Ruby Way – bardzo dobra książka. Jestem właśnie w trakcie czytania. Podaje bardzo dużo rozwiązań często spotykanych problemów zgddnie z “ruby way”. Ponad 600 stron esencji programowania ;-)
- JavaScript: The Definitive Guide – niektórzy narzekają, że książka bardziej zbliżona jest do dokumentacji niż do książki “do nauki”. Dla mnie to plus. Javascripta znam całkiem dobrze. Pewnie dzięki temu, że w poprzednim stuleciu, kiedy o bibliotekach takich jak jQuery nikt nie słyszał, bawiłem się w pisanie różnego rodzaju latających warstw, wysuwanych menu i innych arkanoidów (nawet fajnie się grało ;-). W tym momencie potrzebuję właśnie takiej dokumentacji, do której mogę spojrzeć podczas pisania i zobaczyć jakie metody udostępnia dana klasa, albo jak powinien się zachowywać jakiś kod. Jeżeli ktoś chce pisać coś więcej niż efekty z użyciem jQuery (czy innego script.aculo.us), to naprawdę zachęcam do kupna.
Podejrzewam, że gdyby początkujący programiści więcej inwestowali w wiedzę książkową to byłoby dużo mniej partactwa. Szczególnie, że książki związane z Rubim i RoR oprócz nauki programowania próbują od początku wpajać dobre praktyki programistyczne. I koniecznie trzeba przeczytać Pragmatycznego programistę :)
Posted in Programowanie | Tags javascript, książki, programowanie, rails, ruby, rubyonrails | no comments
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
Posted by Piotr Sarnacki
Sat, 16 Dec 2006 14:30:00 GMT
Dzisiaj chcialem napisać o swoich bojach z liczbą requestów na sekundę.
Po przepisaniu pewnej aplikacji okazało się, że jej szybkość nie powala na podłogę. A właściwie to powala, ale w złym tego wyrażenia znaczeniu.
Tak właściwie to spodziewałem się czegoś takiego podczas pisania. Od jakiegoś czasu próbuję się oduczyć przedwczesnej optymalizacji i pisania zbyt elastycznego kodu, tam gdzie on się nie przydaje. Wielu początkujących programistów pisze kod na wyrost. Gdy trzeba rozwiązać problem, próbują napisać niemalże bibliotekę, którą można będzie wykorzystać później w wielu innych przypadkach. Idea fajna. Tylko w praktyce i tak później używa się tych w bólach powstałych linijek kodu w jednym miejscu, w którym wystarczyłoby proste rozwiązanie. O przedwczesnej optymalizacji kilka słów napisał na swoim blogu Stefan Kaes. Należy jej unikać, ale trzeba dobrze wszystko przemyśleć, żeby później nie skończyć z przepisywaniem połowy aplikacji.
Wracając do sedna (o ile takowe w ogóle istnieje). Przed optymalizacją poczytałem trochę gdzie najłatwiej zyskać upragnione requesty. Od razu można polecić wspomniany wyżej rails express. Oprócz tego bloga pan Google mówi jeszcze o kilku artykułach:
Pierwsza rzecz, o której należy wspomnieć: testy muszą być wykonywane przed i po zmianach. Czasami próby optymalizacji kończą się na zmniejszeniu szykości. A tego byśmy przecież nie chcieli ;-)
Liczbę requestów na sekundę najłatwiej sprawdzić odpalając aplikację w trybie production. Wyświetlając plik log/production.log poleceniem tail -f log/production.log mamy na bieżąco informacje o czasie wtgenerowania strony podzielonym na czas wykonywania zapytań do bazy i renderowania strony:
Processing PageController#index (for 127.0.0.1 at 2006-12-11 23:39:32) [GET]
Session ID: 7d4349aab2db813aababe45624ff9a25
Parameters: {"action"=>"index", "controller"=>"admin/page"}
Rendering within layouts/application
Rendering admin/page/index
Completed in 0.11678 (8 reqs/sec) | Rendering: 0.09057 (77%) | DB: 0.02425 (20%) | 200 OK [http://localhost/admin/pages]
W moim wypadku zapytania do bazy danych wykonywały się jakieś 80-90% czasu. W takim wypadku dobrze jest uruchomić aplikację w trybie development i zobaczyć w logach jakie zapytania wykonują się podczas generowania strony. U mnie funkcją wykonującą najwięcej zapyń było tworzenie menu. Klient zażyczył sobie rozwijanego menu javascriptowego. Z reguly odradzam takie rozwiązanie (w większości wypadków jest to niewygodne i niepotrzebne), ale klienci są uparci, więc w trosce o swoje zdrowie psychiczne ustępuję ;-). W jednej z trzech sekcji ma ono około 120 elementów (a liczba ta pewnie będzie się zwiększała z biegiem czasu).
Struktura użyta do budowy menu to oczywiście drzewo - rekurencyjna funkcja wyświetla wszystkie elementy. Najprostsza jej wersja, przy każdym wywołaniu wykonuje jedno zapytanie, łatwo można policzyć, że zaytań było kilkadziesiąt (jedno dla każdego elementu, który posiadał dzieci). Przepisałem funkcję tak, aby kolekcja była pobierana tylko raz, a później przekazywana jako jeden z argumentów. Bingo! Szybkość wzrosła około 2 razy.
Nadal jednak coś było nie tak - metoda children, którą zapewniał plugin acts_as_tree. Już chciałem ją podmienić na swoją, która dostawałaby kolekcję w argumencie, kiedy przypomniałem sobie, że na forum ruby on rails Paweł Kondzior (PaK) pisał o swoim pluginie acts_as_tree_element. Plugin bardzo fajny - generuje tylko jedno zapytanie i zapewnia funkcje, które pracują na pobranej kolekcji - dokładnie to czego szukałem! Paweł pomyślał jeszcze o jednej rzeczy: elementy zapisane jako zmienna instancji są cache’owane w trybie produkcyjnym (o ile config.cache_classes == true). Szybkość wzrosła o kilkanaście procent.
Pojawił się jednak problem - scachowana raz kolekcja pozostaje w pamięci i nie bardzo daje się wyczyścić - nie jest to na rękę, gdy chcemy pobierać różne części menu. Po krótkiej rozmowie na IRC’u Paweł lekko zmodyfikował plugin - cache jest teraz hashem z zapamiętanymi warunkami w zapytaniu. No i git! ;-)
Jakie z tego wnioski? Większość aplikacji w dość dużym stopniu eksploatuje bazę danych. Zadbajmy o to, żeby zapytań było jak najmniej. Dodatkowo warto używać opcji include w metodzie find. Warto też założyć indeksy na kolumnach w bazie. Których? Najprostsza odpowiedź to: na tych, na podstawie których wyszukujemy, lub sortujemy wyniki. Ale takie proste to nie jest - można popytać u pana Google’a, albo w księgarni w celu nabycia dodatkowej wiedzy.
W tym momencie czas wykonywania zapytań spadł do kilku procent. Pomyślałem, że warto jest przyjrzeć się rzeczom, które wykonują się podczas renderowania strony. W artykule Kaes’a czytamy, że dużo czasu zajmuje generowanie adresów za pmocą linkto i urlfor. A co mamy w menu? Około 120 takich linków. Zamieniłem url_for na stringa ze zmieniającym się numerem id artykułu. Szybkość dość znacznie wzrosła.
Uruchomiłem testy Apache Bench (ab -n 1000 -c 20). Wartość, ktorą zobaczyłem bardzo mile mnie zaskoczyła: 40% szybciej od wersji php.
Co jeszcze można zoptymalizować? Zapewne wiele rzeczy, ale mało jest tych naprawdę żrących pamięć miejsc. Najlepiej pomyśleć o cache’owaniu treści - railsy dają możliwość bardzo łatwego cache’owania zarówno całych stron, jak i pojedyńczych akcji, czy fragmentów strony. Jeżeli zmiany są rzadkie na pewno warto pomyśleć o cache’owaniu całych stron - przy odczycie takiej strony serwer nawet nie dotyka railsów. Jeżeli aplikacja jest dynamiczna można pomyśleć o cache’owaniu fragmentów, które rzadko sie zmieniają, albo o memcached (w wypadku naprawdę dużego obciążenia: ~3000 req/s).
Życzę powodzenia w zdobywaniu kolejnych rps’ów ;-)
Posted in Ruby on Rails | Tags optymalizacja, rails | no comments
Posted by Piotr Sarnacki
Thu, 14 Dec 2006 23:30:00 GMT
Jakiś czas temu miałem wątpliwą przyjemnmość modyfikowania pewnego systemu CMS napsanego w PHP, w celu dopasowania go do klienta. CMS słaby jeżeli chodzi o kod (dość wolny, w niektórych miejsach mała dbałość o bezpieczeństwo, kiepskie zaprojektowanie). Po przejrzeniu specyfikacji stwierdziliśmy (z kolegą, który miał pisać to ze mną), że poprawki nie będą duże i można je będzie wprowadzić bez wielu zmian w kodzie. Nic bardziej mylnego.
Chciałbym podzielić się wrażeniami z rozwijania dużej (jak się później okazało) aplikacji. Na razie wolałbym nie mówić o tym dla kogo ją pisaliśmy, gdyż jeszcze do końca się nie uwolniliśmy od tego projektu. Człowiek uczy się na błędach, więc o błędach chciałem napisać.
Odejście od specyfikacji
Niedługo po rozpoczęciu prac można było zobaczyć wyniki. Wstawiliśmy pierwszą wersję szablonów HTML, jeszcze bez ostatecznego formatowania, ale z grubsza przypominającą otrzymane grafiki. Od razu zaczęły się uwagi i spekulacje jakie moduły przydałoby dopisać i co zmienić aby ułatwić pracę przy wstawianiu treści. Stwierdziliśmy, że robiąc drobne poprawki nie przysparzamy sobie zbyt dużo pracy, a klient będzie zadowolony, więc jest nam to nawet na rękę. Bardzo duży błąd. Dopisując jeszcze funkcjonalność zawartą w specyfikacji, zaczęliśmy ową specyfikację zmieniać. Większość propozycji wychodzących od klienta nie było przemyślanych, czasami po dopisaniu kodu trzeba było go usunąć, bo “to jednak nie było to”. Kod zaczął się powoli zaśmiecać, a my niejako staliśmy w miejscu - robiliśmy poprawki, a aplikacja była cały czas daleko od ukończenia.
Wybór technologii
W tamtym okresie nie myśleliśmy poważnie o czymkolwiek innym niż PHP. Baliśmy się deploymentu, klopotów z serwerem. Poza tym prawie gotowy system mieliśmy już napisany (należy w tym miejscu przypomnieć, że prawie robi wielką różnicę). Napisany, ale tak jak już wspomniałem napisany kiepsko. Trzymaliśmy się konwencji, w której napisany był CMS, a liczba linii kodu i funkcji drastycznie rosła. System był coraz bardziej zaśmiecony, o czymś takim jak moduły do testowania nawet nikt nie myślał, a często podczas poprawiania jednych błędów wynikały inne (zarówne w kodzie jak i te na poziomie projektowania). Nie wyglądało to dobrze.
Niewiedza klienta!
Niestety nie wzięliśmy poprawki na to z kim będziemy mieli dane pracować. Informatycy z owej instytucji nie są osobami kompetentnymi - cały czas wnosili o zmiany w serwisie, a bardzo łatwo było stwierdzić, że nie znają się nic a nic na specyfice aplikacji internetowych, ani na programowaniu czegokolwiek (ewentualnie jakiejś prostej w obsłudze pralki). Najlepiej chyba to opisze tekst szefa informatyków: Linki, na które kliknę zmieniają kolor (wcześniej proszono nas o zmianę visited na odmienny kolor) i zostają takie nawet przez kilka dni. Trzeba było mu wytłumaczyć na czym to polega… Albo wysyłanie maili: “Prosimy o zrobienie archiwum dla całego serwisu”, żeby zaraz potem wysłać maila “Oczyistym jest, że archiwum powinno być oddzielne dla każdego działu w serwisie”.
Najbardziej rozbrajały mnie te oczywstości, których było niesamowicie dużo. Wszystko co zrobiliśmy, a co nie było zgodne z ich błyskotliwymi przemyśleniami komentowane było: “Oczywistą rzeczą jest… “. Tłumaczenia, że nic nie jest takie oczywiste na niewiele się zdały.
Przedwczesny start
Z racji tego, że czas naglił zmuszeni byliśmy uruchomić serwis i przeprowadzić szkolenia przy niedokończonej wersji - od strony użytkownika wchodzącego na stronę wszystko wyglądało dobrze, ale w panelu administracyjnym sporo było rzeczy, które mieliśmy zmienić.
Kozi róg?
W tamtym momencie byliśmy w sytuacji podbramkowej. System był nieskończony, a było parę błedów i rzeczy do dopisania. Wtedy oczywiście klient przeszedł do ofensywy i nie patrząc na to ile zrobiliśmy rzeczy poza specyfikacją zaczął wytykanie nam “ewidentnych błędów, które uniemożliwiają pracę nas serwisem”.
Współpraca
A raczej jej brak. Niestety to był jeden z tych projektów, w których nie mamy przyjaźnie nastawionych użytkowników. Cały system od początku był postrzegany jako zło konieczne - pracownicy mieli wprowadzać na stronę wyniki swojej pracy. Czyżby nagle trzeba było zacząć pracować? Najgorszym elementem okazał się edytor WYSIWIG. Pomimo długich tłumaczeń, że tekst z word’a nie zawsze się w nim dobrze sformatuje notorycznie mieliśmy telefony, że tekstu nie da się sformatować. Niezliczoną ilość razy musiałem powiedzieć o tym jak wtedy należy postąpić: zapisać dokument jako plik .txt, wkleić do edytora i dopiero wtedy sformatować. Według użytkowników to jest za dużo pracy. Cóż z tego, że w większości wypadków formatowanie tektu zajmowało mi ok. 10 min? Najlepszy byłby oczywiście system, który sam napisałby i sformatował artykuł, a później zaparzył kawę i poszedł do kiosku po gazetę.
Rewolucja?
Po jakimś czasie poprawiliśmy większość błędów, ale pewne rzeczy ciężko było zmienić bez przebudowy wielu linijek kodu. Zdecydowaliśmy się na przepisanie systemu używając ruby on rails. Jak pomyśleliśmy tak też zrobiliśmy i mamy teraz tak samo wyglądający system, ale z railsami pod maską. Aplikacja chodzi o jakieś 30% szybciej - łatwo można zauważyć, że poprzedni cms był naprawdę kiepsko napisany. Do tego nie dopisaliśmy jeszcze cache’owania, a na pewno to zrobimy - zmiany na stronie są wprowadzane dość rzadko.
Nie muszę chyba wspominać, że napisanie tego systemu od początku zajęło nam dużo mniej czasu niż modyfikacja poprzedniego. Trochę boli fakt, że wyszedł z tego taki sam system. Nie wykorzystaliśmy więc mocy jaka drzemie w railsach. Jeżeli chodzi funkcjonalność i ciekawe rozwiązania wyszedł z tego marny przeciętniak.
Podsumowując!
Podczas pracy nad tym projektem popełniliśmy chyba wszystkie błędy, które dało się popełnić (zgodnie z prawem Murphy’iego ;-) ). Czasu na tym straciliśmy tyle, że pracowaliśmy za przysłowiową miskę ryżu, a momenty zwątpienia lub chęci odwiedzenia pracowników owej instytucji z kałasznikowem w ręku były bardzo częste. Można spytać, czy…
…warto było?
Myślę, że pomimo wszystko warto było się pomęczyć. Wiele się nauczyłem i czuję, że takie doświadczenia mi się przydadzą. Swoją drogą jest to doskonały przykład uczenia się na błędach ;-) I wiem nad czym muszę jeszcze dużo popracować: komunikacja z klientem.
I to by było na tyle jeżeli o historię pewnej aplikacji chodzi.
Posted in PHP, Programowanie | Tags php, projektowanie, rails, web | no comments
Posted by Piotr Sarnacki
Thu, 07 Dec 2006 22:10:00 GMT
W ramach pierwszego sympozjum CyberGuru na Wojskowej Akademii Technicznej w Warszawie wygłosiłem dzisiaj prezentację Przedstawienie framework’a Ruby on Rails. CeyberGuru skupia studentów, którzy chcą rozwijać swoje zainteresowania informatyczne. Do tej pory grupy wchodzące w skład CyberGuru były w większości mało aktywne, o ile w ogóle miały jakichś członków. Dość niedawno powstała inicjatywa odświeżenia owej organizacji, owocem której jest pierwsze (i mam nadzieję nie ostatnie) sympozjum.
Jeżeli chodzi o przebieg sympozjum to było bardzo przyjemnie. Wygłoszone prezentacje:
- Bezepieczeństwo aplikacji webowych, Krzysztof Wilkos
- Przedstawienie framework’a Ruby on Rails, Piotr Sarnacki
- Java w zastosowaniach wbudowanych, Andrzej Olszak
- Wprowadzenie do C# - Jakub Bańkowski
Wszystko nieco się przedłużyło - nie mamy dużego doświadczenia w orgaznizowaniu takich spotkań, więc prezentacje trwały dłużej niż zakładaliśmy. Uczestnicy sympozjum wypełnili ankiety, dzięki czemu będziemy wiedzieli co zmienić. Z tego co widziałem przydałoby się więcej przykładów, a mniej teorii. Prawdopodobnie będziemy dążyli wtedy do zmniejszenia liczby referatów podczas jednego sympozjum, co automatycznie wydłuży czas trwania pojedyńczego referatu.
Korzystając z okazji, po prezentacji, powiedziałem o swoim pomyśle stworzenia grupy webowej. Chciałbym zebrać ludzi zainteresowanych pisaniem i rozwijaniem aplikacji internetowych i rozpocząć pracę nad (bliżej jeszcze neisprecyzowanym) wspólnym projektem. Jedyne co na razie mogę powiedzieć na pewno - będziemy pisać używając Ruby on Rails. Jeżeli coś z tego wyniknie na pewno o tym napiszę.
Posted in WAT, Ruby on Rails | Tags cyberguru, rails, sympozjum, wat | no comments