Trzy koncepcje awarii, które warto znać
Witaj
Systemy rozproszone ulegają awariom w określonych wzorcach. Gdy poznasz te wzorce, każdy raport po awarii stanie się ćwiczeniem rozpoznawczym, a nie zagadką.
Trzy koncepcje obejmują większość tego, co ma znaczenie w analizie awarii produkcyjnych:
Pojedynczy punkt awarii (SPOF): komponent, którego awaria powoduje zatrzymanie większego systemu. Często ukryty: serwer DNS, od którego wszyscy zależą; certyfikat, względem którego wszystko się odnawia; pojedynczy master bazy danych.
Awaria kaskadowa: awaria jednego komponentu wywołuje awarię innego, który z kolei wywołuje awarię kolejnego. Powolna baza danych powoduje przekroczenia limitów czasu (timeouts) w warstwie API, co prowadzi do ponowień żądań (retries), które obciążają bazę danych jeszcze bardziej, co z kolei powoduje kolejne przekroczenia limitów czasu. Fala uderzeniowa się rozprzestrzenia.
Promień rażenia (blast radius): jak duża część systemu ulega awarii, gdy jeden element zawiedzie. Wybory architektoniczne albo ograniczają, albo nieograniczają ten promień. Punkt pojedynczej awarii (SPOF) ma nieograniczony promień rażenia. Usługa zbudowana z przegródkami (bulkhead) ma ograniczony promień rażenia.
Na koniec tej lekcji będziesz w stanie:
- Identyfikować punkty pojedynczej awarii (SPOF) w architekturze poprzez jej analizę
- Rozpoznawać wzorce awarii kaskadowych: gromada grzmotów (thundering herd), burza ponowień (retry storm), kolejka śmierci (queue of death)
- Czytać rzeczywisty harmonogram zdarzeń i rozdzielać wyzwalacz od ukrytej wady, którą ten wyzwalacz ujawnił
- Pisać bezwinne (blameless) pozycje listy działań, które celują w systemy, a nie w ludzi, obejmujące zapobieganie / wykrywanie / odzyskiwanie
- Rozumować na temat przegródek (bulkheads) i wyłączników obwodu (circuit breakers) jako narzędzi ograniczających promień rażenia
Znajdź pojedynczy punkt awarii
Inspekcja warstwowej architektury
Rozważ małą architekturę webową:
- DNS: api.example.com -> pojedynczy adres IP serwera nazw 203.0.113.10 hostowany przez jednego dostawcę DNS
- CDN: pojedynczy dostawca CDN przed api.example.com
- Ingress: dwa maszyny proxy odwrotnego za balancerem obciążenia
- Backend: sześć replik API w dwóch strefach dostępności (po trzy w każdej strefie)
- Baza danych: jeden primarny + jeden replikata do odczytu, w tej samej strefie dostępności
- Cache: klastr Redis, trzy węzły rozmieszczone w tych samych dwóch strefach dostępności
Pytanie: które komponenty są SPOF? Wskazówka: SPOF to nie zawsze oczywisty typ „pojedynczy serwer”. Klastr trzech maszyn znajdujących się w jednej strefie dostępności jest SPOF w przypadku awarii tej strefy.
Trzy klasyczne wzorce kaskadowe
Awarie przenoszą się przez zależności
Wzorzec 1: Tłum żądających (Thundering herd). Wspólny zasób (pamięć podręczna, blokada, baza danych) ulega awarii lub jest restartowany. Każdy klient, który od niego zależał, ponawia żądania jednocześnie. Ta fala żądań przytłacza to, co wraca do działania; ponowienia gromadzą się szybciej, niż proces odzyskiwania może je obsłużyć; odzyskiwanie nigdy się nie kończy.
Wzorzec 2: Burza ponowień (Retry storm). Usługa dół łańcucha (downstream) spowalnia. Wywołujący z góry łańcucha (upstream), zamiast zgłaszać błąd, ponawiają żądania. Ponowienia mnożą oryginalne obciążenie. Powolna usługa spowalnia jeszcze bardziej, wywołując kolejne ponowienia. Ostatecznie obciążenie przewyższa możliwości nawet zdrowej wersji usługi.
Wzorzec 3: Kolejka śmierci. Kolejka przetwarzania bez mechanizmu hamowania (backpressure) przyjmuje elementy szybciej, niż je przetwarza. Kolejka rośnie bez ograniczeń. Pamięć wyczerpuje się; konsument ulega awarii; jest restartowany; znajduje kolejną, jeszcze większą kolejkę; ulega awarii ponownie.
Wspólny mianownik: małe początkowe zaburzenie wywołuje pętlę sprzężenia zwrotnego dodatniego. Własna reakcja systemu wzmacnia awarię zamiast ją tłumić.
Mechanizmy tłumienia
Wykładnicze wycofywanie z jitterem. Klienci wykonujący ponowne próby czekają coraz dłużej, z losowym przesunięciem. Zapobiega zsynchronizowanym falom ponownych prób.
Wyłącznik obwodu (circuit breaker). Wywołujący śledzi wskaźnik awarii w dół łańcucha. Po przekroczeniu progu, wywołujący przestaje wysyłać żądania na czas okresu chłodzenia i natychmiast zwraca błędy dla własnych żądań. Zapobiega marnowaniu zasobów i pozwala systemowi docelowemu na odzyskanie.
Bulwark (Bulkhead). Izolacja zasobów dla każdego zależności. Basen połączeń A dla bazy danych, osobny basen połączeń B dla pamięci podręcznej. Powolna baza danych nie może wyczerpać wszystkich połączeń; wywołania do pamięci podręcznej kontynuują działanie.
Odrzucanie obciążenia (Load shedding). W przypadku przeciążenia, odrzucaj żądania na krawędzi systemu zamiast przyjmować je i powoli zawodzić. Odpowiedź 429 w 1 ms jest lepsza niż 500 po 30 sekundach.
Ciśnienie zwrotne (Backpressure). Zwolnij producentów, gdy konsumenci nie nadążają. Kolejki stają się ograniczone; nadawcy są blokowani; pierwotne źródło pracy odczuwa opór.
Diagnoza awarii kaskadowej
Warstwa API zespołu ulega awarii podczas rutynowego przełączania bazy danych. Oś czasu:
- 14:00:00 — operator promuje bazę danych standby. Oczekiwana niedostępność: ~10 sekund.
- 14:00:08 — baza danych primary jest niedostępna. Żądania warstwy API zaczynają kończyć się błędami połączenia z bazą danych.
- 14:00:08 — warstwa API wykonuje ponowne próby (domyślna konfiguracja: 5 ponowień, bez opóźnienia, w odstępach 100 ms).
- 14:00:11 — baza standby została promowana i przyjmuje nowe połączenia.
- 14:00:11 — warstwa API otwiera jednocześnie tysiące nowych połączeń z bazą danych (każdy replikant × każde równoległe żądanie × każde ponowienie).
- 14:00:13 — pula połączeń nowej bazy primary jest wyczerpana; nowe połączenia są odrzucane.
- 14:00:13-14:05:00 — replikanty warstwy API wyczerpują pule połączeń, wyrzucają wyjątki, ulegają awarii, restartują i powtarzają ten cykl.
- 14:05:00 — operator ręcznie zatrzymuje ruch warstwy API; baza danych stabilizuje się.
- 14:10:00 — zakończenie stopniowego przywracania ruchu. Łączny czas przestoju: ~10 minut (w porównaniu z oczekiwanym ~10 sekundami).
DNS SERVFAIL: Dwa nakładające się na siebie defekty
Postmortem w realnym kształcie
Poniżej znajduje się zanonimizowana wersja rzeczywistego incydentu. Nazwy dostawców zostały zmienione, adresy IP zanonimizowane; kształt, oś czasu oraz wnioski są prawdziwe.
Podsumowanie
Strona example.com zwracała SERVFAIL ze wszystkich publicznych resolverów DNS przez około 3–4 godziny. Wszystkie pozostałe 46 strefy na tym samym serwerze DNS master nie były dotknięte. Przyczyna główna: dwa nakładające się na siebie defekty.
1. Dostawca A (wtórny dostawca DNS) dodał nowy wewnętrzny adres IP synchronizacji, który nie znajdował się na liście dozwolonych allow-axfr-ips serwera primary.
2. Strefa example.com miała konflikt CNAME naruszający RFC, istniejący od kilku lat (demo.example.com miał zarówno rekord CNAME, jak i rekordy MX/TXT na tej samej etykiecie), co spowodowało, że Dostawca A odrzucił strefę podczas świeżego AXFR.
Oś czasu (UTC)
- ~15:00 — Dostawca A dodaje nowy IP synchronizacji 198.51.100.42 do swojej infrastruktury
- 15:02 — w logach głównego DNS pojawia się pierwszy wpis AXFR-out denied dla 198.51.100.42 (brak alertowania na ten sygnał)
- ~18:00 — osiągnięto okno wygaśnięcia SOA; Dostawca A usuwa strefę example.com z pamięci podręcznej
- ~18:30 — wykryto zewnętrznie błąd SERVFAIL
- ~19:45 — zidentyfikowano przyczynę źródłową
- 20:00 — 198.51.100.42 dodano do allow-axfr-ips; zainicjowano ponowne uruchomienie serwera głównego
- 20:05 — wysłano powiadomienie NOTIFY; zainicjowano AXFR; strefa nadal zwraca SERVFAIL (konflikt CNAME)
- 20:07 — check-zone wykrywa 1 błąd: konflikt CNAME dla demo.example.com
- 20:09 — rekord CNAME zastąpiony rekordem A; kontrola strefy czysta (0 błędów)
- 20:10 — wysłano NOTIFY; zakończono AXFR; Dostawca A zaczyna serwować strefę
- 20:11 — dig @8.8.8.8 example.com A zwraca poprawny adres IP — ROZWIĄZANO
Dlaczego tylko example.com?
Wszystkie 47 stref korzysta z tego samego DNS primary. Blokowanie IP przez AXFR wpłynęło na wszystkie strefy. Jednak tylko example.com miało konflikt CNAME, a tylko example.com wymagało świeżego AXFR w momencie, gdy egzekwowano zakaz. Inne strefy odświeżyły się przed zakazem lub jeszcze nie wymagały tego.
Ukryta wada
Konflikt CNAME w demo.example.com istniał od lat. Działał, ponieważ primary serwowało strefę z bazy danych (było wyrozumiałe wobec naruszeń RFC), a Dostawca A serwował ze starych danych w pamięci podręcznej sprzed wprowadzenia naruszenia. Gdy Dostawca A wyczyścił pamięć podręczną i potrzebował świeżych danych, naruszenie ujawniło się.
Wyzwalacz
Dostawca A cicho dodał nowy adres IP synchronizacji. Lista dozwolonych adresów na serwerze głównym nie zawierała tego adresu. AXFR został odrzucony. Trzy godziny później (po wygaśnięciu SOA) Dostawca A usunął strefę. Ukryta wada ujawniła się, gdy system próbował się odzyskać.
Napisz bezosobowe działania naprawcze
Bezosobowość: Celuj w systemy, nie w ludzi
Bezosobowe działanie naprawcze wskazuje, co system powinien robić inaczej, a nie co dana osoba powinna robić inaczej. „Przeszkolić operatora” to działanie obwiniające. „Dodać automatyczną weryfikację, która wychwyci to przed wdrożeniem”, to działanie bezosobowe.
Dobre bezosobowe działania naprawcze grupują się w trzech wymiarach:
- Prewencja: sprawienie, by zła rzecz była trudniejsza lub niemożliwa
- Wykrywanie: zauważenie tego wcześniej, jeśli do tego dojdzie
- Odzyskiwanie: ograniczenie szkód, gdy do tego dojdzie
Każdy element powinien wskazywać (1) konkretną zmianę systemową, (2) zespół odpowiedzialny oraz (3) wymiar, któremu służy.
Komory, które toną bez statku
Zapożyczone z inżynierii okrętowej
Statki posiadają wodoszczelne przegrody: pionowe ściany dzielące kadłub na komory. Jedna komora może się zalać, nie zatapiając statku; inna może ulec awarii bez wpływu na pozostałe.
Systemy rozproszone pożyczają to samo słowo i ten sam pomysł.
Wzorzec przegród (bulkhead): izolacja zasobów dla każdego zależności. Usługa wywołująca trzy API w dół używa trzech osobnych puli połączeń, trzech osobnych puli wątków i trzech osobnych budżetów ponowień. Wolna lub awaryjna zależność nie może zużyć zasobów przeznaczonych dla pozostałych dwóch.
Bez przegród: jedna wolna zależność wyczerpuje wspólną pulę wątków; wywołania do innych zależności blokują się, czekając na wątki; cała usługa staje się nieodpowiadająca.
Z przegrodami: jedna wolna zależność wyczerpuje własną pulę; wywołania do niej kończą się błędem szybko; wywołania do innych zależności przebiegają normalnie; promień rażenia pozostaje ograniczony do awaryjnej zależności.
Wyłączniki obwodu
Wzór obwodu rozłącznego (circuit breaker): stanowy wrapper wokół zależności w dół łańcucha, który śledzi wskaźnik awarii. Trzy stany:
- Zamknięty (normalny): wywołania przechodzą dalej. Awarie są liczone.
- Otwarty (wyłączony): po przekroczeniu progu awarii (np. 50% awarii w ciągu ostatnich 30 sekund), obwód się otwiera. Wywołania kończą się błędem natychmiast, bez próby dotarcia do zależności. Oszczędza to czas wywołującemu; chroni zależność przed odbieraniem obciążenia, gdy jest niezdrowa.
- Półotwarty (testowy): po okresie chłodzenia, obwód przepuszcza niewielki ułamek wywołań. Jeśli się powiedzą, wraca do stanu zamkniętego (normalnego). Jeśli się nie powiedzą, otwiera się ponownie na kolejny okres chłodzenia.
Kluczowa obserwacja: obwód rozłączny zapobiega marnowaniu wysiłku w okresach, gdy stan jest znany jako niezdrowy, oraz daje zależności szansę na odzyskanie sprawności bez ciągłego obciążenia.
Przegrody (bulkheads) ograniczają promień rażenia. Obwody rozłączne zapobiegają samonakręcaniu się rażenia.
Ogranicz promień rażenia
Twoja usługa API wywołuje cztery usługi w dół łańcucha: Usługę Użytkownika, Usługę Rekomendacji, Usługę Powiadomień oraz zewnętrzne API Płatności. Zespół usłyszał, że „Usługa Rekomendacji bywa nieco niestabilna” i chce upewnić się, że gdy dojdzie do jej awarii, reszta systemu pozostanie zdrowa.
Dziś usługa korzysta z jednego wspólnego puli wątków 200 wątków oraz jednego wspólnego puli połączeń HTTP. Wszystkie cztery zależności konkurują o te zasoby. Nie ma tu obwodu rozłącznika (circuit breakera).
Zaprojektuj przegląd trybów awarii
Synteza
Nauczyłeś się wykrywać pojedyncze punkty awarii (SPOF) przez inspekcję, rozpoznawać wzorce kaskadowych awarii, rozdzielać przyczynę wyzwalającą od ukrytej wady przy czytaniu analizy postmortem, pisać bezwinne pozycje akcji w obszarach zapobiegania / wykrywania / odzyskiwania oraz ograniczać promień rażenia za pomocą grodzi (bulkheads) + obwodów rozłącznych (circuit breakers) + eleganckiego degradowania.
Zastosuj wszystkie pięć.
Twoja drużyna wdraża nową usługę search.example.com, która zależy od trzech usług po stronie odbiorcy: głównego indeksu wyszukiwania (index.example.com), usługi analitycznej (analytics.example.com) oraz usługi rekomendacji (recs.example.com). Drużyna chce, abyś poprowadził „przegląd trybów awarii” przed wdrożeniem.
Kierunek dalszego rozwoju tego kursu
Kierunek dalszego rozwoju tego kursu
Teraz potrafisz rozpoznać SPOF, zidentyfikować kaskadę, produktywnie czytać raporty po awariach, pisać bezwinne elementy akcji oraz ograniczać promień rażenia przez odpowiedni projekt.
Ostatnia lekcja w tym kursie (cs_distsys_observability_and_capacity) uczy, co mierzyć, aby wykryć problem, zanim zrobią to użytkownicy. Sprawdzanie stanu, punkty końcowe wersji, cztery sygnały złote na warstwie proxy oraz jak decyzje dotyczące pojemności awaryjnej wiążą się z obserwowanymi danymi.
Lekcja towarzysząca: geometry_of_failure_modes_and_blast_radius wyprowadza centralność międzywęzłową (który węzeł grafu jest wąskim gardłem) oraz minimalne cięcie (granica promienia rażenia).
Brawo. W drogę.