Zastanawiałeś się, czy optymalizacja pracy serwerów ma sens w aplikacjach internetowych? Wydawało mi się że tak, ale źródła bibliograficzne mówiły inaczej. Postanowiłem więc to zweryfikować.

Szybkość ładowania aplikacji internetowej to jeden z najważniejszych czynników generowania ruchu na stronie. Według danych opublikowanych w 2013 roku przez Stevena Souders’a, specjalistę Google do spraw wydajności aplikacji, opóźnienie odpowiedzi serwerów o 400 ms zaowocowało aż 9% spadkiem całkowitego ruchu na stronie w przypadku yahoo.com. Poniżej zamieściłem video z wspomnianej konferencji.

W przypadku dużych aplikacji internetowych, 9% wejść na stronę przekłada się na miliony wyświetleń dziennie, czego przykładem jest firma cookpad.com, która chwali się osiągnięciem ponad 15 000 zapytań na sekundę w samej Japonii. 9% dla cookpad.com to prawie 5 milionów wyświetleń na godzinę, dlatego biorąc pod uwagę, jak ważna jest szybkość ładowania witryn internetowych dla dużych koncernów, zaskakujący jest fakt, że opracowania tego zagadnienia nie są kompletne.

Optymalizacja witryn internetowych to nie tylko frontend.

Pionierem w omawianiu wielu zagadnień przyśpieszania aplikacji przeglądarkowych jest Steven Souders który jako pierwszy pokazał jak ważna jest wydajność witryn. Niestety w swoich badaniach i książkach skupił się niemal wyłącznie na części frontend aplikacji.

Zogniskowanie jego pracy wokół frontendu jest spowodowane faktem, że:

  • 70-90% czasu ładowania witryny zajmuje frontend, przez co skupiając się na nim, można niższym nakładem pracy osiągnąć znacznie lepsze wyniki.
  • Jeżeli dla właściciela aplikacji ważne jest zwiększenie satysfakcji użytkowników z korzystania z witryny, należy przyśpieszyć całkowity czas ładowania strony tak, by jak najbardziej był odczuwalny dla internautów.

Steve Souders przetarł szlaki, jednak omawia tylko połowę zagadnienia, pomijając zupełnie zagadnienie szybkości pracy serwera. Według niego częścią przyśpieszania pracy serwerów nie ma sensu się zajmować, bo zmiany są kosztowne, a efekty znacznie mniejsze. Ruch w Internecie przyrasta jednak wykładniczo od momentu jego powstania – a co z tym idzie, serwery stają się bardziej obciążone – i by zapewnić tę samą szybkość przesyłania danych do przeglądarki, przyśpieszenie odpowiedzi serwera o każde 10ms staje się znaczące.

internet users count

Pojedynczy użytkownik nie jest w stanie dostrzec przyśpieszenia serwera o 1ms, ale serwer już tak. Serwer, dysponując określoną ilością zasobu i poświęcając na obsługę jednego użytkownika 1ms mniej, może obsłużyć większą ilość zapytań, a co z tym idzie aplikacja może obsłużyć większy ruch na stronie.

Kolejnym powodem dla którego warto zainteresować się wydajnością pracy serwerów jest redukcja kosztów prowadzenia działalności. Spadek wydajności serwera oznacza, że każde zapytanie jest obsługiwane przez większą ilość czasu. W przypadku dużego ruchu jeden serwer nie jest w stanie obsłużyć określonej ilości zapytań, uruchamianych jest więc kilka by odciążyć poszczególne maszyny. Dla takich aplikacji jak http://cookpad.com, którą obsługuje 300 serwerów [1] takie optymalizacje mają znaczenie. Usuwając błędy w kodzie powodujące spadek wydajności można by zredukować ilość potrzebnych serwerów do obsługi a przez to zredukować koszty utrzymywania aplikacji.

Eksperci mówią co innego, więc postanowiłem to zbadać.

Celem tej serii artykułów jest zwiększyć efektywność oraz zmniejszyć koszty prowadzenia dużych aplikacji internetowych poprzez zbadanie, jak bardzo można przyśpieszyć aplikację, skupiając się na części backend aplikacji.

Przed rozpoczęciem projektu, bazując na moich doświadczeniach założyłem, że szybkość, z jaką serwery aplikacji przetwarzają zapytania jest bardzo istotna, jednak źródła bibliograficzne na to nie wskazują.

Opracowania w zakresie wydajności aplikacji internetowych skupiają się jednak wokół badań i wniosków sformułowanych w okolicach 2006r., dlatego postanowiłem zweryfikować ich poprawność i aktualność.

W tym celu napisałem 4 aplikacje internetowe, po jednej do każdego badanego zagadnienia, symulujące opisywany w odpowiadającym im artykule problem. Napisałem także skrypt do mierzenia czasu odpowiedzi tych aplikacji.

Aplikacje uruchomiłem w środowisku produkcyjnym na hostingu Heroku, który jest popularnym hostingiem wśród komercyjnych projektów. Następnie zbadałem czas odpowiedzi serwera dla każdego problemu i porównałem go z czasem odpowiedzi tej samej aplikacji po usunięciu omawianego błędu.

Każdy test wykonałem 1000 razy, zaś dane uzyskane z badań przedstawiłem na wykresach w postaci histogramów w celu porównania wyników. Każdy problem szczegółowo opisałem w przeznaczonym dla niego artykule, dołączając opis implementacji jego rozwiązania w technologii Ruby on Rails.

Sposoby zwiększania wydajności aplikacji internetowych

W artykułach poruszyłem temat przyśpieszania backendu aplikacji. Zbadałem sposoby jej przyśpieszania oraz zbadałem wpływ proponowanych rozwiązań na czas odpowiedzi serwera.

Zbadałem różnice w wydajności dla poniższych zaganień:

  1. Redukcja niepotrzebnych zapytań do bazy danych
  2. Wykorzystanie pamięci podręcznej serwera. Opisano trzy sposoby jej wykorzystania w technologii Ruby on Rails:
    1. cache całych stron
    2. cache akcji kontrolerów
    3. fragmentów widoków
  3. Oddelegowanie zadań do procesów działających w tle
  4. Wykorzystanie technologii AJAX w celu zmniejszenia rozmiaru porcji danych przesyłanych przez sieć.

Założenia i ograniczenia

Ze względu na brak dostępu do rzeczywistych danych już istniejących aplikacji napisałem cztery aplikacje generujące pojedynczy problem. Dodatkowo maksymalnie uprościłem przesyłaną przez sieć odpowiedź, aby uniknąć nadmiernych zniekształceń otrzymanych wyników.

Ze względu na brak fizycznej możliwości przeciążenia rzeczywistej aplikacji działającej w środowisku produkcyjnym, przeprowadziłem testy czasu odpowiedzi nie obciążające aplikacji. Oznacza to, że zapytania były mierzone jeden po drugim w odstępach dwóch sekund, by uniknąć zablokowania przez serwer nadmiernej ilości zapytań czy zablokowania łącza.

W projekcie założyłem, że dla aplikacji rzeczywistych i rozbudowanych, które podczas obsługi pojedynczego zapytania mogą generować kilka z omawianych problemów, różnice w wynikach będą większe. Ponadto założyłem, że długi ogon w otrzymanych wynikach jest spowodowany niestabilnością na łączu internetowym oraz faktem gubienia przesyłanych pakietów podczas przesyłania przez sieć. Zgubione pakiety muszą być wysłane ponownie, co zajmuje dodatkowy czas.

Opis rozwiązania

W celu wykonania badań czasu odpowiedzi serwera napisałem cztery małe aplikacje obrazujące badany problem i jego rozwiązanie.

Testując jaki wpływ mają omawiane zagadnienia na czas odpowiedzi serwera, generowane zapytania są interpretowane w formacie JSON, jeżeli aplikacja nie wymaga generowania widoku HTML. Dzięki temu zredukowałem błąd powstały przy czasie potrzebnym przez serwer by przetworzyć widoki oraz uniknąłem utraty czasu na przesyłanie kompletnego dokumentu HTML przez sieć. W przypadkach, w których przesyłałem kompletny dokument HTML, nie dołączyłem do niego żadnych stylów skryptów ani innych zewnętrznych zasobów, by jak najbardziej zredukować błąd pomiarów.

Wykorzystywane narzędzia i technologie

  • RUBY 2.2.2 – Język programowania wykorzystany do napisania skryptu do mierzenia czasu odpowiedzi serwera oraz do implementacji aplikacji testowych.
  • Ruby on Rails 4.2.4 – Framework do szybkiego tworzenia aplikacji sieciowych w języku Ruby, jeden z najpopularniejszych obecnie wśród startup’ów.
  • PostgreSql 9.3.5 – system do zarządzania relacyjnymi bazami danych.
  • NET::HTTP – wykonywanie zapytań do serwerów hostujących badane aplikacje.
  • BENCHMARK – mierzenie czasu odpowiedzi serwerów.
  • CSV – eksport danych do pliku CSV w celu późniejszego generowania wykresów.

Poniżej przedstawiono listing kodu programu mierzącego czas odpowiedzi serwera.

ruby: time-measure.rb

Powyższy skrypt wykonuje zapytanie pod wskazany adres url określoną ilość razy, a następnie mierzy czas każdego z wykonanych zapytań oraz zapisuje wyniki do pliku CSV. Dla każdego z badanych rozwiązań wykonano 1000 pomiarów, a otrzymane dane przedstawiono za pomocą histogramu, prezentując liczbę wyników w danym zakresie czasu.

Sens przyśpieszania backend aplikacji.

Głównym problemem przy pracy nad wydajnością aplikacji internetowych jest opisanie, kiedy warto skupić się na zwiększaniu wydajności pracy serwera, a kiedy jest to nieopłacalne.

yahoo waterfall view

Patrząc na wykres czasu ładowania poszczególnych elementów witryny http://yahoo.com widać, że podczas pierwszego ładowania witryny, które trwa 3.5s, pobranie pierwszego bajtu danych dokumentu HTML zajmuje około 15% całkowitego czasu ładowania witryny – czyli tyle trwa kompletna odpowiedź serwera.

W związku z tym, przyśpieszając backend o 50%, całość ładowania witryny przyśpieszyłaby się tylko o 7.5%, dlatego zanim postanowimy się skupić na przyśpieszaniu pracy serwera, komunikacji z bazą danych oraz szukania sposobów na szybsze wykonanie pętli, należy sprawdzić, czy nie lepiej zacząć od przyśpieszania części frontend aplikacji. W tym celu należy odpowiedzieć na pytanie, czy głównym powodem zainteresowania się wydajnością aplikacji internetowych jest ograniczenie kosztów związanych ze zbyt dużym ruchem i potrzeba zwolnienia zasobów serwerów, czy może generowanie większego ruchu niż obecny. W drugim przypadku należy skupić się na części frontend aplikacji.

Pracując nad częścią frontend zwiększamy zadowolenie użytkowników podczas pracy z naszą witryną, niemniej w żaden sposób nie odciążamy serwera. Odciążamy wtedy przeglądarki i komputery poszczególnych użytkowników, dlatego jeśli serwer nie radzi sobie z obsługą ruchu na stronie, należy skupić się na jego odciążeniu, czyli przyśpieszaniu części backend aplikacji.

W innych przypadkach prawdopodobnie jest to nieopłacalne.

W przypadku wspomnianego wyżej yahoo.com przyśpieszenie ładowania części frontend, zaowocowałoby skróceniem całkowitego czasu ładowania strony o 42.5%, co dla użytkowników witryny jest znacznie bardziej odczuwalne.

Potrzeba przyśpieszania części backend w startup’ach

Ostatnimi laty coraz bardziej popularny staje się model tworzenia aplikacji przeglądarkowych, który nie polega na przygotowaniu kompletnego planu projektu, wykonaniu go i dostarczeniu użytkownikowi, lecz raczej na programowaniu przyrostowym. Projekty te, popularnie nazywane są startup’ami i łączy je fakt, że startując z relatywnie małym kapitałem, mają za cel jak najszybsze dostarczenie działającej, ale okrojonej wersji aplikacji dla użytkowników, następnie zebranie opinii i danych o dalszych potrzebach, po czym poprawienie błędów, dodanie kolejnych funkcji i dostarczenie kolejnych wersji aplikacji.

Problemów, które pojawiają się w takim modelu jest kilka – jednym z nich jest fakt, że po kilku latach tworzenia projektu, wersja końcowa jest zazwyczaj zupełnie odmienna od bazowej. Za przykład przedstawiam aplikację facebook.com, oraz jak zmieniła się jej część wizualna na przełomie lat.

facebook original profile 2004

facebook profile 2015

Na zdjęciach powyżej nie widać zmian, jakie zaszły w strukturze bazy danych, relacjach między obiektami, metodami i klasami, niemniej zmiany takie zachodzą znacznie częściej, niż te w widokach, z jednego powodu: nie są zauważane przez użytkowników, przez co internauci mają czas ,,oswoić się” ze stroną.

Ze względu na bardzo dynamiczny rozwój, ciągłe zmiany w istniejącym kodzie oraz małe finansowanie w początkowych fazach projektu dokumentacja projektowa w wielu startup-ach po prostu nie istnieje, a kod jest bardzo trudny do zarządzania. Programiści w zespołach przychodzą i odchodzą, czasem zatrudniani są tymczasowo, pisząc kod bez dokumentacji czy podręcznych instrukcji na temat stosowanych praktyk. Duży nacisk na dostarczenie działającej funkcji jak najszybciej często wyklucza napisanie danego rozwiązania w najlepszy możliwy sposób i dostarczane jest rozwiązanie tymczasowe, w celu późniejszego poprawienia kodu.

Niezależnie od tego, jak dobrzy są programiści, problemu z bałaganem w kodzie startup’ów nie da się uniknąć – część funkcji zostaje po prostu usunięta z wywołań, ale ich kod jest zostawiony, bo zespół testujący potrzeby użytkowników nie jest pewien czy nie będzie potrzeby go przywrócić. Powtórzenia w kodzie się mnożą, bo szybciej jest napisać fragment kodu od zera, niż zmieniać istniejące moduły.

To wszystko prowadzi do momentu, gdy w kodzie aplikacji serwerowej jest tak wiele punktów powodujących spadek wydajności, że przeznaczane są duże pieniądze na to, by przyśpieszyć działania serwerów. Praca z obszernym kodem jest trudna i czasochłonna, a skutkiem czego mało opłacalna, patrząc krótkoterminowo, z poziomu właściciela projektu.

Podsumowanie

Wyniki moich badań jasno pokazują, że skupienie się na przyśpieszeniu pracy serwerów ma sens dla witryn, które:

  • Wykonują skomplikowane obliczenia po stronie serwera (przetwarzanie obrazów, filmów, albo dużej ilości danych, np. przeliczanie danych statystycznych, tworzenie wykresów itp.)
  • Generują duży ruch i ilość zapytań (serwery są bardziej obciążone, przez co dostarczenie danych jest wolniejsze)
  • Mają już bardzo dobrze zarządzaną część odpowiedzialną za prezentację aplikacji.
  • Dostarczają zewnętrzne API oraz komunikujących się z przeglądarką za pomocą zapytań i odpowiedzi w formacie JSON, a nie przesyłających kompletne dokumenty HTML.
  • Generują zbyt duże koszty utrzymania lub serwery są przeciążone.
  • Na przestrzeni lat wielokrotna zmiana głównych założeń projektu spowodowała wystąpienie w wielu miejscach niewydajnego kodu, generującego niepotrzebne zapytania do bazy danych albo niepotrzebne obroty pętli.

Co Ty myślisz na temat przyśpieszania aplikacji sieciowych? Być może pominąłem jakieś ważne zagadnienie? Napisz o tym w komentarzach.

Źródła:

  1. [1] Akira Matsuda: RUBY On Ales 2015 – The Recipe for the Worlds Largest Rails Monolith, Konferencja.
  2. [2] RailsConf 2014Improve Performance Quick and Cheap: Optimize Memory, Konferencja.
  3. [3] Stive Souders:Airbnb Tech TalkDive into performance, Konferencja.
  4. [4] Steve Souders: Google I/O 2012High Performance HTML5, Konferencja
Sekrety Produktywności

Sekrety Produktywności

DARMOWE szkolenie mailingowe! 10 porad jak zwiększyć produktywność w programowaniu.

Dziękujemy za zapisanie się na kurs. Wkrótce otrzymasz wiadomość email z potwierdzeniem zapisu.

Polub nas na facebooku!

Jeśli podoba Ci się ten blog, polub nas na facebooku! Dzięki temu nie ominą Cię nowe treści!

You have Successfully Subscribed!