Strona głównaArtykułyCRDT: Typy danych skonfliktowo-szczelne powtarzalne

CRDT: Typy danych skonfliktowo-szczelne powtarzalne

Pomyśl o dwóch osobach edytujących taki sam dokument na lotniskach bez internetu, lub dwóch serwerach w różnych krajach aktualizujących ten sam koszyk podczas utraty sieci. Gdy znowu się połączą, kto wygrywa? Traditionalne systemy odpowiadają na to z użyciem zasłon, timestampów czy centralnego autoru, który arbitrowało konflikty. Typ danych skonfliktowo-szczelny powtarzalny (CRDT) podchodzi do tego problemu w całkowicie inny sposób: jest to struktura danych zaprojektowana tak, aby nigdy nie było potrzeby arbitrowania. Każda kopia może być czytana i aktualizowana niezależnie, równocześnie, a nawet całkowicie oddzielona od każdej innej kopii. Gdy kopyty w końcu ponownie komunikują się, wymieniają swoją stan lub operacje, stosują funkcję łączącą i matematycznie gwarantują, że każda kopia konverguje do tego samego końcowego wartości, niezależnie od porządku, czasu ani liczby powtórzeń aktualizacji. Ta gwarancja nie jest eleganckim trikiem inżynierskim czy heurystyką; jest ona konsekwencją precyzyjnej algebraicznej własności nazywanej semi-lattice ze łącznością, asocjatywnością i idempotentnością. Ten laboratorium przeprowadzi Cię przez te matematyczne podstawy, dwie główne rodziny projektów CRDT, przykład zrobiony, a także rzeczywiste systemy, od edytorsów wspólnej pracy do rozproszonych baz danych, które korzystają z tych konstrukcji, aby utrzymać dane zgodne bez potrzeby przerwania negocjacji.

mysimulator teamZaktualizowano — czerwiec 2026≈ 8 min czytania▶ Otwórz symulację

Sąsiedni Problem: Harmonizacja Jest Drogie

W systemie rozproszonym wielokrotne kopie lub repliky tej samej danych często istnieją na różnych maszynach: telefon, notebook, serwer w jednym centrum danych, serwer w innym. Jeśli dwa kopie są aktualizowane jednocześnie i są odłączone, proste scalanie może wygenerować różne wyniki w zależności od tego, która aktualizacja została zastosowana najpierw. Klasyczne rozwiązania wymagają harmonizacji replik przed przyjęciem pisowni, korzystając z zasłon, protokołów zgody lub jednego lidera, przez który muszą przechodzić wszystkie pisownie. Harmonizacja gwarantuje jednoznaczny porządek aktualizacji, ale to kosztuje dużo: każda pisownia musi czekać na pełną podróż do harmonizatora, a jeśli sieć zostanie podzielona, harmonizator staje się niewykorzystany i pisownie muszą być odrzucone całkowicie. Ta sprzeczność jest zasłoną teorem CAP, który obserwuje, że system rozproszony nie może jednocześnie gwarantować pełnej zgodności i dostępności podczas podziału sieci. CRDTy unikają tej konfliktu dla określonej klasy problemów, przestawiając wymagania dotyczące harmonizacji operacji. Zamiast pytać w czasie rzeczywistym, która aktualizacja została zastosowana najpierw, CRDT jest zaprojektowany tak, że końcowy wynik nie zależy od porządku. Każda replika może przyjmować pisownie natychmiastowo, lokalnie, bez żadnej harmonizacji, bez czekania i bez ryzyka zablokowania przez awarię sieci. Cena za to jest taka, że CRDTy działają tylko dla struktur danych, dla których operacja scalenia może być zdefiniowana z odpowiednimi matematycznymi właściwościami, a zestaw możliwych operacji na danych jest bardziej ograniczony niż w ogólnodostępnym bazie danych z dowolnymi transakcjami. Ale dla wielu rzeczywistych aplikacji, takich jak liczniki, zbiory, rejestratorzy, uporządkowane sekwencje, dokumenty tekstowe, ten ograniczony zestaw jest całkowicie akceptowalny, a odliczeniem jest system, który nadal działa płynnie nawet wtedy, gdy części sieci są niedostępne przez godziny lub dni, pozwalając na automatyczną i poprawną reagencję dalej.

Matematyka: Semiściany łączące

Garancja zbieżności założona pod kątem każdego CRDT opiera się na dziedzinie algebry nazywanej teorią siatki. Semiścian łączący to zestaw możliwych stanów wyposażony w operację binarną scalenia, często oznaczaną jako łączenie (join), która kombinuje dwa dowolne stanow do nowego stanu reprezentującego ich najmniejszy wspólny górnik. Aby funkcja scalenia mogła sprawdzić CRDT, musi spełniać trzy właściwości. Pierwsza to komutatywność: scalenie stanu A z stanem B powoduje taki sam rezultat jak scalenie B z A. To ma znaczenie, ponieważ wiadomości między kopiami mogą dotrzeć w dowolnej kolejności, a końcowy stan nie może zależeć od tego, która kopia przypadkowo otrzymała aktualizację pierwsza. Druga to łączność: scalenie A z B, a następnie scalenie wyniku z C, powoduje taki sam rezultat jak scalenie B z C najpierw, a potem z A. To pozwala kopiom na scalanie aktualizacji w grupach lub otrzymywanie je przez różne ścieżki sieciowe bez zmiany wyniku. Trzecia to idempotencja: scalenie stanu ze sobą, czyli stosowanie tej samej aktualizacji dwukrotnie, pozostawia stan niezmienionym. To ma ogromne znaczenie w rzeczywistych sieciach, gdzie wiadomości mogą być podwójnie wysłane lub ponownie przesłane, a system nie idempotentny by dwukrotnie liczył powtarzające się dostarczenia. Wspólnie te trzy właściwości oznaczają, że niezależnie od tego ile razy, w jakiej kolejności lub ile duplikatów każdej aktualizacji otrzyma każda kopia, ciąg scalen jest zawsze prowadzi do identycznego końcowego stanu. To czasami nazywane jest Silną Zdecydowaną Zgodnością: nie tylko że kopie zdecydują się w końcu na taki sam stan, ale że dowolne dwie kopie, które otrzymały taką samą zestaw aktualizacji, są gwarantowane by być w tym samym stanie teraz, bez okresu czekania i bez możliwości utrzymywanej konfliktu.

Dwa rodziny: oparte na stanie i oparte na operacjach

CRDTy istnieją w dwóch głównych wersjach, które osiągają taką samą gwarancję zbieżności poprzez różne mechanizmy. Pierwsza z nich to CRDT oparty na stanie lub konwentencyjny (CvRDT). W tej implementacji każda replika utrzymuje całe swoje lokalne stan i okresowo przesyła cały ten stan innym replikom, być może poprzez protokół gossip. Gdy replika otrzyma stan innej repliki, łączy je za pomocą operacji join-semilattice opisanej wcześniej, co efektywnie zwraca maksimum połączonego dla każdego śledzonego wartości. Ponieważ łączenie jest komutatywne, asocjatywne i idempotentne, nie ma znaczenia, jak częściowe, stare lub powtarzane mogą być te transmisyje stanu; powtórne lub zaniedbane łączenia skierowane w inny sposób wreszcie zbiegają się do tego samego wyniku. W zamian za to kosztuje to przepustowość: przesyłanie pełnego stanu może być drogie, gdy dane stają się duże, mimo że w praktyce często użycie delt i wersji wektorów jest stosowane do zmniejszenia obciążenia. Druga rodzina to CRDT oparty na operacjach lub komutatywny (CmRDT lub CoRDT). W tym przypadku repliki nie wymieniają pełnego stanu; zamiast tego przekazują indywidualne operacje, które są stosowane lokalnie, takie jak inkrementacja tego licznika lub dodanie tego elementu do tej kolekcji. Każda replika zastosowuje wszystkie otrzymane operacje do swojego własnego kopii lokalnej. Aby to działało poprawnie, operacje muszą być komutatywne, co oznacza, że stosowanie operacji X a następnie Y powoduje taki sam rezultat jak stosowanie Y a następnie X, przynajmniej dla operacji, które mogłyby dotrzeć w nieporząkowanej kolejności. CRDT oparte na operacjach zazwyczaj przyjmują założenie kanału przesyłania wiadomości niestosowanych dokładnie raz lub potrzebują dodatkowej rejestrowania, takiego jak liczby sekwencyjne, aby chronić przed utraconymi lub powtarzonymi komunikatami, ponieważ w przeciwieństwie do podejścia opartego na stanie, ponowne wykonanie tej samej operacji dwa razy może zniszczyć poprawność, chyba że została ona zaprojektowana tak, aby była idempotentna.

Przykład konkretny: Licznik tylko zwiększający się (G-Counter)

Najprostszym CRDT do rozważania jest Licznik tylko zwiększający się, czyli G-Counter, który obsługuje tylko zwiększenia, nigdy nie zmniejszania. Zamiast przechowywać pojedynczą wspólną liczbę, każda replika utrzymuje własny prywatny licznik, indeksowany przez unikalny identyfikator tej repliki. Gdy replika A zwiększy licznik lokalnie, to tylko zwiększa swój slot w swoim miejscu w tablicy; nigdy nie dotyka slotów należących do innych replik. Aby odczytać całą liczbę na dowolnym momencie, replika sumuje każdy slot poznanej przez siebie. Operacja łączenia między tablicami dwóch replik jest zdefiniowana slot po slotu: dla każdego identyfikatora repliki bierze się maksimum zapisanych wartości. Ponieważ branie maksimum jest komutatywne, asocjatywne i idempotentne, łączenie dwóch G-Counterów w dowolnej kolejności, wielokrotnie, zawsze daje tę samą tablicę łączoną, a więc tę samą sumę całkowitą. Rozważmy trzy repliki: A, B i C. Replika A zwiększa swój slot dwukrotnie, osiągając lokalną wartość 2, podczas gdy jest całkowicie poza siecią. Replika B, również poza siecią, zwiększa swój slot trzykrotnie, osiągając 3. Gdy A i B w końcu ponownie się polaczą i łączą, każda bierze maksimum dla każdego slotu: slot repliki A staje się 2, slot repliki B staje się 3, a całość czyta 5, poprawnie reprezentując wszystkie pięć zwiększeń obu replik, nawet jeśli dwie repliki przeprowadziły swoje zwiększenia jednocześnie i nigdy nie komunikowały się podczas tego okresu. Relatywny i równie edukujący przykład to Rejestr o ostatnim pisaniu zwycięskim (Last-Write-Wins Register, LWW-Register), który przechowuje pojedynczą wartość razem z datą i czasem; zasada łączenia prosi o zachowanie wartości, która ma późniejszy timestamp, używając identyfikatora repliki jako rozstrzygacza w przypadku równych timestamps, który jest również komutatywny, asocjatywny i idempotentny.

CRDT w rzeczywistości

CRDT nie są jedynie teoretycznym zainteresowaniem; podpinają się do systemów, których używamy codziennie. Najczęściej cytowanym przykładem jest edytory dokumentów collaborative, a podstawowa idea polegająca na tym, że wielu ludzi może jednocześnie pisać i automatycznie łączyć ich edycje, jest dokładnie problemem, który CRDT był zaprojektowany do rozwiązania. Warto jednak zwrócić uwagę, że wiele znanych edytorów collaborative, w tym Google Docs, historycznie budowało swoje łączne łączenie na innej technice oznaczanej jako Transformation Operacyjna (TO), a nie CRDT, choć nowsze edytory i biblioteki coraz częściej przyjmują CRDT z sekwencyjnymi takimi jak RGA lub Logoot, ponieważ ułatwiają współpracę peer-to-peer bez centralnego serwera. Inne duży adoptery to bazy danych dystrybuowane. Redis oferuje typy danych oparte na CRDT w swojej funkcji geo-dystrybucji aktywna-aktywna, co pozwala wielu klasterów Redis w różnych regionach do przyjmowania zapisów niezależnie i automatycznego łączenia bez konfliktów. Riak, starsza baza danych dystrybuowana klucz-wartość, wprowadziła native obsługę CRDT counterów, zestawów, map i rejestrowych bezpośrednio do swojego modelu danych, co pozwala aplikacjom na osiąganie silnej końcowej zgodności bez ręcznego rozwiązywania konfliktów. Ruch znany jako software local-first również wykorzystuje CRDT w dużej mierze: aplikacje takie jak narzędzia do notowania, tablice sztuczne i mobilne aplikacje offline używają CRDT, aby urządzenie użytkownika pozostawało pełnoaktywnym i funkcjonalnym bez żadnego połączenia z siecią, łącząc się w sposób spójny i poprawny, gdy powróci do połączenia, co może nastąpić po sekundach lub dniach. Wszystkie te przypadki mają tę samą atrakcję: przewidywalne, matematycznie gwarantowane zbieżności bez opóźnienia, skomplikowania ani punktów jednopunktowych niepewności, które wymagają koordynacji centralnej.

Często zadawane pytania

Czy CRDTy gwarantują, że podczas scalania nigdy nie zostanie utraconych danych?

CRDTy gwarantują, że repliki zbiegają się do tego samego stanu, ale ten stan jest określony przez zasady scalania danego typu danych, które mogą świadomie odrzucić informacje. Na przykład Rejestr Last-Write-Wins świadomie odrzuca wartość przeciwniczną w momencie konfliktu dwóch równoczesnych zapisów, zachowując tylko wartość z późniejszym czasem zapisu. Inne CRDTy, takie jak G-Counter, są zaprojektowane tak, aby każdy wpływ operacji był zachowany w końcowym wyniku. Czy dane zostaną odrzucone zależy wyłącznie od wybranego CRDTa dla konkretnego przypadku użycia.

Czy CRDTy obsługują zmniejszanie liczników, nie tylko zwiększanie ich?

Tak. PN-Counter, czyli licznik dodatni-i-ujemny, rozszerza ideę G-Counter'a, dając każdemu replikatorowi możliwość śledzenia dwóch osobnych liczników rosnących tylko dla zwiększeń i zmniejszeń wewnętrznie. Aktualna wartość jest sumą wszystkich slotów zwiększania odejmując sumę wszystkich slotów zmniejszeń. Ponieważ oba wewnętrzne liczniki są rosnące i scalane za pomocą maksimum dla każdego slotu, cała struktura nadal spełnia wymagania semilattice-join potrzebne do automatycznego zbiegania się.

Dlaczego operacyjne CRDTy nie tolerują duplikatów ani zmian kolejności wiadomości tak łatwo jak stanowe?

Stanowe CRDTy łączą całe stany przy użyciu idempotentnej, komutatywnej operacji join, więc otrzymanie tego samego stanu dwukrotnie lub w innej kolejności nie zmienia niczego. Operacyjne CRDTy stosują pojedyncze operacje bezpośrednio, a nie każda operacja jest naturalnie idempotentna, zastosowanie zwiększenia dwukrotnie rzeczywiście zmienia wynik. Dlatego operacyjne systemy zwykle wymagają warstwy nadejściowa, dokładnie-jednokrotne i kausalnie-zagnieżdżone dostawu pod nimi do gwarancji poprawności.

Czy CRDTy mogą zastąpić tradycyjne bazy danych z silną zgodnością?

Nie, rozwiązuje oni inny problem. Systemy wymagające ścisłej globalnej kolejności, takie jak bank, który wymaga, aby salda kont nigdy nie spadły poniżej zera podczas jednoczesnych wypłat, w generalności potrzebują zgodności opartej na koordynacji, a nie CRDTów. CRDTy błyskawicznie się wyróżniają, gdy ważniejsze są dostępność i operacje offline niż ścisła kolejność, a kiedy typ danych, takie jak liczniki, zbiory, sekwencje, tekst, może być modelowany za pomocą dobrze zdefiniowanej komutatywnej scalności.

Co oznacza Silna Zgodność Ostateczna i jak jest ona inna od zwyczajnej zgodności ostatecznej?

Zwyczajna zgodność ostateczna tylko gwarantuje, że repliki będą zgody w pewnym niewy * przyszłym momencie, po którym już nie dojdzie do konfliktujących aktualizacji, ale nie daje żadnej gwarancji, co będzie się działo, jeśli porównamy dwie repliki otrzymane dokładnie taką samą aktualizację teraz. Silna Zgodność Ostateczna, właściwość dostarczana przez CRDTy, gwarantuje, że dowolne dwa repliki otrzymujące identyczną kolekcję aktualizacji są już w tym samym stanie natychmiastowo, bez oczekiwania i bez szansy na odchylenie, dzięki gwarancjom scalenia semilattice-join.

Wypróbuj na żywo

Wszystko powyżej działa bezpośrednio w Twojej przeglądarce — otwórz CRDTs: Merging Data Without a Central Coordinator i zmieniaj parametry podczas działania. Nic nie jest instalowane ani przesyłane na serwer, cały model działa w jednej karcie.

▶ Otwórz symulację CRDTs: Merging Data Without a Central Coordinator

Co znalazłeś?

Dodaj kroki odtworzenia (opcjonalnie)