모든 이야기

2단계 커밋: 여러 저장소의 변경을 함께 확정하는 절차

2단계 커밋은 여러 참가자가 관련된 트랜잭션을 모두 커밋하거나 모두 중단하도록 결정하는 원자 커밋 프로토콜이다. 한 저장소에서 출금하고 다른 저장소에서 입금하는 작업처럼 여러 곳의 결과가 함께 맞아야 할 때 사용한다. 참가자와 결정을 조정하는 코디네이터가 있다.

prepare 투표 뒤 영속 결정이 전달되는 2단계 커밋과 준비 상태의 대기

준비 완료 참가자는 다른 곳의 결정과 모순되지 않도록 최종 결정을 확인해야 한다.

첫 단계는 확정이 아니라 준비다

코디네이터는 참가자들에게 prepare를 보낸다. 각 참가자는 로컬 변경을 검증하고, 이후 커밋 결정을 수행할 수 있는 상태를 영속 기록한 뒤 yes 또는 no로 투표한다. yes는 지금 최종 커밋했다는 뜻이 아니라 결정이 오면 커밋할 책임을 맡았다는 뜻이다.

모든 참가자가 yes라고 답하면 코디네이터는 commit 결정을 영속 기록하고 전파한다. 하나라도 no이거나 준비 단계에서 필요한 확인을 얻지 못하면 abort를 결정할 수 있다. 참가자는 받은 결정에 따라 변경을 확정하거나 취소하고 자원을 해제한다.

Phase 1:
Coordinator -> A,B : PREPARE
A -> Coordinator  : YES, prepared record durable
B -> Coordinator  : YES, prepared record durable

Phase 2:
Coordinator durably records COMMIT.
Coordinator -> A,B : COMMIT
A and B finalize the transaction.

“두 단계”는 메시지가 총 두 번만 오간다는 뜻은 아니다. 요청, 응답, 확인, 재전송이 발생하며 실패 복구에는 로그 조회와 추가 통신이 필요하다. 참가자의 로컬 트랜잭션과 분산 결정 상태도 별도로 관리한다.

준비 뒤 장애가 왜 어려운가

A가 yes를 보낸 뒤 코디네이터와 연락이 끊겼다고 하자. 코디네이터가 B에도 commit을 보냈을 수 있다. A가 타임아웃만으로 abort하면 B는 커밋한 상태와 모순될 수 있다. 반대로 아무 근거 없이 커밋해도 다른 참가자가 no였을 수 있다.

따라서 준비 완료 참가자는 최종 결정을 알기 전까지 기다려야 하는 상태에 빠질 수 있다. 락이나 자원이 오래 유지되어 관련 작업까지 막힐 수 있다. 기본 2PC가 블로킹 프로토콜이라고 불리는 이유다. 준비에 들어가기 전과 yes를 보낸 뒤의 중단 가능성은 구분해야 한다.

재시작 후에는 영속 로그로 준비 여부와 최종 결정을 복원한다. commit 결정은 일부 참가자에게만 전달되었더라도 뒤집어서는 안 된다. 메시지를 다시 받아도 같은 결정을 적용할 수 있도록 처리한다.

코디네이터 상태를 복제하면 장애 위험을 줄일 수 있지만 참가자가 준비한 변경과 외부 효과까지 모두 해결되지는 않는다. 합의 알고리즘을 쓴다는 사실과 전체 트랜잭션의 원자성·진행 조건을 따로 검토해야 한다.

2PC는 로컬 트랜잭션의 모든 격리 수준을 대신하지 않는다. 어떤 읽기가 허용되는지는 DB의 락·버전·격리 설계에도 달려 있다. 프로토콜은 모두가 같은 최종 결정을 따르게 하고, 그 대가로 실패 중 대기와 조정 비용을 만든다.

참고 자료

2단계 커밋: 여러 저장소의 변경을 함께 확정하는 절차 · ariser