Do niektórych tekstów wraca się latami. Ja od dawna wracam do pracy magisterskiej o stochastycznym modelowaniu komórkowych szlaków sygnałowych - biochemia, enzymy, fosforylacja białek, teoretycznie zupełnie nie moja bajka. A jednak sporo procesów, które projektuję - wydania, utrzymanie, reakcję na incydenty - układam na jej fundamentach. Bo idea pod spodem jest ta sama: masz zbiór dyskretnych bytów, które zmieniają stan przez zdarzenia zachodzące z pewną szybkością. W biologii to molekuły i reakcje; w platformie to instancje usług, wdrożenia, żądania i awarie. Autor pisał wprost o paradygmacie „molekuła jako proces” - wystarczy podmienić molekułę na usługę, a cały ten sposób myślenia zaczyna opisywać utrzymanie i wydawanie aplikacji.
Ten artykuł bierze więc pomysł z tamtej pracy i przykłada go do procesów platformowych w duchu everything-as-code (EIAC - „wszystko jako kod”). Piszę go tak, żeby dało się go przeczytać bez ani jednego wzoru: cała argumentacja jest w słowach, a matematyka siedzi w wydzielonych, opcjonalnych ramkach „zapis formalny” - dla tych, którzy chcą zobaczyć, że to nie metafora, tylko dobrze opisana teoria. Jeśli wolisz, możesz te ramki pominąć i nic nie stracisz z głównej myśli.
Proces wydawania i utrzymania aplikacji jest z natury losowy: to układ dyskretnych zdarzeń (wdrożenie, awaria, przywrócenie) o mierzalnych szybkościach. Uśredniona, „deterministyczna" intuicja („zwykle działa") ukrywa ryzyko ogona - rzadkich, kosztownych zdarzeń - a to one generują większość kosztu. Kiedy zaczniesz myśleć o procesie jak o łańcuchu zdarzeń o pewnych szybkościach, a o swoich metrykach jak o tych szybkościach, przenosisz decyzje z przeczucia na rozkład prawdopodobieństwa. Everything-as-code i obserwowalność dostarczają stanów i szybkości jako danych, więc to podejście przestaje być akademickie, a staje się narzędziem.
Średnia kłamie: makro kontra mikro
W modelowaniu reakcji chemicznych klasyczne, „gładkie” równania (ciągłe i deterministyczne - dające zawsze ten sam wynik) działają świetnie w skali makro, gdzie molekuł są miliardy i fluktuacje się uśredniają. Ale w skali mikro, gdzie kluczowych cząsteczek są dziesiątki, uśrednianie kłamie: liczy się konkretna, losowa trajektoria. Dlatego do małych, ważnych układów stosuje się opis stochastyczny (czyli losowy - oparty na prawdopodobieństwie, a nie na jednej z góry ustalonej wartości).
Dokładnie ten sam podział jest w platformie. „Nasza dostępność to 99,9% średniorocznie” to opis makro - i bywa prawdziwy, a zarazem bezużyteczny. Bo to nie średnia Cię boli, tylko ten jeden apply w piątek o 17:00, który zbiegł się z awarią bazy i wywrócił produkcję na 40 minut. Zdarzenia rzadkie i skorelowane - ogon rozkładu - są z natury losowe, a to one dominują koszt incydentów. Deterministyczna średnia je zamiata pod dywan. Jeśli chcesz o nich rozmawiać serio, potrzebujesz języka rozkładów prawdopodobieństwa, nie jednej liczby „przeciętnie”.
Twój system jako łańcuch zdarzeń
Standardowym narzędziem do opisu takich układów jest łańcuch Markowa z czasem ciągłym (ang. continuous-time Markov chain, w skrócie CTMC). Brzmi poważnie, ale to po prostu formalna nazwa na „system, który przeskakuje między dyskretnymi stanami, a każde przejście ma swoją intensywność”. Ma trzy składniki, które w platformie mają dosłowne odpowiedniki:
- Stany - dyskretne sytuacje, w których może być usługa lub wdrożenie. Np.
Zdrowa → Zdegradowana → Awaria → Przywracanie. To nie metafora: to realne stany, które i tak widzisz w obserwowalności. - Przejścia - możliwe zdarzenia zmieniające stan (degradacja, awaria, samonaprawa, przywrócenie, wdrożenie, wycofanie).
- Szybkości (ang. rate) - jak intensywnie zachodzi dane przejście. W chemii to stała szybkości reakcji; tu - częstość awarii, tempo naprawy, częstotliwość wdrożeń. Umownie oznacza się je greckimi literami: λ (lambda) dla „psucia się” i μ (mi) dla „naprawiania”.
Najważniejsza cecha takiego łańcucha to brak pamięci: to, co stanie się dalej, zależy tylko od bieżącego stanu, a nie od tego, jak do niego doszliśmy. To potężne uproszczenie - i zaraz wrócę do tego, gdzie ono kłamie.
Zapis formalny (opcjonalny)
„Brak pamięci” znaczy, że czas do następnego zdarzenia ma rozkład wykładniczy:
Twoje metryki to po prostu te szybkości
I tu jest pointa dla praktyka, którą można wypowiedzieć bez jednego symbolu: metryki, którymi już mierzysz dostarczanie, to zmierzone szybkości tego łańcucha. Nie musisz ich zgadywać - masz je w danych.
Weźmy cztery klasyczne metryki DORA (od zespołu badawczego DevOps Research and Assessment - nie mylić z unijnym rozporządzeniem DORA, czyli Digital Operational Resilience Act; ten sam akronim, zupełnie co innego). Mapują się wprost na elementy łańcucha:
| Metryka DORA | Rola w łańcuchu zdarzeń |
|---|---|
| Częstość wdrożeń (deployment frequency) | szybkość przejść „wdrożenie” - tempo, w jakim wprowadzasz zmianę stanu |
| Odsetek nieudanych zmian (change failure rate) | prawdopodobieństwo, że przejście „wdrożenie” prowadzi do stanu Zdegradowana/Awaria |
| Czas przywrócenia usługi (MTTR - Mean Time To Restore, średni czas do naprawy) | odwrotność szybkości naprawy μ: krótki MTTR = duże μ |
| Czas realizacji zmiany (lead time for changes) | jak długo zmiana czeka, zanim w ogóle wejdzie do układu |
Innymi słowy: ponieważ w EIAC wszystko jest kodem i przechodzi przez obserwowalną platformę, każdy stan i każde przejście zostawiają ślad. Dostajesz λ i μ jako dane, nie jako przeczucie.
Zapis formalny (opcjonalny)
W modelu stochastycznym każdemu przejściu przypisuje się propensity (skłonność) - miarę tego, jak „chętnie” zdarzenie zachodzi tu i teraz:
Dlaczego szybciej naprawiać, niż rzadziej się psuć
Najbardziej praktyczny wniosek z tego całego aparatu też da się powiedzieć jednym zdaniem: dostępność podniesiesz szybciej, skracając czas naprawy, niż polując na kolejną rzadką przyczynę awarii. Skrócenie przywracania z 40 do 10 minut robi dla dostępności więcej niż heroiczne tropienie jednego usterki-widma raz na kwartał.
To nie jest opinia - to wprost wynika z tego, jak liczy się dostępność. Reliability engineering (inżynieria niezawodności) to po prostu stosowana teoria procesów losowych, a znany wszystkim wzór na dostępność jest stanem ustalonym prostego łańcucha Markowa. To samo mówi pętla OODA z wprowadzenia (Observe-Orient-Decide-Act: obserwuj-orientuj się-decyduj-działaj): wygrywa ten, kto szybciej domyka pętlę - czyli ma większe μ.
Zapis formalny (opcjonalny)
Dla najprostszego naprawialnego układu (dwa stany: Up/Down, awaria z szybkością
gdzie MTBF (Mean Time Between Failures)
Przećwicz okno wdrożeniowe, zanim je otworzysz
Prosty wzór wystarcza dla dwóch-trzech stanów. Przy realnej sieci przejść (wiele usług, zależności, okna wdrożeniowe) nie liczy się tego na kartce - symuluje się. Puszczasz tysiące losowych „przebiegów” swojego procesu i patrzysz na rozkład wyników. To zwykłe Monte Carlo (metoda szacowania przez wielokrotne losowanie - nazwa od kasyna), tyle że zastosowane do Twojego pipeline’u.
Kanonicznym sposobem takiego losowania trajektorii jest algorytm Gillespiego (ang. Stochastic Simulation Algorithm, SSA). W środku jest prosta pętla: policz, jak szybko może się wydarzyć cokolwiek, wylosuj kiedy zajdzie następne zdarzenie i które to będzie, zastosuj je, powtarzaj.
t = 0
while t < T_okna:
a0 = Σ aᵢ # jak szybko może zajść cokolwiek (suma szybkości)
τ = Exp(a0) # kiedy zajdzie następne zdarzenie (losowy czas)
j = wylosuj zdarzenie ∝ aⱼ # które zdarzenie zaszło (proporcjonalnie do szybkości)
zastosuj_zdarzenie(j) # zmień stan (wdrożenie, awaria, przywrócenie...)
t += τ
Mając zmierzone szybkości, odpowiadasz na pytania, których średnia nie tknie: jakie jest prawdopodobieństwo dwóch równoczesnych awarii w oknie wdrożeniowym?, ile wdrożeń kanarkowych trzeba, żeby ryzyko regresji spadło poniżej progu?, czy autoskaler nadąży za szpilką ruchu? (to ostatnie to klasyczny model kolejkowy M/M/c - w notacji Kendalla: losowe, wykładnicze napływy / wykładnicza obsługa / c równoległych serwerów - też odmiana łańcucha Markowa). Zamiast wdrażać „na czuja w piątek”, przećwiczasz okno wdrożeniowe, zanim je otworzysz.
Cel poziomu usług jako własność, nie życzenie
Praca opisuje też formalne reprezentacje takich łańcuchów - stochastyczne sieci Petriego, algebry procesów PEPA (Performance Evaluation Process Algebra, formalny język do opisu wydajności systemów) - oraz weryfikację modelową: automatyczne sprawdzanie, czy model spełnia zadaną własność. Robi się to np. weryfikatorem probabilistycznym PRISM, a własności zapisuje w logice CSL (Continuous Stochastic Logic, stochastyczna logika temporalna - język do wyrażania zdań typu „z prawdopodobieństwem co najmniej X w czasie Y…”). Brzmi bardzo akademicko, dopóki nie zauważysz, że dokładnie o to chodzi w SLO.
SLO (ang. Service Level Objective, cel poziomu usług - mierzalny cel niezawodności, np. „99,9% udanych żądań w miesiącu”) to bliski krewny SLA (ang. Service Level Agreement, umowa o poziomie usług - zobowiązanie kontraktowe wobec klienta). Różnica jest praktyczna: SLA to obietnica w umowie, SLO to wewnętrzny cel, który faktycznie pilnujesz.
I teraz clou: zdanie „z prawdopodobieństwem co najmniej 0,999 system wraca do stanu Zdrowa w ciągu 5 minut” to nie slajd - to własność, którą można zweryfikować względem modelu. To ten sam ruch, co deterministyczny szkielet platformy agentowej i policy-as-code: przestajemy mieć nadzieję, że system się zachowa, a zaczynamy tworzyć specyfikację i sprawdzać jego własności. Policy-as-code egzekwuje reguły na pojedynczym apply; weryfikacja modelowa pyta o zachowanie całej dynamiki procesu w czasie.
Uczciwie: gdzie model kłamie
Nie chcę Ci tego wciskać, przemilczając haczyki - a te są istotne. Założenie o braku pamięci w rzeczywistości jest nieprawdziwe: model udaje, że każde zdarzenie zaczyna z czystą kartą, a rzeczywistość ma pamięć. Nieudane wdrożenie zwiększa szansę na kolejne nieudane. Po nocnym incydencie zespół jest zmęczony, więc następna naprawa idzie wolniej - μ spada dokładnie wtedy, gdy najbardziej go potrzebujesz. Awarie chodzą stadami, bo jedna pociąga za sobą następne. Do tego czasy naprawy rzadko układają się w gładki, „podręcznikowy” rozkład. A rzadkie zdarzenia - te najważniejsze - są z natury trudne do oszacowania, bo masz ich w danych mało.
Wniosek nie jest „odrzuć model”, tylko „używaj go jak modelu”: to narzędzie do myślenia i do zadawania właściwych pytań, nie wyrocznia. Prosty łańcuch zdarzeń, którego szybkości realnie zmierzyłeś, powie Ci o Twojej niezawodności więcej niż najładniejszy slajd o dostępności oparty na średniej. A tam, gdzie założenia bolą - świadomie sięgasz po bogatszy opis.
Jak to spina EIAC
Cała ta teoria staje się operacyjna dopiero, gdy proces jest policzalny - a to właśnie robi everything-as-code:
- Stany i przejścia jawne - skoro infrastruktura, wdrożenia i polityki są kodem, cykl życia usługi (wdrożenie, degradacja, wycofanie, przywrócenie) jest zdefiniowany wprost, nie domyślany. Masz zbiór możliwych stanów za darmo.
- Szybkości jako dane - obserwowalność i metryki dostarczania dają zmierzone
λiμ. Model karmisz faktami, nie zgadywaniem. - Własności jako kod - cel poziomu usług (SLO) zapisujesz jako sprawdzalną własność, a guardrails (policy-as-code) trzymają układ w tych stanach, które uznałeś za bezpieczne.
To domyka wątek, który ciągnie się przez całą serię: przewagą nie jest heroizm przy incydencie, lecz system produkcyjny, który zamienia losowość w policzalne ryzyko. Ten sposób myślenia daje temu język.
Podsumowanie
Wydawanie i utrzymanie aplikacji to nie ciąg deterministycznych kroków, lecz proces losowy - układ dyskretnych zdarzeń o mierzalnych szybkościach, czyli łańcuch Markowa. Deterministyczna średnia (dostępność „zwykle działa”) ukrywa ogon, który generuje koszt; język rozkładów prawdopodobieństwa go odsłania. Metryki DORA to zmierzone szybkości tego łańcucha, dostępność to jego stan ustalony (i rośnie szybciej przez skracanie naprawy μ niż przez ściganie awarii λ), symulacja pozwala przećwiczyć okno wdrożeniowe, a weryfikacja modelowa zamienia cel poziomu usług z życzenia w sprawdzalną własność. Model ma granice - założenie braku pamięci kłamie - ale prosty, zmierzony model bije przeczucie. A everything-as-code jest tym, co czyni proces policzalnym: podmieniasz „molekułę jako proces” na „usługę jako proces” i cały ten sposób myślenia rusza. Weź jeden swój pipeline, wypisz jego stany i zmierz dwie szybkości - jak często się psuje i jak szybko wraca do zdrowia. Reszta, jeśli zechcesz po nią sięgnąć, to już matematyka w opcjonalnych ramkach powyżej. :)
Inspiracja: Mikołaj Rybiński, „Stochastyczne modele komórkowych szlaków sygnałowych”, praca magisterska, Uniwersytet Warszawski, 2006.