Metryka bezczynności, przez którą prawie zmniejszono walidator
Punkt wyjścia
Miesięczny rachunek za chmurę dla dwóch środowisk platformy tradingowej opartej na blockchainie wyniósł około 2500 USD za samą moc obliczeniową. Nie alarmująco, ale wystarczająco, żeby zadać oczywiste pytanie: czy płacimy za pojemność, której nikt nie używa? Dwie grupy maszyn wyróżniały się jako kandydaci — kilka ogólnych węzłów zarządczych i większa grupa mocniejszych maszyn przeznaczonych do uruchamiania węzłów walidatorów blockchaina platformy. To walidatory były warte prawdziwych pieniędzy: dziewięć dużych instancji na środowisko, każda z ośmioma wirtualnymi CPU i 32 GB pamięci.
Instynkt podpowiadał, żeby po prostu spojrzeć na zużycie CPU i na tej podstawie zdecydować.
Odkrycie
Trzydzieści dni metryk CPU wróciło z wynikami wręcz zawstydzająco niskimi. Węzły zarządcze dreptały w rozsądnych 10-20% z okazjonalnymi skokami — w porządku, zgodnie z oczekiwaniami. Węzły walidatorów za to ledwie oddychały: średnio 1-5% CPU, ze skokiem do całych 13% przy najcięższym obciążeniu w miesiącu. Na papierze to osiem drogich wirtualnych CPU stojących niemal całkiem bezczynnie, miesiąc po miesiącu, na osiemnastu maszynach.
Każdy instynkt mówił: zmniejsz je. Połowa CPU, połowa ceny, oczywista wygrana.
Zwrot
Zanim wprowadzono tę zmianę, warto było sprawdzić jeszcze jedno: o co faktycznie prosi obciążenie działające na tych węzłach? Własna konfiguracja zasobów podów walidatorów opowiadała inną historię niż wykres CPU. Każdy pod żądał 2 CPU, ale pełnych 8 GB pamięci, z zapasem do 20 GB pod obciążeniem — profil, który czyta się jak obciążenie głodne pamięci, a nie CPU. Walidator blockchaina trzyma w pamięci bieżący cache stanu łańcucha; to dokładnie ten rodzaj rzeczy, która na wykresie CPU wygląda na bezczynną, a i tak potrzebuje każdego przydzielonego gigabajta.
Zmniejszenie na podstawie samej liczby CPU byłoby optymalizacją złego zasobu — a jeśli okazałoby się błędem, trybem awarii nie byłoby „aplikacja działa trochę wolniej”. Byłby nim produkcyjny walidator zostający w tyle za konsensusem albo wprost zabijany z braku pamięci. To dużo gorszy wtorek niż przewymiarowana faktura.
Więc: zdobyć prawdziwe liczby pamięci, zanim cokolwiek się ruszy. To powinna była być łatwa część.
Zamknięte drzwi
Narzędzia dashboardów samego klastra — już wdrożone, już zbierające dokładnie potrzebne metryki — wydawały się najszybszą drogą. Ale dostępne poświadczenia tylko do odczytu trafiły na nieoczekiwaną ścianę przy próbie dotarcia do nich bezpośrednio przez narzędzia wiersza poleceń klastra: żądanie dotarło do klastra bez problemu, tożsamość za nim była ważna, ale odpowiedź wróciła jako krótkie „you must be logged in”.
To sformułowanie samo jest tropem. Problem sieciowy wygląda jak timeout. Naprawdę nieważna tożsamość zostaje odrzucona, zanim w ogóle dotrze do API. „You must be logged in” z żądania, które widocznie dotarło aż do serwera, oznacza coś węższego i koncepcyjnie łatwiejszego do naprawy, choć nie w tej chwili: lista dostępu klastra po prostu nie miała wpisu mapującego tę konkretną tożsamość na jakiekolwiek uprawnienia. Na liście były dwie tożsamości administracyjne. Ta tylko do odczytu, dodana później właśnie do takich śledztw, nie była.
Zamiast poszerzać dostęp do klastra tylko po to, żeby pobrać jedną metrykę — zmiana większa i trudniejsza do cofnięcia, niż uzasadniało pytanie — lepszym ruchem było całkowite obejście problemu. Klaster już uruchamiał dashboard metryk z własnym logowaniem, dostępny przez web, bez potrzeby jakiegokolwiek dostępu na poziomie klastra.
Druga ściana
Podłączenie asystenta AI do kodowania tak, żeby bezpośrednio odpytywał ten dashboard, oznaczało mały kawałek nowego narzędzia: lokalnie uruchamiany proces konektora, uwierzytelniony ograniczonym tokenem tylko do odczytu, rozmawiający z asystentem przez standardowy protokół wtyczek.
Połączył się. A potem nic nie robił.
Każde żądanie do niego po prostu wisiało aż do timeoutu, bez komunikatu błędu po żadnej stronie, który by wyjaśnił dlaczego. Token potwierdzono jako ważny — zwykłe żądanie webowe do endpointu health dashboardu z tym samym tokenem od razu wróciło zdrowe. Ścieżka sieciowa była w porządku. Proces konektora wyraźnie działał; po prostu nie odpowiadał.
Wbudowany tekst pomocy konektora stwierdzał czarno na białym, że domyślnie używa dokładnie tego trybu komunikacji, którym do niego mówiono. To powinno było zakończyć śledztwo — tyle że faktyczne zachowanie w ogóle nie zgadzało się z udokumentowaną wartością domyślną. Całkowite pominięcie asystenta i ręczne podanie konektorowi surowej wiadomości handshake, a potem obserwacja jego własnego logu wewnętrznego, ujawniły prawdziwą historię: po cichu startował w zupełnie innym trybie — jako stale działający serwer sieciowy — zamiast w trybie żądanie-odpowiedź, który jego własny tekst --help podawał jako domyślny. Nic z tej niezgodności nie było widoczne, dopóki nie spojrzało się bezpośrednio w logi samego procesu; z zewnątrz oba tryby wyglądają identycznie — działający proces, który przyjmuje połączenie i nic nie odpowiada.
Poprawką była jedna jawna flaga, wymuszająca właściwy tryb zamiast ufania deklarowanej wartości domyślnej narzędzia. Gdy flaga była na miejscu, ten sam handshake, który minutę wcześniej dał ciszę, wrócił natychmiast, czysto i w pełnej formie.
Rozwiązanie
Po drodze wyszły dwa mniejsze ustalenia: metoda uwierzytelniania sugerowana w instrukcji konfiguracji konektora była już wewnętrznie oznaczona jako przestarzała na rzecz nowszej — łatwo to przeoczyć, idąc za pierwszym znalezionym przykładem zamiast sprawdzić notkę o wycofaniu. A rejestracja gotowego konektora w asystencie do kodowania dwa razy wylądowała w zupełnie złym projekcie — polecenie rejestracji przypisuje się do folderu, w którym akurat stoi terminal w chwili uruchomienia, a nie do projektu, w którym faktycznie pracuje asystent. Za każdym razem poprawką było sprawdzenie własnego polecenia statusu narzędzia zamiast zakładania, że pierwszy komunikat „sukces” oznacza, że trafiło tam, gdzie miało.
Pod koniec sesji konektor działał, był poprawnie zarejestrowany i zwracał prawdziwe dane. Faktyczny werdykt, czy te węzły walidatorów można zmniejszyć, nadal nie zapadł — teraz zależy od pobrania prawdziwych liczb pamięci przez świeżo działający konektor, a nie od wykresu CPU, który na samym początku wyglądał tak przekonująco.
Wskazówki na następne śledztwo
- Zasób bezczynny w jednym wymiarze może wcale nie być bezczynny. Wykorzystanie CPU i wykorzystanie pamięci opowiadają zupełnie różne historie o tym samym obciążeniu; sprawdź metrykę, której obciążenie faktycznie potrzebuje, a nie tę, którą najłatwiej pobrać jako pierwszą.
- „Dociera do serwera, ale zostaje odrzucone” i „nigdy nie dociera do serwera” to różne awarie z różnymi poprawkami. Czysty komunikat odrzucenia, nawet mylący, często dowodzi, że trudna część — łączność, tożsamość — już działa, a prawdziwa luka jest węższa, niż się na początku wydaje.
- Wybieraj mniejszą, łatwiej odwracalną poprawkę. Poszerzenie listy dostępu systemu produkcyjnego, żeby odpowiedzieć na jedno pytanie, było możliwe, ale przejście przez istniejące, już ograniczone narzędzie uniknęło zmiany, która przeżyłaby pytanie, które ją wywołało.
- Udokumentowana i faktyczna wartość domyślna narzędzia potrafią się po cichu różnić, zwłaszcza w czymkolwiek dystrybuowanym jako obraz kontenera z własnym wbudowanym zachowaniem startowym. Kiedy udokumentowana wartość nie zgadza się z obserwowanym zachowaniem, ufaj obserwacji i weryfikuj, rozmawiając z narzędziem bezpośrednio, z pominięciem tego, co je opakowuje, zanim założysz, że poprawka jest zupełnie gdzie indziej.
- Nie pozwól, żeby presja kosztów skróciła decyzję istotną dla bezpieczeństwa. Najtańsza szybka odpowiedź — „zużycie CPU jest niskie, po prostu zmniejsz” — była kierunkowo błędna dla tego obciążenia. Dojście do właściwej odpowiedzi trwało dłużej, ale alternatywą była wiarygodnie brzmiąca liczba sterująca zmianą w infrastrukturze, gdzie pomyłka kosztuje w zupełnie innej walucie niż pieniądze.