Przejdź do treści
megapromotingPorozmawiajmy

Ekspertyza · Złożone architektury

Więcej systemów, które muszą działać razem, także wtedy, gdy jeden z nich pada.

Projektujemy i obsługujemy platformy z wielu usług, z magistralą wiadomości, ograniczonymi ponowieniami, kluczami idempotencji, wyłącznikami obwodu i publikacją z automatycznym powrotem. Każdy z poniższych wzorców działa we własnym systemie, a nie na diagramie.

Już zbudowaneObsługujemy cztery własne platformy o różnych architekturach i możemy otworzyć je wszystkie. Liczby są mierzone dziś poleceniami, a nie pobrane z dokumentacji: aichat ma 28 katalogów usług, 235 modeli danych na czterech schematach i magistralę wiadomości z 297 kolejkami i 17.525 połączeniami; Kallina ma 472 funkcje edge, 559 migracji i 28 zaplanowanych zadań w bazie; MEGA CRM ma backend o długości 9.516 linii z 99 trasami i 179 politykami dostępu na wiersz; Taskin uruchamia 21 zaplanowanych zadań bez żadnego Dockera. Zastrzeżenie, które podajemy: w dwóch miejscach własna dokumentacja została w tyle za kodem — stwierdzenie o liczbie linii było przestarzałe o 16%, a diagram infrastruktury opisywał serwer, na którym już nic nie uruchamiamy. Użyliśmy pomiaru, nie dokumentu.

„Złożona architektura” nie oznacza wielu pudełek na rysunku. Oznacza, że wiesz, co się dzieje, gdy jedno z nich nie odpowiada. System z trzema usługami wywołującymi się synchronicznie, bez limitu ponowień i bez idempotencji, jest bardziej kruchy niż monolit — bo każde nowe połączenie to nowy sposób awarii, a sposobów awarii przybywa szybciej niż funkcjonalności.

Nasza praca wychodzi od podziału odpowiedzialności i kończy się na tym, co widać, gdy coś się psuje. Na jednej z własnych platform wiadomości nie przechodzą bezpośrednio między usługami, lecz przez magistralę z 297 kolejkami, 9 wymianami i 17.525 połączeniami, z których 278 kolejek jest generowanych per klient, a nie pisanych ręcznie. Osiem kolejek ma wymianę dead letter i osiem ma czas życia wiadomości pięć minut — czyli wiadomość, której nie można przetworzyć, trafia gdzieś, gdzie można ją zobaczyć, a nie znika.

Wzorce, które stosujemy, są nieliczne i powtarzalne: ponowienia z limitem zapisanym w kodzie, klucze idempotencji sprawiające, że druga dostawa tego samego zdarzenia jest nieszkodliwa, przełącznik, który zatrzymuje wywołania do usługi padającej powtarzalnie, bramka konsumpcji przed czynnościami kosztującymi pieniądze oraz alerty z progiem powtórzeń, aby usterka przerywana nie pogrzebała reszty.

Ostatnia część, ta, której nie widać: publikacja. Jedna z platform jest publikowana przez synchronizację plików, bez Docker, z weryfikacją, że serwowana strona zawiera dokładnie identyfikator pakietu właśnie opublikowanego — nie tylko że serwer odpowiada 200 — oraz z automatycznym powrotem do poprzedniej wersji, jeśli weryfikacja nie przejdzie. Reguła powstała po realnym incydencie, w którym powierzchowna kontrola anulowała dobrą publikację.

Co obejmuje

Praca, komponent po komponencie

Oddzielamy usługi według tego, co może zepsuć się niezależnie

W jednej własnej platformie są 28 katalogów usług, z których 16 ma własny punkt wejścia, a na produkcji działają pięć procesów pod menedżerem procesów. Każdy kanał komunikacji ma własną usługę i własny port właśnie po to, by awaria jednego nie zatrzymała reszty. Dane są modelowane w czterech oddzielnych schematach, z 235 modelami łącznie — separacja nie jest tylko na poziomie procesu, ale też na poziomie schematu.

Stawiamy magistralę wiadomości, a nie łańcuch synchronicznych wywołań

297 kolejek, 9 wymian, 17.525 połączeń. Nazewnictwo kolejek jest per klient, a w niektórych przypadkach per wątek rozmowy — czyli topologia jest generowana, a nie pisana ręcznie. Istnieje też wymiana z opóźnioną dostawą, dla rzeczy, które mają wydarzyć się później, nie teraz. Osiem kolejek ma skonfigurowaną wymianę dead letter, a osiem ma czas życia wiadomości pięć minut.

Ponowienia z limitem, nie w nieskończoność

Trzy różne limity dla trzech różnych sytuacji, wszystkie zapisane w kodzie: pięć ponowień dla wiadomości zaplanowanych, z licznikiem utrwalonym, a nie trzymanym w pamięci; trzy ponowienia na poziomie kolejki, liczone w nagłówku wiadomości; oraz trzy ponowienia z wykładniczym zwiększaniem pauzy dla wysyłki e-mail, na dwóch ścieżkach dostawcy. Ponowienie bez limitu to nie odporność, to pętla.

Klucze idempotencji, żeby druga dostawa niczego nie psuła

Dla zdarzeń od stron trzecich używamy wstawienia, które kończy się niepowodzeniem, jeśli zdarzenie było już widziane: naruszenie unikalności bazy danych traktowane jest jako „duplikat”, a nie jako błąd, wraz ze sprawdzeniem świeżości momentu. Dla płatności klucz jest oznaczeniem na transakcji kredytowej, więc ponowne wysłanie tego samego webhooka nie kredytuje dwa razy. Dla wiadomości deduplikacja odbywa się po identyfikatorze, z czasem życia i czyszczeniem.

Przełącznik obwodu dla kruchej integracji

Integracja zależna od sesji strony trzeciej ma rzeczywisty przełącznik, a nie tylko notatkę w dokumentacji: po dziesięciu kolejnych niepowodzeniach wywołania zatrzymują się na dwie minuty. Bez niego strona, która nie odpowiada, zamienia padnięty kanał w wolną platformę, bo wszyscy czekają w żądaniu.

Bramka konsumpcji przed czynnościami kosztującymi pieniądze

W jednej z platform każda płatna akcja przechodzi przez funkcję weryfikacji z czterema krokami, każdy z własnym, jasno zwróconym powodem: konto zawieszone, osiągnięto miesięczny limit rozmów, przekroczono dzienny limit wydatków, niewystarczające saldo. W przypadku wyjątku zwraca „niedozwolone”, nie „dozwolone” — brama się zamyka, nie otwiera, gdy coś nie działa. W tle stoi rejestr zdarzeń zużycia z kosztem na zdarzenie, rozbity według dostawcy, modelu i sposobu.

Limitowanie szybkości, które przetrwa ponowne uruchomienie

Przesuwne okno trzymane w bazie danych, a mapa w pamięci tylko jako skrót wewnątrz jednego wywołania. Powód jest zapisany na samym początku pliku: procesy obsługujące żądania są krótkotrwałe, więc ich pamięć nie może być źródłem prawdy. Korzysta z tego piętnaście funkcji, a limit dla zewnętrznego dostawcy ma własny, osobny ogranicznik.

Obserwowalność, która rozróżnia „odpowiedziało” od „zadziałało”

Odpowiedź 200 nie oznacza, że zadanie się powiodło. Otoczka, w której uruchamiamy zaplanowane zadania, przechodzi rekurencyjnie przez odpowiedź po listach błędów lub niepowodzeń i traktuje „200 z niepowodzeniami” jako osobny wynik. Alerty mają próg powtórzeń — dziesięć minut w jednym systemie, dwie godziny w innym — a znacznik jest usuwany przy pierwszym powodzeniu, aby wracający problem mógł zaalarmować ponownie od razu.

Publikacja z weryfikacją i automatycznym powrotem

Publikacja sprawdza nie tylko, czy adres odpowiada 200, lecz także czy serwowana strona zawiera dokładnie identyfikator właśnie opublikowanego pakietu; w przeciwnym razie przywraca poprzednią wersję. Dla usługi działającej w tle publikacja porównuje drzewo po sumach kontrolnych i pomija ponowne uruchomienie, jeśli nic się nie zmieniło — niepotrzebne ponowne uruchomienie zabija pętlę będącą w trakcie działania.

Jak to wygląda

Trasa, krok po kroku.

01

Rysujemy mapę trybów awarii, nie pudełek

Dla każdego połączenia między dwoma systemami: co się dzieje, jeśli drugi jest wolny, jeśli jest niedostępny, jeśli odpowiada dwa razy, jeśli odpowiada błędnie. Dostarczamy: listę połączeń z oczekiwanym zachowaniem w każdej z czterech sytuacji, plus decyzję synchronicznie/asynchronicznie dla każdego.

02

Wdrażamy magistralę i kontrakty wiadomości

Kolejki, wymiany, wiadomości martwe, czas życia wiadomości i klucz idempotencji dla każdego typu zdarzenia. Dostarczamy: topologię, kontrakty wiadomości i udokumentowane zachowanie przy ponownym wysłaniu.

03

Dodajemy bramy: szybkość, zużycie, wyłącznik

Limitowanie szybkości w przesuwanym oknie w bazie, brama zużycia przed akcjami, które kosztują, oraz wyłącznik dla integracji poza naszą kontrolą. Dostarczamy: ustawione progi, jasno zwrócone powody i testy dla każdego stopnia odmowy.

04

Budujemy publikację i powrót

Publikacja z weryfikacją treści, nie tylko kodu odpowiedzi, poprzednia wersja trzymana obok i automatyczny powrót. Dostarczamy: procedurę publikacji, procedurę powrotu przećwiczoną co najmniej raz oraz sondy zdrowia — jedną płytką dla „żyje”, jedną głęboką dla „naprawdę działa”.

05

Przekazujemy z dokumentacją zweryfikowaną względem kodu

Dokument jest weryfikowany względem pomiaru przed przekazaniem, ponieważ dokumentacja, która zostaje w tyle, jest bardziej niebezpieczna niż jej brak. Dostarczamy: diagram, procedury i wyraźną listę miejsc, gdzie dokument i kod zostały znalezione w niezgodzie, wraz z tym, co poprawiono.

DocumenteRozmowyŹródłaSyntezaDziałanieZespółKontekstualizujŹródła autoryzowane. Działania sprawdzone.
Systemy nie wywołują się łańcuchowo: między nimi stoi magistrala z kolejkami per klient, osobnymi giełdami na typ ruchu i martwymi listami dla tego, czego nie da się przetworzyć. Wokół niej bramki — ograniczanie częstotliwości, bramka zużycia, wyłącznik obwodu — i sondy zdrowia, jedna powierzchowna i jedna głęboka.

Dane

Czego dotykamy, gdzie to leży i jak długo zostaje

Pytania, które zadaje każdy, kto ma inspektora ochrony danych — postawione tutaj, zanim on je zada.

Gdzie są dane, per platformę
Nie ma jednej odpowiedzi i dobrze, że jej nie ma: jedna platforma używa MySQL na własnym serwerze, dwie używają PostgreSQL przez Supabase — jedna hostowana, jedna zainstalowana na naszej infrastrukturze — a jedna trzyma treść jako pliki statyczne, bez bazy danych w ogóle. Wybór robi się według wymagań, nie z przyzwyczajenia.
Dostęp stosuje się w bazie, nie tylko w aplikacji
W jednym z naszych systemów wewnętrznych zabezpieczenie na poziomie wiersza jest włączone na 43 tabelach, przez 90 instrukcji, z 179 zapisanymi politykami. Zasada, której pilnujemy: jeśli wywołanie omija aplikację i trafia bezpośrednio do bazy, i tak nie powinno widzieć cudzych wierszy.
Rejestr zużycia
Jeden wiersz na zdarzenie z typem, dostawcą, ilościami rozdzielonymi według sposobu — sekundy rozmów, tokeny wejściowe, tokeny wyjściowe, znaki — plus koszt w walucie z sześcioma miejscami po przecinku i odliczone kredyty. Z niego wychodzi dzienny limit wydatków, koszt na wywołanie i projekcja zużycia.
Migracje są historią, nie dokumentacją
559 migracji w jednej platformie, 80 w innej. Schemat zmienia się przez wersjonowane migracje, więc stan bazy można odtworzyć i odczytać chronologicznie. Gdy dokument i migracja się nie zgadzają, migracja ma rację.
Zaplanowane zadania działają w bazie albo w cron, nie w procesie
28 zaplanowanych zadań działa wewnątrz bazy danych w jednej platformie; w innej 21 zadań działa przez systemowy cron. Powód jest zapisany w pliku: zegary wewnątrz usługi resetują się przy każdym ponownym uruchomieniu, więc pętla co trzy godziny w usłudze, która jest republiko w ana, nigdy się nie uruchamia.

Przykład

Topologia wiadomości, która generuje się sama dla każdego klienta

Sytuacja

Platforma z wieloma klientami, każdy z własnymi kanałami komunikacji, własnymi wątkami rozmów i własnymi powiadomieniami. Naiwna wersja — wspólna kolejka i filtr po identyfikatorze klienta — sprawia, że klient o dużym wolumenie blokuje resztę, a błąd w jednym wątku zatrzymuje kolejkę dla wszystkich.

Co zbudowaliśmy

Topologia generuje się per klient, nie pisze się jej ręcznie: z 297 kolejek, 278 to typ „powiadomienia dla konkretnego konta”, a niektóre schodzą aż do poziomu pojedynczego wątku rozmowy. Nad nimi dziewięć exchange’ów rozdziela typy ruchu — powiadomienia, tokeny, webhooki, kanały — i istnieje exchange z opóźnioną dostawą dla tego, co ma wydarzyć się później. Osiem kolejek ma dead-letter exchange, osiem ma czas życia wiadomości pięć minut. Reguły orkiestracji można przeładować na gorąco, przez kanał publikacji, bez restartu konsumentów.

Co z tego wyszło

Klient o dużym wolumenie nie opóźnia pozostałych klientów, a wiadomość, której nie da się przetworzyć, trafia w miejsce, gdzie można ją zobaczyć i ponowić, zamiast znikać albo blokować kolejkę. Liczba połączeń w magistrali — 17.525 — dokładnie pokazuje, dlaczego topologia musi być generowana: nikt nie utrzymuje ręcznie czegoś takiego.

Czego nie mówi ten przypadek

Cena jest operacyjna: magistrala tej wielkości wymaga własnego monitorowania i planu na porzucone kolejki, inaczej będzie rosła bez końca. A generowanie per klient zakłada, że usunięcie klienta usuwa też jego topologię — jeśli tego kroku brakuje, gromadzą się martwe kolejki.

Pytania

O co pytają ludzie, zanim zadzwonią

Skąd mam wiedzieć, że nie sprzedajecie mi złożoności, której nie potrzebuję?

Bo pierwsza rekomendacja, którą często dajemy, to nie separować. Każda nowa usługa to nowy sposób awarii, a sposoby awarii mnożą się szybciej niż funkcje. Przykład z naszych własnych systemów: backend jednego z nich to pojedynczy plik o długości 9.516 linii z 99 trasami. Nie jest to eleganckie i mówimy to wprost, ale publikuje się jednym krokiem i diagnozuje w jednym miejscu. Separację robi się wtedy, gdy istnieje mierzalny powód — usługa, którą trzeba skalować osobno, osobny zespół, osobne tempo wdrożeń.

Co się dzieje, gdy jeden z systemów w łańcuchu nie odpowiada?

To zależy od tego, co wspólnie ustaliliśmy na etapie mapy, i o to chodzi. W naszych systemach: wiadomość trafia do kolejki i jest ponawiana ograniczoną liczbę razy, potem trafia do wymiany martwych listów, gdzie może zostać zobaczona; niestabilna integracja ma wyłącznik, który po dziesięciu kolejnych niepowodzeniach wyłącza się na dwie minuty zamiast trzymać żądania w oczekiwaniu; a działania, które kosztują pieniądze, są zatrzymywane przez bramkę, która przy wyjątku odrzuca, nie pozwala.

Jak zapobiegacie przetworzeniu tego samego zdarzenia dwa razy?

Za pomocą klucza idempotencji, a nie przez sprawdzenie „czy już to widzieliśmy?”, które ma wyścig danych. Konkretnie: wstawiamy wiersz z identyfikatorem zdarzenia i jeśli baza odrzuci go z powodu naruszenia unikalności, traktujemy ten kod błędu jako sygnał duplikatu, a nie awarii. W przypadku płatności znacznik znajduje się na transakcji kredytowej, więc ponowne wysłanie tego samego webhooka nie kredytuje dwa razy.

Używacie Docker czy nie?

Oba, i za każdym razem uzasadniamy wybór. Jeden system działa w kontenerze, ale bez zamontowanych wolumenów, co oznacza, że wdrożenie to kopiowanie do kontenera plus restart — `docker rm` tam powoduje utratę stanu i jest to zapisane w dokumencie operacyjnym dokładnie tak. Inna platforma nie ma żadnego Docker: wdrożenie to synchronizacja plików, a skrypty wdrożeniowe są instalowane ręcznie z uprawnieniami administratora i wystawione do automatycznego przepływu dokładnie dwoma dozwolonymi ścieżkami. Powód jest zapisany w kodzie: skrypt, który przepływ może nadpisać, jest skryptem, przez który przepływ może eskalować.

Skąd wiecie, że zaplanowane zadanie naprawdę się wykonało?

Złego doświadczenia. Nasz codzienny raport był martwy jedenaście dni, między 7 a 17 sierpnia, podczas gdy zaplanowane zadanie uruchamiało się codziennie — używana komenda cicho kończyła się błędem i nic nie zapisywała. Od tego czasu każde zadanie działa w otoczce, która analizuje odpowiedź pod kątem list błędów, traktuje „200 z błędami” jako odrębny wynik, ma maksymalny czas wykonania i zapisuje jedną ustrukturyzowaną linię na uruchomienie. Alerty mają próg powtórzeń, a znacznik usuwa się po pierwszym sukcesie.

Jak bardzo ważna jest abstrakcja po stronie dostawcy?

Dużo, jeśli dostawca jest w obszarze, który szybko się zmienia. Dla głosu mamy trzy osobne mosty — po jednym dla każdego dostawcy mowy w czasie rzeczywistym — które implementują ten sam interfejs. Most konwertuje audio między centralą telefoniczną a dostawcą, na dedykowanym porcie, z serwerem sprawdzania stanu powiązanym tylko z interfejsem lokalnym, niewystawionym na zewnątrz. Zmiana dostawcy staje się decyzją, nie przepisywaniem.

Czy wasza dokumentacja jest aktualna?

Nie wszędzie, i wolimy mówić gdzie. Podczas przygotowywania tej strony zmierzyliśmy dwa systemy i znaleźliśmy, że własny dokument nie nadąża: jedno stwierdzenie o rozmiarze pliku było mniejsze o około 16% niż rzeczywistość, a diagram infrastruktury opisywał serwer, z którego już się przenieśliśmy. Zasada, którą stosujemy i której wymagamy w projektach: gdy dokument i pomiar się nie zgadzają, pomiar ma rację, a dokument jest poprawiany w tym samym kroku.

Czego nie robicie?

Nie obiecujemy celów dostępności bez pomiaru — procent typu „99,9%” wymaga danych terenowych z pewnego okresu, a jeśli ich nie mamy, nie twierdzimy tego. Nie projektujemy systemów, których nie możemy utrzymywać ani przekazać: jeśli rezultatem jest architektura, której wasz zespół nie może utrzymać, to jest to zła architektura. I nie robimy certyfikacji zgodności — możemy zbudować kontrole, nie możemy wydać certyfikatu.

Na czym opierają się powyższe stwierdzenia (23 źródeł)

23 z nich to kod i pliki z naszych repozytoriów. Nie publikujemy ich nazw ani numerów linii: razem, na jednej stronie, opisałoby to zbyt dokładnie, jak zbudowane są systemy, które nie należą tylko do nas. Przechodzimy je razem z Tobą, w repozytorium, na życzenie — weryfikacja pozostaje możliwa, tylko odbywa się w rozmowie.

Co chcesz, żeby działało lepiej?

Opowiedz nam o swoim procesie. Wspólnie ustalimy, co warto zbudować, co da się połączyć i jak sprawdzimy wynik.

Porozmawiajmy