Two-Phase Commit (2PC)
Two-Phase Commit (ang. Two-Phase Commit, skrót 2PC) – protokół koordynacji transakcji w systemach rozproszonych, który umożliwia atomową i spójną aktualizację danych przechowywanych w wielu bazach danych lub innych zasobach.
Historia
Protokoły koordynacji transakcji zostały opracowane w latach 1970–1980 w ramach badań nad systemami rozproszonymi. Pierwsze formalne opracowanie Two-Phase Commit pojawiło się w pracach Jima Graya i Andreasza Reutera w 1978 roku. Od tego czasu protokół jest standardowym elementem systemów DBMS takich jak Oracle, Microsoft SQL Server czy MySQL.
Idea i zasada działania
Podstawową ideą 2PC jest podział procesu zatwierdzania transakcji na dwie odrębne fazy, które odbywają się pod kontrolą jednego węzła – koordynatora. Koordynator komunikuje się z uczestniczącymi w transakcji uczestnikami (ang. participants), z których każdy zarządza własną lokalną transakcją.
Faza 1 – prepare (przygotowanie)
- Koordynator wysyła do wszystkich uczestników żądanie
prepare. - Każdy uczestnik wykonuje wszystkie operacje transakcji w trybie tymczasowym i sprawdza, czy może je zatwierdzić (brak konfliktów, dostępność zasobów, spełnienie ograniczeń integralności).
- Uczestnik odsyła koordynatorowi jedną z dwóch odpowiedzi:
vote‑commit– gotowy do zatwierdzenia.vote‑abort– niezdolny do zatwierdzenia, wymusza anulowanie.
Faza 2 – commit/abort (zatwierdzenie lub anulowanie)
- Jeżeli koordynator otrzymał
vote‑commitod wszystkich uczestników, rozsyła komunikatglobal‑commit. Wtedy każdy uczestnik trwale zapisuje zmiany i zwalnia zasoby. - Jeżeli choć jeden uczestnik odpowiedział
vote‑abortlub nie odpowiedział w określonym czasie, koordynator wysyłaglobal‑abort. Wszyscy uczestnicy wycofują zmiany, przywracając stan sprzed transakcji.
Właściwości
- Atomowość – transakcja jest albo w pełni zatwierdzona, albo całkowicie wycofana.
- Spójność – po zakończeniu transakcji wszystkie bazy danych spełniają reguły integralności.
- Izolacja – operacje wewnątrz transakcji nie wpływają na inne transakcje do momentu zatwierdzenia.
- Trwałość – po zatwierdzeniu zmiany pozostają zapisane pomimo awarii systemu.
Ograniczenia i problemy
Pomimo swojej prostoty, Two-Phase Commit ma kilka istotnych ograniczeń:
- Blokowanie zasobów – w fazie przygotowania każdy uczestnik trzyma zablokowane zasoby, co może prowadzić do zakleszczeń.
- Jednopunkt awarii – koordynator jest krytycznym węzłem; jego awaria w trakcie protokołu może spowodować, że uczestnicy pozostaną w nieokreślonym stanie (uncertain state).
- Słaba odporność na awarie sieci – utrata komunikacji pomiędzy koordynatorem a uczestnikiem wymaga mechanizmów timeout i ponawiania, co wydłuża czas odpowiedzi.
- Skalowalność – liczba wiadomości rośnie liniowo wraz z liczbą uczestników, co ogranicza wydajność w dużych klastrach.
Rozwiązania i alternatywy
Aby złagodzić wady 2PC, opracowano szereg rozszerzeń i alternatywnych protokołów:
- Three-Phase Commit (3PC) – wprowadza dodatkową fazę prepare‑to‑commit, która eliminuje problemy z blokowaniem w przypadku awarii koordynatora.
- Protokół Paxos i jego wariant Raft – zapewniają konsensus w systemach rozproszonych i mogą służyć jako podstawa dla transakcji wielowersyjnych.
- Architektury SAG (Saga) – dzielą długotrwałą transakcję na serię krótkich działań z kompensacyjnymi operacjami odwracającymi, co eliminuje potrzebę blokowania zasobów.
- Transactional Outbox i Change Data Capture – techniki asynchronicznego utrzymywania spójności danych przy użyciu kolejek wiadomości.
Zastosowania
Protokoł 2PC jest wykorzystywany w wielu rodzajach systemów:
- Systemy bankowe – zapewniają spójność operacji finansowych rozproszonych pomiędzy różnymi serwerami.
- Platformy e‑commerce – koordynują zamówienia, płatności i aktualizację stanów magazynowych.
- Mikrousługi – w architekturach microservices z silnym modelem transakcyjnym.
- Systemy Hadoop i Apache Spark – przy zarządzaniu transakcjami w warstwie HDFS.
Przykład implementacji (pseudo‑kod)
// Koordynator
sendPrepareToAll();
if (allVotesCommit()) {
sendGlobalCommit();
} else {
sendGlobalAbort();
}
// Uczestnik
onPrepare() {
if (canCommit()) {
voteCommit();
awaitDecision(); // commit lub abort
if (decision == COMMIT) {
persistChanges();
} else {
rollback();
}
} else {
voteAbort();
}
}