Współtworzę kurs SSJ: Super Senior Java — powiedz nam, czego potrzebujesz.Wypełnij ankietę →
📦 Ten wpis pochodzi z 2025 roku i jest częścią archiwum. Treść może być nieaktualna. ← Wróć do archiwum
architektura

Refaktoring do wzorca "Scatter-Gather" – jak poprawić wydajność i skalowalność systemu

#nauka#rozwój#architektura#refaktoryzacja

Refaktoring – to słowo ma moc! My, jako programiści, po prostu uwielbiamy refaktoryzować. Jesteśmy w stanie zaplanować całe sprinty, byle tylko poprawić kod. Robimy to z przekonaniem, że stanie się on łatwiejszy w utrzymaniu.

Jednak spróbujcie zapytać o to samo biznes! Niestety, często usłyszycie westchnienie i poczujecie negatywne emocje. Przynajmniej w większości przypadków.

Nie da się ukryć: refaktoring jest konieczny! Kluczem jest jednak to, by był zaplanowany, przemyślany i by ostatecznie przyniósł nam zwrot z inwestycji. Pamiętajmy, że sam refaktoring, zgodnie z definicją, nie dostarcza nowych funkcji, czyli sam w sobie nie przynosi organizacji pieniędzy. On wymienia wewnętrzną implementację, aby była łatwiejsza w utrzymaniu. To inwestycja, która przyniesie korzyści w przyszłości, pozwalając na szybsze dostarczanie kolejnych funkcji i dopracowanie naszego modelu do bieżących lub przyszłych wymagań biznesowych.

Czas na wzorzec “Scatter-Gather” – ratunek dla wydajności naszego projektu

W dzisiejszym wpisie nie pokażę całego procesu refaktoryzacji, ale opowiem, co skłoniło nas do zastosowania wzorca „Scatter-Gather”. Oczywiście w kontekście refaktoryzacji.

Omawiany komponent miał jedno zadanie. Obsługiwał wewnętrzny proces, który co pół godziny uruchamiał się i przetwarzał dane dla X kont. Wykonywał to w sposób synchroniczny, jedno po drugim. Proste, ale nieefektywne. Co jednak istotniejsze, było to na dany moment wystarczające. Dopóki wymagania biznesowe nie uległy zmianie.

Biznes doszedł do wniosku, że w przyszłości ten proces będzie musiał być uruchomiany, dla powiedzmy, 10-krotnie większej ilości kont.

Proces był odpalany co 30 minut i trwał prawie cały ten czas. Dlatego…

Refaktoryzacja była niezbędna.

Rozważaliśmy wiele pomysłów, ale wybór padł na „Scatter-Gather” z jednego kluczowego powodu. W naszej architekturze komponent był skalowalny, ale tylko jedna instancja odpowiadała za uruchomienie i wykonanie procesu. Reszta instancji nudziła się, zbierając jedynie dane. Mieliśmy ewidentny problem z nieoptymalnym wykorzystaniem zasobów!

Podczas burzy mózgów dostrzegliśmy, że proces ma dwie fazy:

  • Uruchomienie co 30 minut
  • Obsłużenie całego zadania

Istotne jest, aby proces odpalił się tylko raz na 30 minut. Za to powinna odpowiadać więc tylko jedna instancja. Natomiast co z obsługą biznesowego zadania? Tutaj możemy przecież wykorzystać wszystkie dostępne instancje, aby proces zrównoleglić!

Zamiast obciążać jedną instancję, tak jak to było ówcześnie, zleciliśmy jej tylko uruchomienie procesu, a następnie publikację wiadomości, w postaci komendy, dla każdego konta, które miało wziąć udział w procesie.

Pozostałe instancje zaczęły nasłuchiwać na wiadomość, które sama wyemitowała jedna z nich, by całość procesu zoptymalizować. To jak przeniesienie puli wątków na wyższy poziom! Efekt? Wszystkie instancje pracują, przyspieszając proces. Co więcej, możemy dokładać kolejne instancje, optymalizując czas wykonania.

Pułapki i kompromisy (czyli: za darmo nic nie ma!)

Oczywiście, nie ma nic za darmo. Pojawiły się wady zastosowania tego wzorce, którymi musieliśmy się zająć.

Po pierwsze: Wiedza o końcu procesu. Gdy mieliśmy centralny byt, wiedzieliśmy, kiedy i z jakim skutkiem proces się zakończył – co było kluczowe dla systemu alertowania. Przy dystrybucji na wiele instancji straciliśmy ten centralny punkt. Trzeba było od nowa przemyśleć, jak zweryfikować, czy zadanie zakończyło się dla wszystkich użytkowników. Tutaj pomogło nam wyrzucenie dodatkowego zdarzenia po przetworzeniu każdego z kont. Jeśli ilość wejściowych zdarzeń i wyjściowych się zgadzała, to znaczyło, że proces zakończył się sukcesem. Jasne, pewnie dałoby się to zrobić lepiej, ale w tym przypadku było to wystarczające.

Po drugie: Message Broker. Wprowadziliśmy nowy punkt awarii do architektury (w naszym przypadku było to wewnętrzne rozwiązanie Hermes). Miał on pewne ograniczenie, które nas lekko uderzyło: maksymalnie 10 sekund na obsłużenie dostarczanego zdarzenia. W procesie brał udział zewnętrzny system, który czasem potrafił „zamulić” i nie odpowiedzieć w tym czasie. Dlatego zdarzało się, że zdarzenie było odrzucane i dla danego konta proces się nie wykonał.

Rozwiązanie? Osobny wątek. Był on odpowiedzialny za szybkie przekazanie Message Brokerowi informacji, że zdarzenie jest „w obsłudze” (szybki feedback), a sam proces był uruchamiany dalej. Jednak, aby zmniejszyć ryzyko niepowodzenia, dołożyliśmy też mechanizm retry, oraz po kilku nieudanych próbach – alert.

Mimo wszystko… błędy podczas refaktoringu się zdarzają!

Nawet refaktoring oparty na stabilnym kontrakcie, który dostrzegliśmy w kodzie, niesie ze sobą ryzyko. Założyliśmy, że relacja konto wewnętrzne/zewnętrzne, biorąca udział w procesie, jest jeden do jednego. Wydawało się to oczywiste. Okazało się, że promil kont miał jednak relację jeden do wielu. Spowodowało to lekkie zamieszanie w końcowym rezultacie procesu, na szczęście dotykającą małą sumę pieniędzy i to na korzyść klienta. Impakt biznesowy był więc mały, a poprawka prosta. To dobitnie pokazuje, że nawet przy najlepiej przeprowadzonej refaktoryzacji możemy wprowadzić nieświadomy błąd.

„Scatter-Gather” – nasza wersja

Warto na koniec wyjaśnić, jak nasz przypadek wpisuje się w literaturową definicję wzorca. Scatter-Gather to wzorzec integracyjny składający się z dwóch faz.

Pierwsza — Scatter — polega na rozesłaniu zadań do wielu niezależnych odbiorców.

Druga — Gather — na zebraniu i agregacji wyników od wszystkich uczestników procesu.

W naszym przypadku jedna instancja odpowiadała za fazę Scatter: emitowała komendę dla każdego konta biorącego udział w procesie. Pozostałe instancje pełniły rolę workerów — każda niezależnie obsługiwała swój fragment pracy. Gather zrealizowaliśmy przez zliczanie zdarzeń wyjściowych: jeśli liczba wysłanych komend zgadzała się z liczbą wysłanych potwierdzeń o przetworzeniu, uznawaliśmy proces za zakończony sukcesem.

Podsumowanie i nauczka

Refaktoring jest niezbędny, ale musi być dobrze przemyślany i zaplanowany, by przyniósł realne korzyści.

Trzeba być przygotowanym, że pojawią się błędy i wyzwania – to jest częścią procesu!

Największa nauczka dla mnie z tego procesu? Często uczymy się „egzotycznych” wzorców, aby się rozwijać. Niestety w pewnym momencie dociera do nas, że nigdy ich nie użyjemy, bo po prostu nie ma to sensu biznesowego. Pojawia się wtedy frustracja i zniechęcenie. Jednak prędzej czy później nadejdzie ten jeden moment, kiedy świadomość istnienia takiego rozwiązania pozwala nam dopasować klocek i wywrócić wszystko do góry nogami.

Dlatego warto stale się rozwijać i budować szeroką, ale płytką wiedzę – być świadomym wielu narzędzi. Potem gdy kontekst biznesowy jest jasny, można wykonać „just-in-time learning” – „doktoryzować się” z tego jednego, idealnie dopasowanego narzędzia rozwiązującego palacy nas problem.

A teraz pytanie do Ciebie: Czy miałeś kiedyś okazję zastosować wzorzec „Scatter-Gather” albo inny “egzotyczny” wzorzec w swoim projekcie? Jakie były Twoje doświadczenia i wyzwania? Podziel się swoimi przemyśleniami w komentarzach!

Znajdź mnie na