Wszystkie artykuły

Zanim powstanie kod

Dlaczego planujemy architekturę i sprawdzamy granice systemu

Aplikacja może działać płynnie podczas prezentacji i zacząć sprawiać problemy, kiedy pojawi się więcej użytkowników. Może również poprawnie wykonywać wszystkie zaplanowane funkcje, ale wymagać infrastruktury zbyt kosztownej dla firmy, która z niej korzysta.

Dlatego projektowanie oprogramowania obejmuje również pytania o warunki jego pracy. Ile osób będzie korzystać z systemu? Co będą robiły? Jak szybko powinny otrzymać odpowiedź? Jakie dane trzeba przechowywać i z jakimi usługami się połączyć?

W Pracowni Kodu chcemy patrzeć na te zagadnienia jako część jednego projektu. Potrzeby biznesowe, architektura, technologia i koszty utrzymania wpływają na siebie.

Od czego zaczynamy

Dobry plan zaczyna się od zrozumienia pracy, którą system ma wykonać.

Weźmy aplikację lojalnościową. Klient sprawdza punkty i odbiera nagrodę. Pracownik rejestruje zakup. Właściciel analizuje wyniki.

Każda z tych czynności ma inne wymagania. Przyznanie nagrody wymaga poprawnego sprawdzenia i zapisania stanu punktów. Raport miesięczny może powstawać w tle. Przypomnienie wysłane klientowi kilka chwil później może być akceptowalne, podczas gdy długie oczekiwanie przy kasie będzie utrudniało obsługę.

Już na tym etapie warto ustalić, które operacje są najważniejsze, jakie opóźnienia można zaakceptować i co powinno się wydarzyć w przypadku błędu.

Po co rysować system

Narysowanie systemu pomaga zobaczyć zależności przed rozpoczęciem programowania.

Jednym z narzędzi porządkujących architekturę jest model C4. Pozwala przedstawiać system na czterech poziomach szczegółowości:

  • Kontekst: kto korzysta z systemu i z jakimi zewnętrznymi usługami się on łączy.
  • Kontenery: główne aplikacje i magazyny danych, na przykład panel internetowy, serwer API i baza danych.
  • Komponenty: podział odpowiedzialności wewnątrz wybranej części systemu.
  • Kod: szczegóły implementacji tam, gdzie ich przedstawienie jest przydatne.

Nie każdy projekt potrzebuje wszystkich czterech poziomów. Oficjalna dokumentacja C4 wskazuje, że dla większości zespołów wystarczające są diagramy kontekstu i kontenerów. Model C4 — rodzaje diagramów.

Na takim schemacie łatwiej omówić, gdzie przechowujemy dane, kto odpowiada za naliczanie punktów i od czego zależy obsługa klienta. Można też uzupełnić go diagramem przebiegu konkretnej operacji, na przykład odebrania nagrody.

Sam rysunek nie potwierdza jeszcze wydajności ani niezawodności. Daje jednak wspólny punkt odniesienia do rozmowy i pomaga wskazać założenia, które trzeba później sprawdzić.

Przykładowy diagram kontenerów C4: klient i pracownik używają aplikacji, która łączy się z API. API zapisuje punkty i nagrody w bazie. Osobny proces tworzy raporty w tle.
Przykład projektowy, nie dokumentacja wdrożenia: aplikacja lojalnościowa na poziomie kontenerów C4. Wybrany podział oddziela obsługę punktów przy kasie od raportów. Przyznanie nagrody musi być atomową operacją, a ponowienie żądania nie może przyznać jej drugi raz. Powiększ diagram.

Jak mierzyć szybkość

Szybkość systemu warto opisać konkretnymi miarami.

Angielskie latency można w tym kontekście opisywać po polsku jako czas odpowiedzi. Trzeba przy tym określić, gdzie go mierzymy: wewnątrz serwera czy z perspektywy klienta, z uwzględnieniem komunikacji przez sieć.

Istotna jest również przepustowość, czyli liczba żądań obsługiwanych w określonym czasie, oraz odsetek błędów. System może szybko odpowiadać komunikatem o niepowodzeniu — taki wynik nie oznacza, że poprawnie obsługuje klientów.

Sama średnia czasu odpowiedzi także może ukrywać problemy. Dlatego obserwuje się między innymi 95. i 99. percentyl, oznaczane jako p95 i p99. Przykładowo p95 równy 500 milisekund oznacza, że około 95% zmierzonych odpowiedzi mieściło się w tym czasie, a pozostałe trwały dłużej. To przykład objaśniający miarę, a nie wynik naszego testu.

Google w materiałach dotyczących niezawodności zaleca obserwowanie czterech podstawowych sygnałów: czasu odpowiedzi, ruchu, błędów i stopnia wykorzystania zasobów. Google SRE — monitorowanie systemów.

Użytkownicy a żądania

Liczba użytkowników nie jest tym samym co liczba żądań.

Tysiąc zarejestrowanych osób może generować niewielki ruch, jeśli większość korzysta z aplikacji sporadycznie. Mniejsza grupa intensywnie pracujących użytkowników może obciążać ją znacznie bardziej.

Rozważmy prosty przykład rachunkowy: 200 aktywnych użytkowników, z których każdy wysyła jedno żądanie co pięć sekund, oznacza średnio 40 żądań na sekundę. Jeśli jedna czynność w aplikacji uruchamia trzy wywołania API, przy tej samej częstotliwości działań otrzymamy już 120 żądań na sekundę.

Do tego dochodzą chwilowe spiętrzenia ruchu. Powiadomienie o promocji może sprawić, że wiele osób otworzy aplikację jednocześnie. Dlatego w testach trzeba odtworzyć sposób korzystania z systemu, uwzględniając zarówno częstotliwość działań, jak i ich przebieg. Grafana k6 — modelowanie obciążenia API.

Node.js, Python i obciążenie

Dobór technologii wymaga zrozumienia rodzaju obciążenia.

Node.js dobrze nadaje się do obsługi wielu operacji związanych z oczekiwaniem na dane, pod warunkiem odpowiedniego zaprojektowania aplikacji. Gdy jedna operacja czeka na odpowiedź sieciową, środowisko może zajmować się innymi zadaniami. Długie obliczenia wykonywane na głównym wątku mogą jednak blokować obsługę pozostałych żądań. Zwraca na to uwagę sama dokumentacja Node.js. Node.js — unikanie blokowania obsługi żądań.

Python również umożliwia programowanie asynchroniczne. Biblioteka asyncio stanowi podstawę narzędzi do współbieżnej obsługi komunikacji sieciowej. W takim rozwiązaniu także trzeba uważać na operacje blokujące, a ciężką pracę odpowiednio przenosić poza pętlę obsługującą pozostałe zadania. Dokumentacja asyncio, wykonywanie kodu blokującego.

Dlatego porównanie „obie aplikacje mają po 20 punktów API” niewiele mówi o ich możliwościach. Jeden punkt może zwracać krótką informację, a drugi przeszukiwać duży zbiór danych i generować raport.

Rzetelne porównanie wymaga podobnego zadania, danych i limitów zasobów, jawnej konfiguracji oraz tych samych kryteriów poprawności i czasu odpowiedzi. Dopiero wtedy można ocenić, która implementacja lepiej odpowiada potrzebom projektu.

Testy obciążeniowe

Założenia dotyczące ruchu trzeba sprawdzić testami.

Test obciążeniowy pozwala obserwować system podczas wykonywania wielu operacji. Powinien obejmować rzeczywiste scenariusze, na przykład logowanie, sprawdzenie salda, zapis transakcji i odebranie nagrody. Oprócz szybkości należy sprawdzać poprawność odpowiedzi i zapisanych danych. Grafana k6 — testowanie API.

Warto sprawdzić kilka sytuacji:

  • zwykły, przewidywany ruch;
  • większe obciążenie w godzinach szczytu;
  • nagły napływ użytkowników;
  • wielogodzinną pracę, która może ujawnić narastające problemy;
  • stopniowe zwiększanie ruchu, aby poznać granice systemu.

Takie scenariusze odpowiadają różnym rodzajom testów opisanym w dokumentacji k6. Rodzaje testów obciążeniowych.

Praktyczną granicą systemu jest już moment, w którym przestaje spełniać ustalone wymagania dotyczące czasu odpowiedzi, poprawności lub liczby błędów. Nie trzeba czekać na całkowite zatrzymanie serwera.

Warto również zaplanować zachowanie podczas przeciążenia: ograniczenie przyjmowanego ruchu, kontrolowane odrzucanie części żądań lub ograniczenie mniej ważnych funkcji. To pomaga chronić kluczowe operacje. Google SRE — obsługa przeciążenia.

Korzyści i ograniczenia

Każde rozwiązanie ma korzyści i koszty.

W projektowaniu systemów często używa się angielskiego określenia trade-off. Po polsku możemy mówić o kompromisie projektowym: wybierając daną korzyść, akceptujemy określone ograniczenie.

DecyzjaMożliwa korzyśćKoszt lub ograniczenie
Przechowywanie części wyników w pamięci podręcznejSzybsze odczyty i mniej pracy dla bazyTrzeba zadbać o aktualność danych
Przeniesienie raportów do zadań w tleKrótsze oczekiwanie na przyjęcie zleceniaGotowy raport pojawi się później
Podział aplikacji na osobne usługiMożliwość niezależnego rozwijania i skalowania części systemuWięcej połączeń, wdrożeń i elementów do utrzymania
Zwiększenie zasobów serweraWiększy zapas mocy, jeśli to zasoby są ograniczeniemWyższy koszt i brak rozwiązania części problemów w kodzie lub bazie

Dobry projekt wymaga ustalenia, które korzyści są potrzebne konkretnej firmie. Dla niewielkiego systemu prostota utrzymania może być ważniejsza niż możliwość niezależnego skalowania wielu usług. W innym projekcie taka niezależność może uzasadniać dodatkową złożoność.

Plan rozwija się z projektem

Plan powinien rozwijać się razem z wiedzą o systemie.

Na początku określamy potrzeby, rysujemy najważniejsze zależności i zapisujemy założenia. Gdy pojawia się działający fragment aplikacji, sprawdzamy je pomiarami. Wyniki mogą prowadzić do zmiany sposobu przechowywania danych, podziału zadań albo konfiguracji infrastruktury.

Warto zachować krótkie uzasadnienia ważnych decyzji: dlaczego wybraliśmy dane rozwiązanie, jakie alternatywy rozważaliśmy i w jakich warunkach będziemy musieli wrócić do tematu.

W Pracowni Kodu chcemy projektować oprogramowanie z uwzględnieniem sposobu działania firmy, jej budżetu i planów rozwoju. Schemat pomaga zrozumieć system, pomiary pozwalają sprawdzić założenia, a świadome decyzje ułatwiają jego późniejsze utrzymanie.

Przykład takiego podziału opisaliśmy w artykule o architekturze ReasonReach. Jeśli planujesz własną aplikację, zobacz, jak podchodzimy do tworzenia systemów dedykowanych, albo uporządkuj zakres w planerze projektu.

Wszystkie artykułyWróć na górę

Masz pomysł na system?

Zacznijmy od potrzeb firmy, najważniejszych operacji i warunków, w których aplikacja będzie pracować.

Porozmawiajmy o projekcie
PRACOWNIA KODU

Twoja prywatność

Ty decydujesz, czy możemy mierzyć korzystanie ze strony. Zmiana zgody jest dostępna w stopce pod przyciskiem „Ustawienia prywatności”.

Niezbędne

Zapamiętanie wyboru prywatności oraz działanie formularza, planera projektu oraz czatu.

Zawsze aktywne
Jakie dane zapisujemy i na jak długo?

Administratorem danych jest Pracownia Kodu. Kontakt w sprawach prywatności: kontakt@pracowniakodu.com.

Analityka działa wyłącznie na podstawie Twojej zgody. Zapisujemy nazwę podstrony (bez parametrów adresu), rodzaj działania, czas, ogólną kategorię źródła wejścia i urządzenia oraz losowy identyfikator sesji. Sesja w tej karcie wygasa po 30 minutach bezczynności. Identyfikator nie zawiera Twojego imienia, e-maila ani telefonu.

Nie nagrywamy ekranu ani wpisywanych treści. W bazie analityki nie zapisujemy adresu IP, pełnego adresu odsyłającego ani treści formularzy. Dane analityczne przechowujemy na własnych serwerach w systemie strony i Matomo do 90 dni; dostęp ma administrator panelu.

Po zaakceptowaniu aktualnych ustawień możemy też odbierać ogólny rodzaj błędu i przybliżony czas ładowania strony. Do raportów nie przesyłamy treści błędów, adresów z parametrami ani identyfikatora odwiedzającego. Przechowujemy błędy do 30 dni, a pomiary czasów do 14 dni na własnym serwerze. Wycofanie zgody zatrzymuje wysyłkę; już przekazane raporty nie są powiązane z Twoją zgodą ani osobą.

Wybór prywatności i potwierdzenie zgody przechowujemy do 180 dni. W przeglądarce korzystamy z localStorage (wybór) i sessionStorage (sesja analityczna po zgodzie). Odmowa wyłącza pomiar. Po wycofaniu zgody blokujemy dalszy pomiar i zlecamy usunięcie powiązanych zdarzeń z aktywnych baz strony i Matomo. Jeśli nie ma połączenia, zlecenie ponowimy po jego odzyskaniu.

Możesz wycofać zgodę, zażądać dostępu do danych, ich usunięcia, sprostowania, ograniczenia przetwarzania lub przeniesienia, gdy mają zastosowanie odpowiednie przesłanki. Masz prawo złożyć skargę do Prezesa UODO. Wycofanie zgody nie wpływa na zgodność wcześniejszego przetwarzania z prawem.

Token czatu i szkic zapisujemy w pamięci tej witryny, aby umożliwić powrót do rozmowy i bezpieczne ponowienie wiadomości. Bez podania e-maila odpowiedź odczytasz w tej samej przeglądarce. Treść rozmowy służy obsłudze zapytania i nie trafia do analityki.

Formularz i planer działają także bez analityki. Zakres planera zapamiętujemy w tej karcie przeglądarki przez maksymalnie 24 godziny od ostatniej zmiany; dane kontaktowe nie są tam zapisywane. Dane przekazane w zapytaniu służą jego obsłudze i są przechowywane oddzielnie od statystyk.