Kaliop Poland
Testy obciążeniowe

Testy obciążeniowe

Czyli jak weryfikujemy stabilne działanie aplikacji

Jednym z częstych problemów aplikacji i serwisów internetowych jest ich niska wydajność przy zwiększonym ruchu. Objawem jest spowolnione działanie serwisu lub nawet jego niedostępność. To oczywiście może powodować straty finansowe oraz godzić w wizerunek marki, jaka stoi za serwisem internetowym.

Jeżeli podczas wdrożenia nie zaplanujemy testów obciążeniowych, nie będziemy w stanie powiedzieć jaki ruch jest w stanie przyjąć nasz serwis. W Kaliop Poland, dla każdego wdrożenia dbamy o to, aby mieć pewność, że serwisy internetowe jakie upubliczniamy w 100% obsłużą zakładany ruch. Wiedzę tę, zapewniają nam testy obciążeniowe / wydajnościowe.

Plan testu

Testy wydajnościowe należą do grupy testów pozafunkcjonalnych i przeprowadzane są według ustalonych scenariuszy użyć serwisu internetowego. Pierwszym krokiem jest przygotowanie planu testu, w którym należy ustalić:

  1. Scenariusz użycia (ścieżkę poruszania się po serwisie, procedurę wykonywania określonych czynności).
  2. Liczbę użytkowników, podążających wg. zaplanowanego scenariusza użycia.
  3. Czas w jakim zadana liczba użytkowników wykonuje scenariusz.

Po przygotowaniu planu testu, uruchamiamy go. Monitorujemy zachowanie aplikacji – obserwujemy m.in. czasy odpowiedzi serwera, czas ładowania strony, poprawność jej działania, ale i stabilność działania serwera, konsumpcji jego zasobów przez aplikację. W jaki sposób to robimy, pokażemy na realnym przykładzie jednego z naszych wdrożeń.

W naszym przypadku, mamy do czynienia z aplikacją internetową działającą w przeglądarce internetowej. Ruch i przesyłanie danych odbywa się przy użyciu protokołu HTTP, a serwerem aplikacji jest NGINX.

Jednym ze scenariuszy użyć jest logowanie się użytkowników posiadających swoje konta w serwisie i przejście do ich profilu w celu przeczytania wiadomości. Wiemy, że w ciągu dnia loguje się do serwisu około 3000 użytkowników. Przyjmując, że znaczna część użytkowników wykonuje te czynności w godzinach 9-17, mamy przedział 8h na przyjęcie takiego ruchu. Znając aplikację wiemy, że największy ruch jest pomiędzy godz. 9 a 11 – wtedy loguje się około 50% wszystkich użytkowników. Zatem na 1 minutę przypada około 12 unikalnych użytkowników. Do testów przyjmujemy liczbę 24 użytkowników, aby mieć pewność, jak zachowa się aplikacja gdy ruch wzrośnie o 100%.

Przygotowanie testu przy użyciu aplikacji JMeter

Testy obciążeniowe wykonujemy przy użyciu darmowej aplikacji JMeter (http://jmeter.apache.org/). Jest to aplikacja napisana w  języku Java, więc działa w każdym systemie operacyjnym, który wspiera tę technologię. JMeter komunikuje się z testowanym serwisem, obciążając go w sposób analogiczny do tego, jak robią to przeglądarki internetowe. Jednak nie działa jak przeglądarka – nie wspiera wszystkich akcji obsługiwanych przez przeglądarkę, np. Java Script’u. Działa na zasadzie wysyłanych Request’ów i sprawdzania Response’ów zwracanych przez serwer.

Znając realny plan użycia aplikacji musimy odwzorować ten sam plan w JMeter. Przygotowanie całego Planu sprowadza się do wykonania kilku kroków:

Krok 1 - Tworzenie planu testu

W najprostszej postaci węzeł planu testu (Test Plan) składa się ze swojej nazwy (Name) oraz opcjonalnie ze zmiennych (User Defined Variables), które później wykorzystamy. Deklarujemy dwie zmienne (ich nazwy zależą wyłącznie od nas):

  • site – adres URL / IP pod którym dostępny będzie testowany system
  • port – numer portu, pod którym dostępny będzie testowany system

Test obciążeniowy - plan testu

Krok 2 – Definicja puli użytkowników wykonujących test

Grupa użytkowników / wątków (Thread Group) pozwala na zadeklarowanie liczby użytkowników (Number of Threads), w zadanym okresie czasu (Ramp-Up Period), jaka będzie tworzyć zapytania do testowanego systemu. Wskazane jest, aby w początkowej fazie projektowania testów, ustawić obydwie wartości na wartość „1”. Dopiero gdy mamy pewność, że cały test przechodzi bezbłędnie zmienimy te wartości na docelowe.

Test obciążeniowy - pula użytkowników

Krok 3 – Dodanie elementów konfiguracyjnych

Dla wskazanej grupy użytkowników dodajemy elementy konfiguracyjne wspólne dla docelowych zapytań http. Są to:

HTTP Request Defaults – domyślna konfiguracja dla wysyłanych zapytań. Używamy w niej zdefiniowanych w planie testów zmiennych site oraz port, aby skonfigurować hosta (Server Name or IP) i port (Port number).

HTTP Header Manager – pozwala na dodawanie lub nadpisywanie wysyłanych nagłówków HTTP.  Dzięki temu JMeter może się przedstawiać jako konkretna przeglądarka i system operacyjny. Możemy zadeklarować np.:

  • User-Agent (przykład dla symulacji systemu Windows 10 i przeglądarki Microsoft Edge wersja Desktop): Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0
  • Accept-Encoding: gzip, deflate
  • Accept-Language: pl-PL,pl;q=0.8,en-US;q=0.6,en;q=0.4

HTTP Cache Manager – pozwala na zasymulowanie cache przeglądarki (o ile zaprojektowana aplikacja ustawia nagłówki Last-Modified oraz Etag. Jeżeli chcemy, aby dla każdego wirtualnego użytkownika budowany był nowy cache, należy zaznaczyć opcję Clear cache each iteration.

HTTP Cookie Manager – pozwala na zapisywanie i wysyłanie cookies, w sposób jaki robi to przeglądarka internetowa. Każdy wątek (thread) zapisuje swoje własne cookie, którego wartość można później obserwować w Listenerze View Results Tree.

Krok 4 – Obsługa docelowych żądań

Po podstawowej konfiguracji, której zadaniem jest symulowanie działania przeglądarki, pozostaje przygotowanie zapytań do aplikacji wg naszego scenariusza użycia.

Odwiedzenie strony logowania – jest pierwszym krokiem jaki wykonałby prawdziwy użytkownik. Dlatego też w teście wysyłamy metodą GET Request HTTP do strony logowania. Ze strony wyrażeniem regularnym pobieramy wygenerowany przez system token CSRF, bez którego nie byłoby możliwe zalogowanie się.

W tym celu dodajemy Regular Expression Extractor, gdzie definiujemy zmienną value, do której JMeter zapisze wartość tokena CSFR. Pobranie wartości tokena realizujemy poprzez użycie wyrażenia regularnego, które w naszym przypadku z tagu HTML Input typu hidden pobiera to, co jest w value tego pola.

Test obciążeniowy - wyrażenie regularne

Logowanie się do strony – czyli wypełnienie pól formularza i wysłanie go na akcję logowania, realizowane jest również jako HTTP Request. Tym razem wysłanie danych jest metodą POST, a jako parametry przesyłane są identyczne parametry, które wysyłane są podczas prawdziwego logowania.

Aby się dowiedzieć jakie parametry / zmienne powinny zostać wysłane w celu zalogowania się, najłatwiej jest się posłużyć narzędziem developerskim przeglądarki, gdzie można podsłuchać komplet zmiennych, jakie są wysyłane również metodą POST. Wcześniej pobrany token CSRF przekazujemy jako zadeklarowaną już zmienną value.

Odwiedzenie strony z wiadomościami dla użytkownika oraz wylogowanie to kolejne dwa HTTP Request, które metodą GET wysyłają zapytania pod wskazane w Path adresy URL.

Krok 5 – Obserwacja wyników – zbieranie danych

Przed uruchomieniem testów, aby móc obserwować wyniki należy dodać tzw. Listenery. Na etapie przygotowywania testu najlepiej dodać je do każdego z HTTP Request’u, aby móc podejrzeć Response jaki przychodzi z serwera. Zatem jako elementy podrzędne do każdego HTTP Request dodajemy Listener View Results Tree.

Aby móc obserwować wynik globalny dla całego testu, a nie tylko pojedynczych kroków, dla całej Thread Group dodajemy również Listenery. W zależności od oczekiwań względem prezentacji wyników, możemy wybrać takie, które są dla nas najbardziej czytelne. W naszym przypadku to Graph Results, Summary Report oraz View Results in Table.

Krok 6 – Uruchomienie testu i wyniki

Pierwszy test należy wykonać, wysyłając do serwera tylko jeden wątek zaplanowanego scenariusza. Po uruchomieniu należy sprawdzić, czy każdy z dodanych Listener’ów zwraca poprawne nagłówki i nie ma błędów. Dopiero po tym kroku powinniśmy ustawić docelowe wartości dla Thread Group – tj. 24 użytkowników w ciągu 60 sekund.

Na ekranach wyników może być bardzo widocznie pokazane, w jaki sposób zwiększenie ruchu w serwisie internetowym wpływa na wydajność i stabilność działania tego serwisu. Średnie czasy ładowania strony mogą znacząco wzrosnąć i mogą pojawić się błędy. W naszym przypadku, założona liczba użytkowników nie wpływa znacząco na szybkość działania aplikacji – niezmiennie średni czas ładowania stron jest liczony w milisekundach. Wielokrotnie zdarzyło się jednak, że czasy bardzo wzrastały. Należy wówczas znaleźć krok, który wpływa na zwiększony czas i analizować jego działanie bardziej niskopoziomowymi narzędziami.

Poza obserwacją wyników w JMeter należy obserwować parametry samego serwera: Load, CPU oraz RAP i Swap oraz procesy, które te zasoby konsumują.

Warto również sprawdzić, jaki maksymalny ruch wytrzyma nasza aplikacja i serwer. W naszym przypadku widać, że dla 24 użytkowników na sekundę aplikacja działa zbyt wolno. Czasy reakcji i odpowiedzi są zbyt długie.

Na sam uzyskiwany wynik może mieć wpływ wiele elementów – jakość / sposób napisanej aplikacji, konfiguracja serwera ale i optymalizacja samej warstwy prezentacji ma często wpływ na szybkość działania.

Wyniki dla jednego użytkownika:

Test obciążeniowy - wynik dla 1 użytkownika

Wynik dla docelowej liczby użytkowników:

Test obciążeniowy - wynik dla 24 użytkowników

Wynik dla 24 użytkowników na sekundę:

Test obciążeniowy - badanie max

Podsumowanie

Opisany przypadek testu pokazuje początkowy stan testu obciążeniowego. Docelowo test powinien uwzględniać logowanie się użytkowników np. z różnymi loginami i ich hasłami, czas „pauzy”, w którym użytkownik np. czyta to co ma na stronie. Jest to zatem scenariusz znacznie bliższy realnemu użyciu aplikacji. Warto również uwzględniać dla swoich kroków tzw. Assertions, które pozwalają na weryfikację, czy po załadowaniu strony, ta posiada wymagane elementy -  np. tekst powitalny użytkownika, jego login itp.

Autor:
Radosław Kuchta, Kierownik Działu wsparcia i Zapewnienia jakości, Kaliop Poland

Radosław Kuchta