Kilka dni temu miała miejsce potężna awaria Credit Agricole. Była ona na tyle uciążliwa, że wielu klientów po prostu straciło wszystkie środki na koncie. Dlaczego o tym piszę na blogu o tematyce związanej z programowaniem? Bo przyczyna tej kompromitującej awarii wynikała z błędu programistów.
Jeśli planujesz pracę jako programista to z pewnością otrzymasz oferty pracy z banków. Zanim się zgodzisz, lepiej zastanów się dwa razy. W bankach odpowiedzialność jest ogromna, a skutki jednej decyzji programisty mogą wywołać burzę w internecie. Tak było w zeszłym tygodniu w banku Credit Agricole.
Jak zaczęła się awaria Credit Agricole?
Był 2 września 2021. Pierwsze znaki świadczące o tym, że coś może być nie tak pojawiły się około godziny 16:00. Nagle wielu klientów straciło wszystkie środki. Na każdym rachunku pojawił się szereg dziwnych transakcji. Wyglądało to tak jakby ktoś ukradł Ci kartę i zaczął robić zakupy w Twojej okolicy. Później jednak okazało się, że bank zdublował Twoje własne transakcje. Ale tylko niektóre. Były to transakcje z ostatnich dni ale też sprzed dwóch miesięcy.
Nikt nie wiedział co się dzieje. Nikt nie mógł zapłacić za zakupy. Nikt nie mógł nawet dodzwonić się na infolinię Credit Agricole. A bank milczał… Ograniczył się jedynie do umieszczenia wpisu na facebooku. Kiedy jest tak poważna awaria Credit Agricole, to należy przede wszystkim poinformować klientów .Taki oto wpis pojawił się kilka godzin po awarii:
W jaki sposób bank rozwiązał problem?
Krótko mówiąc – słabo. Wieczorem, kiedy awaria Credit Agricole przybrała na sile, to na facebooku pojawiła się lawina negatywnych komentarzy (aktualnie jest ich ponad 6 tysięcy). Ich autorami byli niezadowoleni klienci. I nic dziwnego bo zostali pozbawieni pieniędzy, które w banku powinny być jednak bezpieczne.
Bank zobowiązał się na facebooku, że w nocy rozwiąże problem. Stwierdził nawet, że problem został rozwiązany. Choć w rzeczywistości to nie była prawda. To nie była zwykła literówka, tylko awaria Credit Agricole, jednego z największych banków w Polsce.
W nocy na kontach klientów zaczęły pojawiać się kolejne, całkiem losowe transakcje. Do rana nadal nie udało się rozwiązać problemu. Kolejny dzień mijał bez informacji ze strony Credit Agricole. Część klientów nadal pozostawała bez środków na koncie. Inni otrzymali zwrot transakcji, których nigdy nie dokonali. To tak jakby widzieli transakcje innych klientów. Jednym słowem – zrobił się jeszcze większy bałagan. Na dodatek nie dało się tego cofnąć. Sytuacja była patowa.
Czy można było łatwo naprawić awarię?
Ktoś może zapytać: Dlaczego bank nie przywrócił stanu kont sprzed awarii? Można przecież sprawdzić jaki był stan konta przed awarią i zmienić środki dostępne na koncie.
Teoretycznie tak. Ale to dużo bardziej skomplikowane. Dlaczego? Bo bank nie przechowuje Twojego stanu konta w postaci jednej liczby. To ile pieniędzy masz na koncie jest rezultatem wszystkich dotychczasowych operacji. To suma wszystkich przelewów przychodzących i wychodzących. I w ten sposób bank przechowuje stan Twojego konta. W programowaniu taką metodę przechowywania informacji nazywamy Event Sourcing. Event Sourcing to wzorzec architektoniczny opisany 20 lat temu w książce Patterns of Enterprise Application Atchitecture.
Oprogramowanie
Przejdźmy do najbardziej interesującego tematu, czyli do programowania. Poniżej omówię potencjalne przyczyny problemu z punktu widzenia programisty. Nie ulega wątpliwości fakt, że całe to zamieszanie ze znikającymi pieniędzmi było spowodowane przez oprogramowanie.
Potencjalne przyczyny
Potencjalne przyczyny:
- OPCJA 1: System przetworzył zadania zalegające w kolejce – jest to jednak mało prawdopodobne bo na kontach pojawiły się duplikaty transakcji sprzed 2 miesięcy. Credit Agricole raczej nie trzymał wiadomości w kolejce tak długo. Gdyby tak było to ich ilość mogłaby spowodować zawieszenie się kolejki. Poza tym każda awaria kolejki oznaczałaby utratę danych. (mówiąc o kolejce mam na myśli system typu RabbitMQ, czyli aplikację umożliwiającą niezawodną komunikację kilku systemów)
- OPCJA 2: Ktoś mógł wykonać manualne operacje na bazie danych. Wystarczyło wykonać pewne zapytania SQL i problem gotowy. W tym scenariuszu ktoś mógłby po prostu zaktualizować dane w bazie, ale wymagałoby to świadomego użycia poleceń INSERT albo UPDATE.
- OPCJA 3: Atak hakerski. Osoba atakująca mogłaby chcieć szybko się wzbogacić. Najłatwiej to osiągnąć duplikując kilka ostatnich wpływów na konto. Ale gdyby haker zrobił to tylko na swoim koncie to bank łatwo znajdzie winnego. Dlatego haker postanowił podarować pieniądze wszystkim klientom banku.
- OPCJA 4: Błąd programisty. Ta opcja jest najbardziej prawdopodobna. Chodzi o Domain Driven Design. W skrócie – DDD. Więcej o tej technice projektowania systemów informatycznych znajdziesz w książce Erica Evansa pt. Domain Driven Design. Jest to modne, ale skomplikowane podejście. Prawidłowe stosowanie DDD wymaga dużej wprawy i lat doświadczenia. Często nawet doświadczeni programiści spotykają się z tym podejściem po raz pierwszy.
Zatem skupimy się na opcji numer 4, bo najbardziej prawdopodobne, że potężna awaria Credit Agricole była spowodowana błędami programistów. Trzeba pamiętać o tym jak ważna jest wiedza na temat architektury również w pracy każdego programisty. Ważne jest też testowanie bo gdyby odpowiednie testy były wykonywane odpowiednio starannie to raczej nie doszłoby do tej awarii.
Jak do tego doszło?
Głównym problemem okazały się zduplikowane operacje na rachunkach bankowych. Chociaż klienci płacili tylko raz to pieniądze zniknęły z ich kont dwukrotnie. A więc przyczyna to duplikaty operacji.
Prześledzimy teraz jak te duplikaty powstały.
KROK 1: Klient wykonał płatność kartą na kwotę 120 PLN
Oto krótkie wyjaśnienie co dzieje się na powyższym rysunku:
- Początkowe saldo na naszym koncie 2000 PLN
- Wykonujemy płatność w sklepie na kwotę 120 PLN
- Zanim nasza płatność zostanie zaksięgowana na koncie trzeba wykonać kilka kroków.
Jednym z wymaganych kroków jest sprawdzenie czy należy poprosić klienta o PIN. Tylko część płatności będzie spełniać ten warunek.
Krótkie przypomnienie zasad związanych z kodami PIN: Według raportu NBP od marca 2020 podanie kodu PIN jest wymagane przy płatnościach powyżej 100 zł. Z kolei portal money.pl jakiś czas temu zapowiadał, że PIN będzie trzeba podać również przy piątej i każdej kolejnej płatności kartą danego dnia. W praktyce widać jednak, że banki wprowadzają swoje własne limity.
KROK 2: Pojawiają się nowe wymagania. Programista musi zmodyfikować system.
Na spotkaniu programistów z kierownictwem banku, pojawiają się nowe wymagania. Odtąd wszystkie numery PIN wpisane przez klientów musimy zapisać w specjalnej bazie danych. Na domiar złego musimy to zrobić dla wszystkich transakcji od początku lipca 2021.
Musimy zapisywać również wszystkie przyszłe transakcje z użyciem PINu.
Programista proponuje: będziemy generować nową komendę SavePinCommand w komponencie PinRequiredEventHandler. Cały zespół zgadza się z programistą i w efekcie system wygląda tak jak na rysunku poniżej. Zwróć uwagę na niedopuszczalny efekt uboczny. Klient powinien nadal mieć na koncie 1880 zł, a zostaje mu tylko 1760 zł. Tu pojawiają się zmiany w historii rachunków. Zmiany te są nieodwracalne, bo nie można ich po prostu usunąć. Tak działa Event Sourcing. Tak działają banki.
Owszem, takie rozwiązanie będzie perfekcyjnie działać dla wszystkich przyszłych transakcji.
Ale kiedy obsłużymy wszystkie transakcje z PINem od początku lipca aż do dzisiaj, to zabierzemy klientom ich pieniądze. Te pieniądze po prostu zniknęły podczas awarii Credit Agricole.
Jaka była skala awarii Credit Agricole?
Aby uświadomić sobie rozmiar problemu przytoczę trochę danych liczbowych.
Spójrzmy na dane podawane przez portal prnews.pl. Według powyższej tabeli Bank Credit Agricole ma ponad 1.6 miliona klientów. Natomiast liczba transakcji kartą płatniczą w 1. kwartale 2021 roku to aż 1,644 mld.
liczba klientów indywidualnych banków w Polsce: 45 007 875. Klienci Credit Agricole stanowią więc 1 610 000/45 007 875, czyli 3.5% wszystkich klientów. Zatem klienci banku wykonali 57 milionów transakcji przy użyciu karty płatniczej w ciągu zaledwie kwartału. W sumie daje to około 14 mln transakcji miesięcznie. Przy takiej ilości danych o błąd nietrudno.
Ciekawostka: w 1. kwartale 2021 średnia wartość transakcji przeprowadzonej przy użyciu karty płatniczej wyniosła 121 zł.
Zatem liczba transakcji kartą kiedy wymagane było podanie kodu PIN jest całkiem spora.
Problem
Wróćmy do naszego diagramu z komponentami aplikacji. Problem zduplikowanych wpisów rozpoczął się w momencie powstania zdarzenia PinRequiredEvent. W rezultacie klasa PinRequiredEventHanler generuje dwie komendy:
- SavePinCommand – ta komenda spowoduje zapisanie w bazie informacji o transakcji z kodem PIN.
- AddTransactionCommand – w przypadku przetworzonych już raz transakcji ta komenda jest nadmiarowa ponieważ już raz została wykonana. Ponowne jej wykonanie doprowadzi do zduplikowanych transakcji na koncie klienta. To jest właśnie moment kiedy pieniądze zaczynają znikać z kont klientów.
Lepsze rozwiązanie
Czy tej awarii można było zapobiec? Myślę, że z tak. Zobacz jakie powinno być prawidłowe rozwiązanie.
Spójrzmy na poniższy diagram. Zauważ, że rozwiązanie jest o wiele prostsze i nie ma efektów ubocznych w postaci nadmiarowej zmiany stanu konta. Najprostsze rozwiązania bardzo często są lepsze niż te skomplikowane.
Dla historycznych transakcji należy wygenerować SavePinCommand zamiast PinRequiredEvent. Dzięki temu nie zaksięgujemy transakcji po raz drugi. Natomiast dane o transakcji z kodem PIN zostaną zapisane w bazie danych zgodnie z naszymi oczekiwaniami. Podsumowując: Niepozorna zmiana kodu przez programistę może mieć olbrzymie konsekwencje.
Podsumowanie
Podejście Domain Driven Design jest modne, ale trzeba z niego umiejętnie korzystać. Trzeba bardzo uważać. Bo używanie zdarzeń i komend wcale nie jest łatwe. Trzeba pamiętać o wielu zasadach i przeznaczyć dużo czasu na projektowanie. W dużych systemach jedno zdarzenie może powodować uruchomienie wielu innych zdarzeń i komend. W takiej sytuacji trudno zrozumieć skomplikowany system i kontrolować przepływ danych. Wtedy wystarczy mała pomyłka i problem gotowy. Przykład Credit Agricole z dnia 03.09.2021 pokazuje, że straty wizerunkowe i finansowe mogą być nieodwracalne. Pokazuje on też jak wielka odpowiedzialność spoczywa na programistach. To programista musi zaprojektować system w taki sposób, żeby nikt nie stracił swoich pieniędzy. Oczywiście nie jest on w tym sam bo są inne osoby, które są równie odpowiedzialne za tę awarię: testerzy, architekci, managerowie, osoby tworzące testy automatyczne i testy manualne.
Lekcja na przyszłość.
Na zakończenie zobaczmy co Ty, jako programista, możesz wyciągnąć z tej sytuacji. Jak powiedział kiedyś jeden z moich współpracowników: Trzeba uczyć się na błędach, ale najlepiej na cudzych. Oto lekcja na dzisiaj:
- Podczas projektowania systemów powinieneś wraz z zespołem wykorzystać metodę Event Storming. Tak nazywamy spotkanie podczas, którego ustalamy m. in. zdarzenia domenowe i komendy. Zapoznaj się z artykułem na bulldogjob.
- Po skończonym Event Stormingu mamy w zasadzie gotowy diagram przepływu sterowania programu (ang. flow diagram). W różnych projektach do stworzenia takiego diagramu korzystamy z narzędzia online Miro.
- Każde dodanie nowej komendy i zdarzenia należy starannie zaprojektować. Koniecznie należy zaktualizować nasz diagram przepływu. Dzięki temu szybciej wykryjemy błędy w projekcie i potencjalne ryzyko. W przypadku projektu Credit Agricole takim ryzykiem było stworzenie zdarzenia domenowego. Gdyby taki diagram przepływu istniał to od razu moglibyśmy wyłapać, że coś jest nie tak.
- Szkolenia. Dobrze poznaj Domain Driven Design. Choć czasami trudno trafić do firmy, która stosuje dobre praktyki, to jako profesjonalista musisz znać. Zacznij od przeczytania książki Erica Evansa.
- Testuj. Testuj i jeszcze raz testuj. Jeśli jesteś szefem Credit Agricole to pewnie uważasz, że nie warto inwestować w testowanie. Ale zastanów się czy Twoją firmę stać na brak testów. Rok temu z banku Credit Agricole odeszło 90 tys. klientów. Tylko Santander miał gorszy wynik. Inwestując pieniądze w testy, w dział testów, w automatyzację testów można skutecznie zapobiegać takim awariom jak wrześniowa awaria banku Credit Agricole.
- Poznaj podstawy programowania. Zdaję sobię sprawę, że domain driven design wymaga pewnej wiedzy z zakresu programowania. Więc jeśli nie czujesz się na siłach aby opanować DDD, to najpierw zapisz się na mój darmowy kurs programowania online. Dzięki temu poznasz zasady programowania w języku C#. Dzięki temu później łatwiej będzie Ci opanować DDD.