# Proces jako łańcuch Markowa: stochastyka wydań i utrzymania

> Twój pipeline i flota usług to układ dyskretnych zdarzeń o pewnych szybkościach - czyli łańcuch Markowa. Bierzemy aparat ze stochastycznego modelowania reakcji biochemicznych i przykładamy go do wydań i utrzymania aplikacji. Bez straszenia równaniami.

URL: https://eiac.dev/blog/proces-jako-lancuch-markowa
Filar: SDLC / Policy-as-Code
Data: 2026-07-06
Tagi: sdlc, reliability, stochastic, markov, platform-engineering, dora

---

Do niektórych tekstów wraca się latami. Ja od dawna wracam do [pracy magisterskiej o stochastycznym modelowaniu komórkowych szlaków sygnałowych](https://www.mimuw.edu.pl/~trybik/sci/mgr-inf-trybik.pdf) - 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.

<div class="callout">
<strong>Teza</strong>
<p>Proces wydawania i utrzymania aplikacji jest z natury <em>losowy</em>: 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 <em>łańcuchu zdarzeń o pewnych szybkościach</em>, 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.</p>
</div>

## Ś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](https://en.wikipedia.org/wiki/Continuous-time_Markov_chain)** (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".

<figure>
<svg viewBox="0 0 760 210" role="img" aria-label="Łańcuch Markowa cyklu życia usługi: stany Zdrowa, Zdegradowana, Awaria z przejściami o szybkościach lambda (awarie) i mi (naprawy), oraz łuk pełnego przywrócenia z Awarii do Zdrowej.">
  <g fill="none" stroke="currentColor" stroke-width="1.5">
    <rect x="48" y="80" width="150" height="58" rx="6"/>
    <rect x="305" y="80" width="150" height="58" rx="6"/>
    <rect x="562" y="80" width="150" height="58" rx="6"/>
  </g>
  <g font-family="'Poppins', system-ui, sans-serif" fill="currentColor" font-size="15" text-anchor="middle">
    <text x="123" y="114">Zdrowa</text>
    <text x="380" y="114">Zdegradowana</text>
    <text x="637" y="114">Awaria</text>
  </g>
  <g fill="none" stroke="currentColor" stroke-width="1.5">
    <path d="M198 98 H303"/><path d="M455 98 H560"/>
  </g>
  <g fill="currentColor"><path d="M303 98 l-8 -4 v8 z"/><path d="M560 98 l-8 -4 v8 z"/></g>
  <g fill="none" stroke="var(--color-rust)" stroke-width="1.5">
    <path d="M305 122 H200"/><path d="M562 122 H457"/>
  </g>
  <g fill="var(--color-rust)"><path d="M200 122 l8 -4 v8 z"/><path d="M457 122 l8 -4 v8 z"/></g>
  <path d="M637 138 Q637 190 380 190 Q123 190 123 140" fill="none" stroke="var(--color-rust)" stroke-width="1.5" stroke-dasharray="5 3"/>
  <path d="M123 140 l-4 8 h8 z" fill="var(--color-rust)"/>
  <g font-family="'JetBrains Mono Variable', monospace" font-size="12" text-anchor="middle">
    <text x="250" y="90" fill="currentColor">λ₁</text>
    <text x="507" y="90" fill="currentColor">λ₂</text>
    <text x="250" y="138" fill="var(--color-rust)">μ₁</text>
    <text x="507" y="138" fill="var(--color-rust)">μ₂</text>
    <text x="380" y="184" fill="var(--color-rust)">μ (pełne przywrócenie)</text>
  </g>
</svg>
<figcaption>Cykl życia usługi jako łańcuch zdarzeń: λ (lambda) to szybkości degradacji i awarii, μ (mi) - szybkości naprawy. Dostępność to po prostu prawdopodobieństwo, że w losowo wybranej chwili proces jest w stanie „Zdrowa".</figcaption>
</figure>

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.

<div class="callout">

**Zapis formalny (opcjonalny)**

„Brak pamięci" znaczy, że czas do następnego zdarzenia ma rozkład wykładniczy: $\tau \sim \mathrm{Exp}(a_0)$, gdzie $a_0 = \sum_j a_j$ to suma szybkości wszystkich możliwych w danym stanie przejść. Im większa łączna szybkość, tym krócej średnio czekamy na cokolwiek.

</div>

## 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](https://dora.dev/)** (od zespołu badawczego *DevOps Research and Assessment* - *nie mylić* z unijnym rozporządzeniem [DORA](/blog/dora-w-praktyce-platformy), 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.

<div class="callout">

**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: $a_j(x) = c_j\,h_j(x)$, czyli iloczyn stałej szybkości $c_j$ i liczby dostępnych kombinacji „substratów" $h_j$ w bieżącym stanie $x$. W platformie „substratem" bywa gotowa do wdrożenia zmiana albo wolny worker autoskalera.

</div>

## 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](/blog/platform-engineering-normalizacja-pracy) (*Observe-Orient-Decide-Act*: obserwuj-orientuj się-decyduj-działaj): wygrywa ten, kto szybciej domyka pętlę - czyli ma większe μ.

<div class="callout">

**Zapis formalny (opcjonalny)**

Dla najprostszego naprawialnego układu (dwa stany: `Up`/`Down`, awaria z szybkością $\lambda$, naprawa z $\mu$) dostępność w stanie ustalonym to:

$$ A = \frac{\mu}{\lambda + \mu} = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}} $$

gdzie MTBF (*Mean Time Between Failures*) $= 1/\lambda$ to średni czas między awariami, a MTTR (*Mean Time To Restore*) $= 1/\mu$ to średni czas naprawy. Widać wprost: przy podobnym nakładzie łatwiej ruszyć $\mu$ (naprawę) niż $\lambda$ (rzadką awarię). Pełny opis ewolucji rozkładu po stanach daje [równanie master](https://en.wikipedia.org/wiki/Master_equation), którego ten wzór jest szczególnym, ustalonym przypadkiem.

</div>

## 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](https://en.wikipedia.org/wiki/Gillespie_algorithm)** (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.

```text
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](https://www.prismmodelchecker.org/), 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](/blog/deterministyczny-szkielet-adp) i [policy-as-code](/blog/policy-as-code-dla-zespolow): 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](/blog/policy-as-code-dla-zespolow)) 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"](https://www.mimuw.edu.pl/~trybik/sci/mgr-inf-trybik.pdf), praca magisterska, Uniwersytet Warszawski, 2006.*