Asynchroniczna aktywacja kart podarunkowych: kontynuacja historii

Po automatyzacji magazynu wzrosła aktywność na stronie. W szczytowe dni liczba aktywacji sięgała dziesiątek tysięcy dziennie. Aktywacja synchroniczna zaczęła zawodzić: opóźnienia, powielanie żądań, błędy w danych. Przenieśliśmy aktywację na przetwarzanie asynchroniczne za pośrednictwem RabbitMQ i Laravel Jobs, zwiększyliśmy liczbę pracowników i podłączyliśmy WebSockets, aby natychmiast zaktualizować saldo. Obciążenie spadło, duplikaty zniknęły, użytkownik natychmiast widzi wynik.

Asynchroniczna aktywacja kart podarunkowych: kontynuacja historii

Przypomnijmy, od czego wszystko się zaczęło

W poprzednim artykule opowiedzieliśmy, jak zautomatyzowaliśmy obsługę magazynu kart podarunkowych i wyeliminowaliśmy pracę ręczną. Zastąpiliśmy Excela panelem administracyjnym, wdrożyliśmy aktywację bez udziału menedżera, raportowanie oraz API do integracji z systemem 1C.

Wydawało się, że problem został rozwiązany. Jednak ta historia miała ciąg dalszy. Tym razem opowiemy o tym, co się dzieje, gdy automatyzacja zaczyna działać aż za dobrze.

Wzrost aktywności: problem, którego się nie spodziewaliśmy

Po wdrożeniu automatyzacji aktywność na stronie zaczęła rosnąć. Klienci masowo korzystali z funkcji aktywacji kart podarunkowych — dokładnie na to liczyliśmy.

Jednak w okresach szczytowego obciążenia, szczególnie w święta, liczba aktywacji sięgała kilkudziesięciu tysięcy dziennie.

Wtedy ujawniła się druga strona sukcesu.

Jak działała aktywacja: podejście synchroniczne

Początkowo proces aktywacji był synchroniczny:

  1. Użytkownik wprowadza kod karty.

  2. Żądanie trafia do backendu.

  3. Wykonywana jest logika biznesowa aktywacji, w tym zapytania do bazy danych.

  4. Klient otrzymuje odpowiedź.

Przy niewielkim obciążeniu takie rozwiązanie działało bez zarzutu. Jednak gdy duża liczba żądań pojawiała się jednocześnie, zaczęły się problemy:

  • Opóźnienia — wynikające z ograniczonej wydajności systemu i trudności z równoległym przetwarzaniem operacji synchronicznych.

  • Zduplikowane żądania — w okresach szczytowego obciążenia klient czasami wysyłał dwa identyczne żądania, a oba były przetwarzane pomyślnie. Prowadziło to do niespójności danych i błędów.

Najgorsze w przypadku duplikatów było to, że użytkownik mógł otrzymać podwójne doładowanie, a dane w systemie stawały się niespójne. To nie był już tylko problem z wydajnością, ale rzeczywisty incydent.

Rozwiązanie: przetwarzanie asynchroniczne z RabbitMQ i Laravel Jobs

Postanowiliśmy całkowicie zmienić podejście do procesu aktywacji. Wszystkie operacje powodujące spadki wydajności przenieśliśmy do przetwarzania asynchronicznego.

Wykorzystaliśmy:

  • RabbitMQ — broker wiadomości służący do zarządzania kolejkami zadań.

  • Laravel Jobs — wbudowany mechanizm Laravel do obsługi zadań w tle.

Jak zmienił się proces z perspektywy użytkownika:

  1. Użytkownik wprowadza kod karty.

  2. System przeprowadza walidację, sprawdzając format kodu, datę ważności i pozostałe wymagania.

  3. Jeśli walidacja zakończy się pomyślnie, użytkownik otrzymuje komunikat: „Twoje zgłoszenie aktywacji zostało przyjęte. Prosimy czekać na zaksięgowanie środków”.

  4. Zadanie trafia do kolejki, gdzie jest przetwarzane asynchronicznie.

Dzięki temu odciążyliśmy synchroniczną ścieżkę obsługi żądań i rozwiązaliśmy problem z duplikatami.

Nowe wyzwanie: jeden worker to za mało

Początkowo przetwarzanie asynchroniczne odbywało się przy użyciu jednego workera. Zapewniało to stabilność systemu, ale nie wystarczało, aby osiągnąć oczekiwaną szybkość.

Użytkownicy musieli czekać na zaksięgowanie środków dłużej, niż chcieliśmy. Jeden worker nie wystarczał, aby zapewnić niemal natychmiastową aktywację.

Co zrobiliśmy?

  • Zwiększyliśmy liczbę workerów obsługujących aktywacje.

  • Wyeliminowaliśmy opóźnienia w przetwarzaniu, dzięki czemu aktywacje zaczęły przebiegać szybko, nawet w okresach szczytowego obciążenia.

Kolejny problem: użytkownik nie widzi aktualnego salda

Przetwarzanie asynchroniczne rozwiązało problemy z wydajnością i duplikatami, ale jednocześnie stworzyło nowe wyzwanie związane z doświadczeniem użytkownika (UX).

Wcześniej: użytkownik aktywował kartę i natychmiast widział zaktualizowane saldo.

Teraz: użytkownik widział komunikat „Prosimy czekać na zaksięgowanie środków” i nie wiedział, kiedy dokładnie pieniądze pojawią się na koncie.

Było to frustrujące dla użytkowników. Oczekiwanie bez żadnej informacji zwrotnej zawsze jest gorszym doświadczeniem niż natychmiastowy rezultat — nawet jeśli samo przetwarzanie trwa zaledwie kilka sekund.

Rozwiązanie: WebSockets do natychmiastowej aktualizacji salda

Zintegrowaliśmy WebSockets — technologię, która umożliwia serwerowi przesyłanie danych do klienta w czasie rzeczywistym, bez konieczności odświeżania strony.

Jak to działa?

  1. Użytkownik wysyła żądanie aktywacji.

  2. Zadanie trafia do kolejki.

  3. Gdy tylko worker przetworzy aktywację, serwer wysyła zdarzenie za pośrednictwem WebSocket.

  4. Saldo wyświetlane w nagłówku strony aktualizuje się automatycznie, bez przeładowywania strony.

Użytkownik widzi komunikat „Aktywacja została przyjęta”, a sekundę później jego saldo się zmienia.

Wrażenie natychmiastowego działania powraca, mimo że sam proces technicznie nadal odbywa się asynchronicznie.

Rezultaty: co się zmieniło?

Najważniejszy efekt: zmniejszyliśmy obciążenie strony, wyeliminowaliśmy opóźnienia aktywacji dla pracowników, rozwiązaliśmy problem z duplikowaniem żądań, a jednocześnie zachowaliśmy wrażenie natychmiastowego rezultatu dla użytkowników.

Wykorzystane technologie

  • Backend: Laravel (PHP)

  • Kolejki: RabbitMQ + Laravel Jobs

  • Komunikacja w czasie rzeczywistym: WebSockets

  • Baza danych: MySQL

Czego nauczyliśmy się z tego doświadczenia?

1. Sukces automatyzacji to nie koniec, lecz nowy początek

Wzrost aktywności to dobra wiadomość. Jednak wraz z rozwojem pojawiają się problemy, które nie występowały przy mniejszej skali działania. Warto być na nie przygotowanym.

2. Przetwarzanie synchroniczne może stać się wąskim gardłem przy dużym obciążeniu

Wszystko, co można przetwarzać asynchronicznie, warto przenieść do takiego modelu. Pozwala to zmniejszyć obciążenie systemu i wyeliminować całą klasę błędów, takich jak te wynikające ze zduplikowanych żądań.

3. Asynchroniczność bez informacji zwrotnej oznacza słaby UX

Użytkownika nie interesuje, jak system działa od środka. Liczy się dla niego widoczny rezultat.

WebSockets to prosty sposób na przywrócenie wrażenia natychmiastowej reakcji systemu.

4. Skalowanie to proces iteracyjny

Najpierw jeden worker, później kilka.

Najpierw przetwarzanie synchroniczne, później asynchroniczne.

Najpierw brak aktualizacji w czasie rzeczywistym, a następnie WebSockets.

Nie trzeba budować idealnego systemu od samego początku. Wystarczy rozwiązywać problemy w miarę ich pojawiania się.

Na zakończenie

Ta historia pokazuje, że udana automatyzacja często prowadzi do wzrostu, a wzrost przynosi nowe wyzwania.

Przeszliśmy od synchronicznej aktywacji do asynchronicznej, od jednego workera do kilku, a także od komunikatu „prosimy czekać” do natychmiastowej aktualizacji salda.

I prawdopodobnie nie jest to koniec tej historii.

Tak właśnie wygląda rozwój i utrzymanie produktu: nie wystarczy go stworzyć i o nim zapomnieć. Trzeba go stale rozwijać i rozwiązywać kolejne problemy w miarę jego wzrostu.

Porozmawiajmy o projekcie

Opowiedz o zadaniu — wycenimy w kilka dni

Kontakt