Systemy rozproszone, chmura i komputery działające jak jeden system

„Exactly once” zwykle nie oznacza, że sieć magicznie dostarczyła wiadomość dokładnie jeden raz

Hasło exactly-once brzmi jak prosta gwarancja transportowa: wiadomość leci raz i nigdy się nie powtarza. W praktyce niezawodne systemy często osiągają efekt „przetwórz raz” inaczej. Jeśli odbiorca wykona pracę, ale potwierdzenie zaginie, nadawca może wysłać wiadomość ponownie. Dlatego potrzebne są stabilne identyfikatory, trwała informacja o postępie i deduplikacja. Google Dataflow opisuje właśnie taki mechanizm: wiadomości mogą być ponawiane przez RPC, ale każdy rekord ma unikalny identyfikator, a odbiorca sprawdza, czy został już zatwierdzony. Google Pub/Sub również ogranicza dokładne gwarancje do konkretnych warunków i wymaga prawidłowego potwierdzania.

Cache może wywołać największy skok ruchu dokładnie w chwili, gdy miał go zmniejszać

Cache odciąża bazę, dopóki popularne dane znajdują się w pamięci podręcznej. Jeśli jednak bardzo gorący wpis wygaśnie, wiele równoczesnych żądań może zobaczyć cache miss i niemal jednocześnie zapytać źródłową bazę. Powstaje cache stampede: warstwa mająca chronić backend nagle otwiera na niego lawinę ruchu. AWS opisuje podobne problemy przy cold startach i opróżnieniu cache całej floty, a praktyczne systemy rozpraszają czasy TTL przez jitter albo grupują równoczesne odświeżenia w jedno żądanie. To jeden z klasycznych przykładów, w których mechanizm optymalizacyjny tworzy własny tryb przeciążenia.

Chmura nie może pokonać prędkości światła: silna spójność między kontynentami kosztuje fizyczną latencję

Centra danych mogą być połączone światłowodem i bardzo szybkimi routerami, ale informacja nadal potrzebuje czasu na pokonanie tysięcy kilometrów. Jeśli globalna baza wymaga potwierdzeń z odległych regionów przed zakończeniem zapisu, do czasu operacji wchodzi round-trip time sieci. Dokumentacja Azure Cosmos DB mówi wprost, że dla kont z wieloma regionami i strong consistency latencja zapisu zależy od dwukrotności RTT między najdalszymi regionami, powiększonej o narzut usługi. Azure domyślnie blokuje nawet konfiguracje strong consistency dla regionów oddalonych o ponad 5000 mil ze względu na wysoką latencję zapisu. Chmura skaluje geografię, ale nie usuwa fizyki.

CRDT pozwala kilku komputerom edytować dane bez uzgadniania każdej zmiany, a mimo to później dojść do tego samego stanu

Większość ludzi oczekuje, że jeśli dwie repliki jednocześnie zmieniają te same dane, muszą najpierw ustalić kolejność. CRDT pokazuje, że dla odpowiednio zaprojektowanych struktur można zrobić inaczej. Każda replika może przyjmować lokalne aktualizacje bez koordynacji. Gdy później otrzyma te same zmiany co pozostałe, deterministyczne reguły scalania doprowadzą wszystkie do identycznego stanu. Nie jest to magiczny sposób na dowolne konflikty — struktura danych musi być zaprojektowana tak, by operacje można było bezpiecznie łączyć. Dzięki temu część aplikacji może działać podczas rozłączenia i synchronizować się później.

Dodanie jednego serwera może wymusić przeniesienie prawie wszystkich kluczy — chyba że użyjemy consistent hashing

Najprostszy sharding przez `hash(key) mod N` działa dobrze, dopóki liczba serwerów się nie zmienia. Gdy z czterech shardów robi się pięć, dla większości kluczy wynik modulo jest inny, więc ogromna część danych zmienia właściciela. Consistent hashing zaprojektowano tak, aby zmiana liczby węzłów powodowała możliwie małe przetasowanie. W klasycznej pracy Kargera i współautorów funkcja zmienia przypisanie minimalnie, gdy zmienia się zakres. Dzięki temu skalowanie klastra nie musi oznaczać masowej migracji całego magazynu.

Dwa serwery mogą mieć różne dane i oba działać poprawnie

Na jednym komputerze oczekujemy zwykle jednej aktualnej wersji danych. W systemie rozproszonym dwie repliki mogą przez pewien czas przechowywać różne wersje tego samego obiektu i nie musi to oznaczać awarii. Jeśli aktualizacja dotarła do jednego centrum danych, ale jeszcze nie do drugiego, oba serwery odpowiadają zgodnie ze stanem, który znają. Niektóre systemy projektuje się celowo tak, aby podczas problemów z siecią nadal przyjmowały operacje, a różnice uzgadniały później. Amazon Dynamo był właśnie takim systemem: dla wysokiej dostępności dopuszczał czasowe rozbieżności i przechowywał wersje danych, które później mogły wymagać scalenia.

Dwa zdarzenia mogą nie mieć żadnej poprawnej odpowiedzi na pytanie „które było pierwsze?”

W życiu codziennym zakładamy jedną oś czasu: jeśli zdarzyły się dwa fakty, jedno było wcześniej. W systemie rozproszonym interesuje nas jednak często nie fizyczna mikrosekunda, lecz przyczynowość. Jeśli dwa serwery wykonały niezależne operacje i nie wymieniły między nimi żadnej informacji, system może nie mieć podstaw, by uznać jedną operację za poprzedzającą drugą. Są współbieżne. Zegary wektorowe rozszerzają ideę logicznego czasu tak, aby potrafić odróżnić sytuację „wersja B wywodzi się z A” od „A i B powstały niezależnie”. Amazon Dynamo wykorzystywał tę własność do wykrywania konfliktujących wersji danych.

Globalna baza Google czasem celowo czeka, aby móc obiecać silniejszą kolejność transakcji

Wydajność baz danych kojarzy się z usuwaniem każdego zbędnego oczekiwania. Spanner zawiera jednak etap nazywany commit wait: po wybraniu znacznika czasu transakcji koordynator czeka, aż dzięki TrueTime będzie wiadomo, że ten timestamp na pewno należy już do przeszłości. Dopiero wtedy rezultat może zostać bezpiecznie ujawniony klientowi. To oczekiwanie nie jest przypadkowym opóźnieniem, lecz ceną za gwarancję external consistency. System świadomie zamienia część czasu odpowiedzi na mocniejszą semantykę: jeśli użytkownik zobaczył zakończenie jednej transakcji przed rozpoczęciem drugiej, porządek bazy ma być z tym zgodny.

Google zbudowało zegar, który odpowiada przedziałem: „czas jest gdzieś pomiędzy tymi wartościami”

Spanner, globalna baza Google, potrzebuje mocnych gwarancji kolejności transakcji pomiędzy centrami danych. Zamiast udawać, że zegary są idealnie zsynchronizowane, Google stworzyło TrueTime — API jawnie pokazujące niepewność czasu. Wywołanie nie zwraca jednej liczby, lecz przedział, w którym z gwarancją ma znajdować się aktualny czas. Spanner wykorzystuje tę niepewność przy nadawaniu znaczników transakcjom. To wyjątkowo elegancki pomysł: niedoskonałość zegara nie zostaje ukryta pod pozorem dokładności, lecz staje się formalną informacją używaną przez algorytm.

Jeden możliwy crash wystarcza, by idealny consensus stał się niemożliwy w całkowicie asynchronicznym modelu

Wynik FLP należy do najbardziej zaskakujących twierdzeń informatyki rozproszonej. Fischer, Lynch i Paterson pokazali, że w całkowicie asynchronicznym systemie deterministyczny protokół consensusu może nie zakończyć działania nawet wtedy, gdy awarii może ulec tylko jeden proces. Nie oznacza to, że praktyczne systemy „nie potrafią osiągać consensusu”. Oznacza, że nie można jednocześnie zachować wszystkich założeń modelu i zagwarantować zakończenia w każdym dopuszczalnym przebiegu. Produkcyjne algorytmy radzą sobie, dodając założenia o czasie, failure detectory, losowość lub inne warunki wykraczające poza czysty model FLP.

Mechanizm ratunkowy może zamienić mały problem w całkowitą awarię przez retry storm

Retry zwiększa szansę powodzenia przy sporadycznych błędach, ale podczas przeciążenia może działać jak dodatnie sprzężenie zwrotne. Serwer zaczyna odrzucać niewielką część żądań, klienci natychmiast je ponawiają, więc ruch rośnie. Większy ruch powoduje więcej błędów, a te generują jeszcze więcej retry. Google SRE opisuje przykład, w którym naiwnie ponawiane żądania stopniowo zwiększają QPS aż backend może się „stopić”, a awaria jednej repliki przerzuca obciążenie na pozostałe i uruchamia kaskadę. Mechanizm zaprojektowany do zwiększania niezawodności może więc obniżyć ją właśnie wtedy, gdy system jest najsłabszy.

Możesz zapisać zmianę, odświeżyć stronę i przez chwilę jej nie zobaczyć

Jeśli aplikacja zapisuje dane do jednej repliki, a chwilę później odczyt trafia do innej repliki, która jeszcze nie dostała aktualizacji, użytkownik może zobaczyć starszy stan. Z jego perspektywy wygląda to absurdalnie: system potwierdził zapis, po czym „zapomniał” własną zmianę. W rzeczywistości dwie operacje obsłużyły różne kopie danych. Dlatego istnieje gwarancja nazywana read-your-writes. Azure Cosmos DB zapewnia ją w ramach session consistency, używając tokenów sesji, które pozwalają odczytowi wymagać co najmniej wersji widzianej po własnym zapisie.

Można zrobić globalny snapshot systemu bez zatrzymywania wszystkich komputerów w tej samej chwili

Jak zapisać „stan całego systemu”, jeśli nie ma globalnego zegara, maszyny pracują bez przerwy, a wiadomości są w drodze? Chandy i Lamport pokazali w 1985 r., że da się zarejestrować spójny globalny snapshot bez jednoczesnego zatrzymania wszystkich procesów. Specjalne markery przesyłane kanałami pozwalają każdemu procesowi zapisać własny stan oraz odpowiednio uchwycić wiadomości będące „w locie”. Wynik nie jest fotografią wykonaną jednym fizycznym aparatem w jednej nanosekundzie. Jest logicznie spójnym przekrojem wykonania, który mógł odpowiadać stanowi systemu.

Nie zawsze da się odróżnić martwy serwer od serwera, który po prostu bardzo długo milczy

Jeśli druga maszyna nie odpowiada, najbardziej naturalne pytanie brzmi: „czy padła?”. Problem w tym, że przez zawodną sieć dokładnie tak samo może wyglądać serwer działający poprawnie, ale odcięty, przeciążony albo bardzo wolny. W systemie bez znanej górnej granicy opóźnienia samo milczenie nie jest dowodem śmierci. Praktyczne systemy używają timeoutów i failure detectorów, ale są to decyzje oparte na założeniach i prawdopodobieństwie, nie absolutna wiedza. To jeden z powodów, dla których awarie częściowe są znacznie trudniejsze niż zwykły crash jednego programu.

Pięć i sześć serwerów może tolerować dokładnie tyle samo awarii consensusowych

Intuicja podpowiada, że sześć serwerów jest bardziej odporne niż pięć. W klastrze opartym na większości nie zawsze. Pięć węzłów potrzebuje trzech głosów, więc może stracić dwa. Sześć potrzebuje czterech, więc również może stracić tylko dwa. Dopiero siedem węzłów podnosi tolerancję do trzech awarii. Dlatego klastry consensusowe często buduje się z nieparzystej liczby głosujących członków. etcd w dokumentacji ostrzega nawet, że dodanie czwartego węzła do zdrowego klastra trzyosobowego zwiększa quorum z dwóch do trzech, nie zwiększając tolerancji na kolejną awarię.

Problem generałów bizantyjskich opisuje serwer, który może okłamywać różnych sąsiadów na różne sposoby

Awaria nie zawsze oznacza, że komputer się wyłączył. W modelu bizantyjskim wadliwy uczestnik może zachowywać się dowolnie: wysyłać sprzeczne wiadomości, udawać poprawne działanie albo przekazywać różne informacje różnym węzłom. Lamport, Shostak i Pease pokazali, jak trudne jest osiągnięcie uzgodnienia w takim świecie. W klasycznym wariancie bez uwierzytelnionych wiadomości, aby tolerować `m` bizantyjskich zdrajców, potrzeba co najmniej `3m+1` uczestników. Nazwa brzmi historycznie, ale problem dotyczy realnego pytania: jak uzgodnić wspólny stan, gdy część maszyn może nie tylko zamilknąć, ale aktywnie wprowadzać pozostałe w błąd.

Raft używa losowości, żeby komputery rzadziej wpadały na ten sam pomysł w tej samej chwili

Losowość kojarzy się z czymś, czego system krytyczny powinien unikać. Raft wykorzystuje ją celowo podczas wyboru lidera. Jeśli wielu followerów jednocześnie uzna, że lider zniknął, mogą w tej samej chwili zostać kandydatami i podzielić głosy. Bez dodatkowego mechanizmu kolejne wybory mogłyby powtarzać ten remis. Raft losuje election timeout z ustalonego przedziału, dzięki czemu zwykle jeden serwer wystartuje wcześniej, zdobędzie większość i zdąży wysłać heartbeat zanim pozostali rozpoczną własne wybory. Odrobina kontrolowanego przypadku pomaga więc systemowi szybciej osiągnąć porządek.

Replikacja może perfekcyjnie skopiować błąd na wszystkie serwery — dlatego nie jest backupem

Trzy repliki chronią przed utratą pojedynczej maszyny, ale nie przed każdym rodzajem utraty danych. Jeśli administrator przypadkiem usunie tabelę albo aplikacja poprawnie wykona fatalną aktualizację, system replikacji może wiernie rozesłać tę zmianę do wszystkich kopii. Microsoft w dokumentacji niezawodności podkreśla wprost: replication isn't the same as backup. Replikacja synchronizuje zmiany i zwykle nie zachowuje starych wersji, natomiast backup daje historyczny punkt odtworzenia. Redundancja zwiększa dostępność; kopia z przeszłości chroni przed poprawnie zreplikowaną katastrofą logiczną.

Retry po błędzie sieci może wykonać poprawną operację dwa razy

Ponawianie żądań jest podstawowym narzędziem odporności: jeśli połączenie chwilowo nie działa, spróbuj ponownie. Problem zaczyna się przy operacjach ze skutkiem ubocznym. Pierwsza próba mogła się udać, a zginęła tylko odpowiedź. Druga próba może wtedy utworzyć drugi zasób, drugie zamówienie albo drugi zapis. Stripe chroni przed tym przez idempotency keys: klient nadaje operacji unikalny klucz, a powtórzenie tego samego żądania z tym kluczem otrzymuje zapisany rezultat zamiast wykonywać skutek ponownie. AWS stosuje podobną ideę w projektowaniu idempotentnych API.

Split brain może stworzyć dwa działające systemy, z których każdy uważa się za prawdziwy

Najgorsza partycja sieciowa nie musi wyłączyć serwerów. Może zostawić dwie grupy maszyn działające normalnie, ale bez kontaktu ze sobą. Jeśli architektura pozwoli obu stronom przyjmować sprzeczne zapisy, każda tworzy własną historię. To nazywa się split brain. Po odzyskaniu łączności problem nie polega na prostym „zsynchronizuj nowszą kopię”, bo obie strony mogły wykonać poprawne, niezależne operacje. Systemy consensusowe używają quorum właśnie po to, by zapobiegać dwóm aktywnym mózgom: strona mniejszościowa rezygnuje z prawa do decyzji nawet wtedy, gdy jej maszyny są zdrowe.

Sprawny serwer może celowo odmówić zapisu, bo znalazł się po niewłaściwej stronie podziału sieci

Wyobraźmy sobie pięć serwerów tworzących jeden klaster. Sieć dzieli je na grupę trzech i grupę dwóch. Wszystkie maszyny są fizycznie sprawne, ale system consensusowy nie może pozwolić obu grupom niezależnie zatwierdzać zmian, bo powstałyby dwie sprzeczne historie. Dlatego strona mająca większość może działać dalej, a mniejszość przestaje zatwierdzać zapisy. etcd opisuje to wprost: przy partycji część z większością pozostaje dostępnym klastrem, a część mniejszościowa jest niedostępna. Odmowa pracy nie jest więc awarią algorytmu — jest mechanizmem chroniącym dane przed split-brain.

Systemy dodają losowe opóźnienie do retry właśnie po to, żeby działały bardziej przewidywalnie

Exponential backoff wydłuża przerwy między kolejnymi próbami, ale sam nie wystarcza, jeśli tysiące klientów zaczęło retry w tej samej chwili. Wszyscy mogą czekać 1 sekundę, potem 2, potem 4 — i za każdym razem ponownie uderzać w serwer jednocześnie. Jitter dodaje losową składową do opóźnienia, rozpraszając klientów w czasie. Google SRE zaleca randomized exponential backoff, a AWS podkreśla jitter jako sposób uniknięcia zsynchronizowanych fal. Kontrolowany chaos na poziomie pojedynczych klientów tworzy bardziej równomierne i stabilne zachowanie całej populacji.

Timeout nie oznacza, że operacja się nie wykonała

Klient wysyła żądanie do serwera i czeka na odpowiedź. Jeśli mija ustalony czas, otrzymuje timeout. Intuicyjnie brzmi to jak „operacja się nie udała”, ale system rozproszony nie daje takiej pewności. Serwer mógł w ogóle nie dostać żądania, mógł je właśnie wykonywać albo mógł już zakończyć pracę, a zagubiła się tylko odpowiedź. Dlatego po timeoutcie klient często wie jedynie, że nie zna wyniku. Ta niepewność jest powodem, dla którego bezpieczne ponawianie żądań wymaga idempotencji, identyfikatorów operacji albo późniejszego sprawdzania stanu.

Transakcja może utknąć w stanie: „wszyscy są gotowi, ale nikt nie wie, czy wolno zakończyć”

Two-Phase Commit pozwala kilku systemom wspólnie zatwierdzić albo anulować transakcję. Najpierw koordynator pyta uczestników, czy są gotowi; jeśli wszyscy odpowiedzą pozytywnie, wysyła ostateczne COMMIT. Problem pojawia się, gdy uczestnik zapisał stan „prepared”, ale przed poznaniem decyzji koordynator przestał być dostępny. Uczestnik nie może samodzielnie zatwierdzić, bo koordynator mógł zdecydować ABORT, ani anulować, bo mógł już zdecydować COMMIT. Klasyczny Two-Phase Commit jest więc protokołem blokującym przy niektórych awariach koordynatora.

Trzy serwery mogą nie dawać prawdziwej redundancji, jeśli wszystkie zależą od tego samego punktu awarii

Redundancja nie polega wyłącznie na policzeniu kopii. Trzy serwery w tej samej szafie mogą dzielić zasilanie, chłodzenie, przełącznik i łącze. Jedna awaria wspólnego elementu może wyłączyć wszystkie naraz. Dlatego chmury organizują infrastrukturę w failure domains, takie jak Availability Zones. AWS opisuje strefy jako fizycznie oddzielone lokalizacje z odrębną i redundantną infrastrukturą zasilania oraz sieci, projektowane tak, aby ograniczać skorelowane awarie. Prawdziwa redundancja wymaga niezależności przyczyn awarii, nie tylko wielu kopii.

Twierdzenie CAP nie mówi po prostu: „wybierz dowolne dwie z trzech rzeczy”

CAP jest jednym z najczęściej upraszczanych twierdzeń w informatyce. Popularne hasło mówi, że system rozproszony wybiera dwie z trzech cech: consistency, availability i partition tolerance. Formalny wynik Gilberta i Lynch jest bardziej precyzyjny. Gdy dochodzi do partycji sieciowej, system nie może jednocześnie zagwarantować atomowej spójności oraz tego, że każde żądanie skierowane do nieuszkodzonego węzła otrzyma odpowiedź. Partycja nie jest więc zwykłą trzecią funkcją wybieraną z menu. To warunek awarii, w którym ujawnia się konflikt pomiędzy określonym rodzajem spójności i dostępnością.

Usunięty rekord może „zmartwychwstać”, jeśli jedna replika nie dowie się o jego usunięciu

W rozproszonej bazie kasowanie danych też jest informacją, którą trzeba zreplikować. Cassandra nie może po prostu natychmiast wymazać rekordu z jednej maszyny, bo inna replika może być offline i nadal przechowywać starą wartość. Zamiast tego zapisuje tombstone — znacznik mówiący, że dane zostały usunięte. Jeżeli taki znacznik zostanie usunięty z działających replik, zanim długo odłączony węzeł wróci i zostanie naprawiony, stara kopia może zostać potraktowana jak żywa wersja i ponownie rozpowszechniona. Dokumentacja Cassandry nazywa taki rekord zombie.

W systemie rozproszonym nie ma jednego oczywistego „teraz”

Każdy komputer ma własny zegar, a zegary nie są idealne. Różnią się ustawieniem, dryfują i są okresowo korygowane. Jednocześnie wiadomości między maszynami potrzebują czasu na dotarcie. Dlatego spojrzenie na dwa znaczniki czasu nie zawsze wystarcza, aby bezpiecznie stwierdzić, które zdarzenie w systemie naprawdę było pierwsze. Leslie Lamport zwrócił uwagę, że w systemie rozproszonym bardziej fundamentalna od jednej globalnej osi czasu jest relacja przyczynowa: wysłanie wiadomości musi poprzedzać jej odebranie, ale dwa niezależne zdarzenia mogą nie mieć naturalnego porządku. To zmienia sposób myślenia o logach, transakcjach i debugowaniu.

Żeby uratować dostępność, serwer może celowo odrzucać część poprawnych żądań

Brzmi paradoksalnie: jeśli system ma być dostępny, dlaczego miałby sam zwracać błędy? Przy przeciążeniu próba obsłużenia każdego żądania może doprowadzić do wyczerpania pamięci, wątków i kolejek, a w rezultacie do awarii obsługi wszystkich użytkowników. Load shedding polega na wczesnym i tanim odrzuceniu części ruchu, aby zachować zdolność wykonywania reszty pracy. Google SRE wprost rekomenduje odrzucanie żądań, gdy serwer zbliża się do przeciążenia; przykładem jest zwracanie HTTP 503 po przekroczeniu ustalonej liczby operacji w toku.

Zegar Lamporta nie mierzy godziny — mierzy logiczną kolejność

Nazwa „zegar logiczny” może sugerować sztuczny zegarek, ale jego zadanie jest inne. Zegar Lamporta przypisuje zdarzeniom rosnące liczby tak, aby jeśli zdarzenie A mogło przyczynowo poprzedzać B, to liczba A była mniejsza od liczby B. Nie próbuje powiedzieć, ile sekund upłynęło ani która jest godzina. Dzięki prostemu licznikowi i przekazywaniu jego wartości w wiadomościach procesy mogą zachować kluczową informację o kolejności bez wspólnego fizycznego zegara. To przykład rozwiązania, które działa właśnie dlatego, że rezygnuje z odpowiedzi na trudniejsze pytanie i przechowuje tylko informację naprawdę potrzebną algorytmowi.