<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="/stylesheets/rss.css"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/">
  <channel>
    <title>Drogomir: Optymalizacja Rails&#243;w</title>
    <link>http://blog.drogomir.com/articles/2006/12/16/optymalizacja-rails%C3%B3w</link>
    <language>en-us</language>
    <ttl>40</ttl>
    <description></description>
    <item>
      <title>Optymalizacja Rails&#243;w</title>
      <description>&lt;p&gt;Dzisiaj chcialem napisa&#263; o swoich bojach z liczb&#261; request&#243;w na sekund&#281;. &lt;/p&gt;

&lt;p&gt;Po przepisaniu &lt;a href="http://blog.drogomir.com/articles/2006/12/15/historia-pewnej-aplikacji"&gt;pewnej aplikacji&lt;/a&gt; okaza&#322;o si&#281;, &#380;e jej szybko&#347;&#263; nie powala na pod&#322;og&#281;. A w&#322;a&#347;ciwie to powala, ale w z&#322;ym tego wyra&#380;enia znaczeniu.&lt;/p&gt;

&lt;p&gt;Tak w&#322;a&#347;ciwie to spodziewa&#322;em si&#281; czego&#347; takiego podczas pisania.  Od jakiego&#347; czasu pr&#243;buj&#281; si&#281; oduczy&#263; &lt;strong&gt;przedwczesnej optymalizacji&lt;/strong&gt; i pisania zbyt elastycznego kodu, tam gdzie on si&#281; nie przydaje. Wielu pocz&#261;tkuj&#261;cych programist&#243;w pisze kod &lt;em&gt;na wyrost&lt;/em&gt;. Gdy trzeba rozwi&#261;za&#263; problem, pr&#243;buj&#261; napisa&#263; niemal&#380;e bibliotek&#281;, kt&#243;r&#261; mo&#380;na b&#281;dzie wykorzysta&#263; p&#243;&#378;niej w wielu innych przypadkach. Idea fajna. Tylko w praktyce i tak p&#243;&#378;niej u&#380;ywa si&#281; tych w b&#243;lach powsta&#322;ych linijek kodu w jednym miejscu, w kt&#243;rym wystarczy&#322;oby proste rozwi&#261;zanie. O przedwczesnej optymalizacji &lt;a href="http://railsexpress.de/blog/articles/2006/11/10/on-premature-optimization"&gt;kilka s&#322;&#243;w&lt;/a&gt; napisa&#322; na swoim &lt;a href="http://railsexpress.de/blog/"&gt;blogu&lt;/a&gt; Stefan Kaes. Nale&#380;y jej unika&#263;, ale trzeba dobrze wszystko przemy&#347;le&#263;, &#380;eby p&#243;&#378;niej nie sko&#324;czy&#263; z przepisywaniem po&#322;owy aplikacji.&lt;/p&gt;

&lt;p&gt;Wracaj&#261;c do sedna (o ile takowe w og&#243;le istnieje). Przed optymalizacj&#261; poczyta&#322;em troch&#281; gdzie naj&#322;atwiej zyska&#263; upragnione requesty. Od razu mo&#380;na poleci&#263; wspomniany wy&#380;ej &lt;a href="http://railsexpress.de/blog/"&gt;rails express&lt;/a&gt;. Opr&#243;cz tego bloga pan Google m&#243;wi jeszcze o kilku artyku&#322;ach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://poocs.net/2006/3/13/the-adventures-of-scaling-stage-1"&gt;The adventures on scaling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.infoq.com/articles/Rails-Performance"&gt;A Look at Common Performance Problems in Rails&lt;/a&gt; - &#347;wietne podsumowanie napisane przez Stefana Kaes&lt;/li&gt;
&lt;li&gt;&lt;a href="http://glu.ttono.us/articles/2006/06/23/stefen-kaes-optimizing-rails"&gt;Stefen Kaes - Optimizing Rails&lt;/a&gt; - podsumowanie prezentacji wy&#380;ej wspomnianego Stefana ;-)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pierwsza rzecz, o kt&#243;rej nale&#380;y wspomnie&#263;: testy &lt;strong&gt;musz&#261;&lt;/strong&gt; by&#263; wykonywane &lt;strong&gt;przed i po zmianach&lt;/strong&gt;. Czasami pr&#243;by optymalizacji ko&#324;cz&#261; si&#281; na zmniejszeniu szyko&#347;ci. A tego by&#347;my przecie&#380; nie chcieli ;-)&lt;/p&gt;

&lt;p&gt;Liczb&#281; request&#243;w na sekund&#281; naj&#322;atwiej sprawdzi&#263; odpalaj&#261;c aplikacj&#281; w trybie &lt;em&gt;production&lt;/em&gt;. Wy&#347;wietlaj&#261;c plik &lt;code&gt;log/production.log&lt;/code&gt; poleceniem &lt;code&gt;tail -f log/production.log&lt;/code&gt; mamy na bie&#380;&#261;co informacje o czasie wtgenerowania strony podzielonym na czas wykonywania zapyta&#324; do bazy i renderowania strony:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Processing PageController#index (for 127.0.0.1 at 2006-12-11 23:39:32) [GET]
  Session ID: 7d4349aab2db813aababe45624ff9a25
  Parameters: {"action"=&amp;gt;"index", "controller"=&amp;gt;"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]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;W moim wypadku zapytania do bazy danych wykonywa&#322;y si&#281; jakie&#347; 80-90% czasu. W takim wypadku dobrze jest uruchomi&#263; aplikacj&#281; w trybie development i zobaczy&#263; w logach jakie zapytania wykonuj&#261; si&#281; podczas generowania strony. U mnie funkcj&#261; wykonuj&#261;c&#261; najwi&#281;cej zapy&#324; by&#322;o tworzenie menu. Klient za&#380;yczy&#322; sobie rozwijanego menu javascriptowego. Z reguly odradzam takie rozwi&#261;zanie (w wi&#281;kszo&#347;ci wypadk&#243;w jest to niewygodne i niepotrzebne), ale klienci s&#261; uparci, wi&#281;c w trosce o swoje zdrowie psychiczne ust&#281;puj&#281; ;-). W jednej z trzech sekcji ma ono oko&#322;o 120 element&#243;w (a liczba ta pewnie b&#281;dzie si&#281; zwi&#281;ksza&#322;a z biegiem czasu).  &lt;/p&gt;

&lt;p&gt;Struktura u&#380;yta do budowy menu to oczywi&#347;cie drzewo - rekurencyjna funkcja wy&#347;wietla wszystkie elementy. Najprostsza jej wersja, przy ka&#380;dym wywo&#322;aniu wykonuje jedno zapytanie, &#322;atwo mo&#380;na policzy&#263;, &#380;e zayta&#324; by&#322;o kilkadziesi&#261;t (jedno dla ka&#380;dego elementu, kt&#243;ry posiada&#322; dzieci). Przepisa&#322;em funkcj&#281; tak, aby kolekcja by&#322;a pobierana tylko raz, a p&#243;&#378;niej przekazywana jako jeden z argument&#243;w. Bingo! Szybko&#347;&#263; wzros&#322;a oko&#322;o 2 razy. &lt;/p&gt;

&lt;p&gt;Nadal jednak co&#347; by&#322;o nie tak - metoda children, kt&#243;r&#261; zapewnia&#322; plugin acts_as_tree. Ju&#380; chcia&#322;em j&#261; podmieni&#263; na swoj&#261;, kt&#243;ra dostawa&#322;aby kolekcj&#281; w argumencie, kiedy przypomnia&#322;em sobie, &#380;e na &lt;a href="http://forum.rubypnrails.pl"&gt;forum ruby on rails&lt;/a&gt; Pawe&#322; Kondzior (PaK) pisa&#322; o swoim pluginie acts_as_tree_element. Plugin bardzo fajny - generuje tylko jedno zapytanie i zapewnia funkcje, kt&#243;re pracuj&#261; na pobranej kolekcji - dok&#322;adnie to czego szuka&#322;em! Pawe&#322; pomy&#347;la&#322; jeszcze o jednej rzeczy: elementy zapisane jako zmienna instancji s&#261; cache&amp;#8217;owane w trybie produkcyjnym (o ile &lt;code&gt;config.cache_classes == true&lt;/code&gt;). Szybko&#347;&#263; wzros&#322;a o kilkana&#347;cie procent.&lt;/p&gt;

&lt;p&gt;Pojawi&#322; si&#281; jednak problem - scachowana raz kolekcja pozostaje w pami&#281;ci i nie bardzo daje si&#281; wyczy&#347;ci&#263; - nie jest to na r&#281;k&#281;, gdy chcemy pobiera&#263; r&#243;&#380;ne cz&#281;&#347;ci menu. Po kr&#243;tkiej rozmowie na IRC&amp;#8217;u Pawe&#322; lekko zmodyfikowa&#322; plugin - cache jest teraz hashem z zapami&#281;tanymi warunkami w zapytaniu. No i git! ;-)&lt;/p&gt;

&lt;p&gt;Jakie z tego wnioski? Wi&#281;kszo&#347;&#263; aplikacji w do&#347;&#263; du&#380;ym stopniu eksploatuje baz&#281; danych. Zadbajmy o to, &#380;eby zapyta&#324; by&#322;o jak najmniej. Dodatkowo warto u&#380;ywa&#263; opcji include w metodzie find. Warto te&#380; za&#322;o&#380;y&#263; indeksy na kolumnach w bazie. Kt&#243;rych? Najprostsza odpowied&#378; to: na tych, na podstawie kt&#243;rych wyszukujemy, lub sortujemy wyniki. Ale takie proste to nie jest - mo&#380;na popyta&#263; u pana Google&amp;#8217;a, albo w ksi&#281;garni w celu nabycia dodatkowej wiedzy. &lt;/p&gt;

&lt;p&gt;W tym momencie czas wykonywania zapyta&#324; spad&#322; do kilku procent. Pomy&#347;la&#322;em, &#380;e warto jest przyjrze&#263; si&#281; rzeczom, kt&#243;re wykonuj&#261; si&#281; podczas renderowania strony. W artykule Kaes&amp;#8217;a czytamy, &#380;e du&#380;o czasu zajmuje generowanie adres&#243;w za pmoc&#261; link&lt;em&gt;to i url&lt;/em&gt;for. A co mamy w menu? Oko&#322;o 120 takich link&#243;w. Zamieni&#322;em url_for na stringa ze zmieniaj&#261;cym si&#281; numerem id artyku&#322;u. Szybko&#347;&#263; do&#347;&#263; znacznie wzros&#322;a.&lt;/p&gt;

&lt;p&gt;Uruchomi&#322;em testy Apache Bench (ab -n 1000 -c 20). Warto&#347;&#263;, ktor&#261; zobaczy&#322;em bardzo mile mnie zaskoczy&#322;a: 40% szybciej od wersji php. &lt;/p&gt;

&lt;p&gt;Co jeszcze mo&#380;na zoptymalizowa&#263;? Zapewne wiele rzeczy, ale ma&#322;o jest tych naprawd&#281; &#380;r&#261;cych pami&#281;&#263; miejsc. Najlepiej pomy&#347;le&#263; o cache&amp;#8217;owaniu tre&#347;ci - railsy daj&#261; mo&#380;liwo&#347;&#263; bardzo &#322;atwego cache&amp;#8217;owania zar&#243;wno ca&#322;ych stron, jak i pojedy&#324;czych akcji, czy fragment&#243;w strony. Je&#380;eli zmiany s&#261; rzadkie na pewno warto pomy&#347;le&#263; o cache&amp;#8217;owaniu ca&#322;ych stron - przy odczycie takiej strony serwer nawet nie dotyka rails&#243;w. Je&#380;eli aplikacja jest dynamiczna mo&#380;na pomy&#347;le&#263; o cache&amp;#8217;owaniu fragment&#243;w, kt&#243;re rzadko sie zmieniaj&#261;, albo o memcached (w wypadku naprawd&#281; du&#380;ego obci&#261;&#380;enia:  ~3000 req/s).&lt;/p&gt;

&lt;p&gt;&#379;ycz&#281; powodzenia w zdobywaniu kolejnych rps&amp;#8217;&#243;w ;-)&lt;/p&gt;</description>
      <pubDate>Sat, 16 Dec 2006 15:30:00 +0100</pubDate>
      <guid isPermaLink="false">urn:uuid:7f19c48d-5ac8-4211-b447-ab8481da67bb</guid>
      <author>Piotr Sarnacki</author>
      <link>http://blog.drogomir.com/articles/2006/12/16/optymalizacja-rails%C3%B3w</link>
      <category>Ruby on Rails</category>
      <category>rails</category>
      <category>optymalizacja</category>
    </item>
  </channel>
</rss>
