본문 바로가기

분산 트랜잭션과 XA 복구

2PC의 준비 투표·영속 결정·불확실 차단을 추적하고 MySQL XA PREPARED의 프로세스 장애 복구를 확인합니다.

단편화와 복제는 데이터를 여러 곳에 놓는 선택입니다. 여러 DB의 변경을 하나의 전체 성공/전체 취소 단위로 확정하려면 별도의 결정 프로토콜이 필요합니다. 로컬 COMMIT 두 개를 차례로 실행하면 첫 DB만 확정되고 두 번째는 실패할 수 있습니다. 이번 절은 트랜잭션과 로그 회복을 읽은 뒤 진행합니다. 관리자 연결·학습 전용 서버 준비는 최소 권한 절의 접속 안내를 따릅니다.

2PC는 준비와 최종 결정을 나눈다

예를 들어 재고 DB A의 수량 감소와 주문 DB B의 주문 생성을 같은 글로벌 트랜잭션 order-7로 묶습니다. 조정자(coordinator)는 참여자 목록과 최종 결정을 관리하고, 참여자(resource manager)는 자기 로컬 변경을 준비·확정합니다.

단계조정자참여자 A·B
준비 요청모두에게 PREPARE 요청로컬 검증·변경 수행
투표각 READY 또는 거절 수집복구 가능한 PREPARED 상태를 저장한 뒤 READY 전송
결정 저장모두 READY이면 COMMIT 가능; 거절/준비 timeout 등에는 ABORT 결정. 결정을 영속 기록PREPARED는 아직 최종 커밋이 아니며 관련 잠금/자원을 보존
결정 전달저장한 COMMIT/ABORT를 재전송 가능해당 결정을 적용하고 완료 응답

READY는 “이미 커밋했다”가 아니라 나중에 어느 결정이 와도 따를 준비를 저장했다는 뜻입니다. 투표 메시지 READY와 참여자의 저장 상태 PREPARED를 구별합니다. 조정자는 COMMIT을 참여자에게 보내기 전에 그 결정을 저장해야 합니다. 한 참여자가 이미 커밋한 뒤 조정자가 재시작해 기억만 보고 ABORT를 고르면 전부 성공/취소가 깨지기 때문입니다. 저장한 COMMIT을 마음대로 ABORT로 바꿀 수 없습니다.

재시작 때 어디서 결정을 찾는가

실패 시점남은 기록안전한 처리
참여자가 READY를 보내기 전 실패준비 완료를 보장하지 못함조정자는 전부 READY가 아니므로 COMMIT하지 않음; 미완료 로컬 변경 복구
참여자 PREPARED 후 실패참여자 준비 기록재시작해 XID/상태 복원, 조정자의 결정 조회
조정자가 최종 결정 저장 전 실패참여자 일부/전체 PREPARED, 결정 불명참여자가 timeout만으로 임의 COMMIT/ABORT하지 않음. 조정자 복구/다른 확실한 결정 근거를 기다림
조정자가 COMMIT 저장 후 전달 중 실패COMMIT 결정과 미응답 참여자 목록재시작 후 같은 COMMIT 재전송; 이미 적용한 결과와 대조
완료 응답만 유실참여자는 최종 상태, 조정자는 응답 불명확정 여부 조회/기록 대조. XA 목록에서 사라졌다는 사실만으로 COMMIT과 ABORT를 구별하지 않음

기본 2PC에서 조정자 결정에 접근할 수 없는 PREPARED 참여자는 불확실 상태로 차단될 수 있습니다. timeout은 상대의 응답을 받지 못했다는 관측이며 상대가 다른 참여자에 결정을 보냈는지를 증명하지 않습니다. 결정 전달의 재시도는 저장한 동일 결정을 따릅니다. 고가용 조정자는 결정 로그 복제·리더의 유일성 등 추가 설계를 요구합니다. 이 절은 합의 프로토콜 구현이나 무중단 2PC를 주장하지 않습니다. Gray·Lamport의 원 논문, 2PC와 차단 절

MySQL의 참여자 상태를 추적하기

MySQL 8.4에서 XA START는 ACTIVE, XA END는 IDLE, XA PREPARE는 PREPARED로 옮깁니다. PREPARED에서 XA COMMIT 또는 XA ROLLBACK이 최종 상태를 만듭니다. XA RECOVER는 PREPARED XID를 나열합니다. SQL 준비문 PREPARE와 트랜잭션의 XA PREPARE는 서로 다릅니다.

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다

XA 준비가 영속 상태로 남는다. 조정자의 한 최종 결정을 따른다. 각 단계의 값과 전환 이유를 비교합니다.

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다

한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다.

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다. 실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.재시작 경계 · 저장소 존속crashrestartdecisionPREPAREDREADY 전 저장자체 엔진 종료datadir 보존재시작·조회XA RECOVER에 남음저장 결정 적용COMMIT / ABORT실선: 사건 순서 · 점선: 재시작 경계
PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다. 실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.재시작 경계저장소 존속crashrestartdecisionPREPAREDREADY 전 저장자체 엔진 종료datadir 보존재시작·조회XA RECOVER에 남음저장 결정 적용COMMIT / ABORT실선: 사건 순서점선: 재시작 경계
실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 PREPARED부터 저장 결정 적용까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
PREPARED

XA 준비가 영속 상태로 남는다.

  • XA·저장 결정 1: XA RECOVER: cs_order_7
  • XA·저장 결정 2: 다른 일반 읽기 100
  • XA·저장 결정 3: XA 내부 변경 70
  • READY 투표 전 저장
엔진 재시작

엔진은 멈춰도 저장소는 남는다.

  • XA·저장 결정 1: datadir 보존
  • XA·저장 결정 2: 결정 파일 보존
  • 재시작 경계 통과
복구 조회

XID가 여전히 준비 상태다.

  • XA·저장 결정 1: XA RECOVER에 남음
  • XA·저장 결정 2: locking read 대기 가능
  • timeout만으로 취소 금지
저장 결정 적용

조정자의 한 최종 결정을 따른다.

  • XA·저장 결정 1: COMMIT이면 70/ABORT이면 100
  • XA·저장 결정 2: RECOVER에서 제거
  • 둘 중 하나 적용; 반복 1397만으로 과거 결정 판별 불가

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다

XA 준비가 영속 상태로 남는다. 조정자의 한 최종 결정을 따른다. 각 단계의 값과 전환 이유를 비교합니다.

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다

한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다.

PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다. 실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.재시작 경계 · 저장소 존속crashrestartdecisionPREPAREDREADY 전 저장자체 엔진 종료datadir 보존재시작·조회XA RECOVER에 남음저장 결정 적용COMMIT / ABORT실선: 사건 순서 · 점선: 재시작 경계
PREPARED는 엔진 재시작 뒤에도 최종 결정을 기다린다한 MySQL 참여자의 준비 상태 복구입니다. READY는 준비 저장 뒤의 투표이며 COMMIT 완료가 아닙니다. 실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.재시작 경계저장소 존속crashrestartdecisionPREPAREDREADY 전 저장자체 엔진 종료datadir 보존재시작·조회XA RECOVER에 남음저장 결정 적용COMMIT / ABORT실선: 사건 순서점선: 재시작 경계
실선 화살표는 설명용 사건 순서, 점선은 엔진 재시작 경계입니다. 조정자 결정 파일과 참여자 datadir은 보존됩니다. 최종 결정이 불명하면 PREPARED를 timeout만으로 취소하지 않으며 이 단일 참여자 그림은 다중 서버/네트워크 fault 실험이 아닙니다.

이 예의 조건

  • 본문과 전체 도표의 예를 따라 PREPARED부터 저장 결정 적용까지 진행합니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
PREPARED

XA 준비가 영속 상태로 남는다.

  • XA·저장 결정 1: XA RECOVER: cs_order_7
  • XA·저장 결정 2: 다른 일반 읽기 100
  • XA·저장 결정 3: XA 내부 변경 70
  • READY 투표 전 저장
엔진 재시작

엔진은 멈춰도 저장소는 남는다.

  • XA·저장 결정 1: datadir 보존
  • XA·저장 결정 2: 결정 파일 보존
  • 재시작 경계 통과
복구 조회

XID가 여전히 준비 상태다.

  • XA·저장 결정 1: XA RECOVER에 남음
  • XA·저장 결정 2: locking read 대기 가능
  • timeout만으로 취소 금지
저장 결정 적용

조정자의 한 최종 결정을 따른다.

  • XA·저장 결정 1: COMMIT이면 70/ABORT이면 100
  • XA·저장 결정 2: RECOVER에서 제거
  • 둘 중 하나 적용; 반복 1397만으로 과거 결정 판별 불가
학습 전용 서버: XA 한 가지의 상태 추적
CREATE DATABASE IF NOT EXISTS cs_xa_lab;
CREATE TABLE cs_xa_lab.account(id INT PRIMARY KEY,balance INT NOT NULL) ENGINE=InnoDB;
INSERT INTO cs_xa_lab.account VALUES(1,100);

XA START 'cs_order_7';
UPDATE cs_xa_lab.account SET balance=70 WHERE id=1;
SELECT balance FROM cs_xa_lab.account WHERE id=1; -- 이 XA 연결 안에서는 70
XA END 'cs_order_7';
XA PREPARE 'cs_order_7';
XA RECOVER CONVERT XID;
-- cs_order_7이 PREPARED 목록에 남음

기본 xa_detach_on_prepare=ON에서는 PREPARE 뒤 다른 연결도 이 XID의 결정을 적용할 수 있습니다. 별도 연결의 일반 일관 읽기는 아직 확정 전 값 100을 봅니다. 반면 같은 행의 locking read/UPDATE는 준비된 트랜잭션의 잠금 때문에 기다릴 수 있습니다. 준비 완료와 변경의 공개를 같다고 해석하지 않습니다. MySQL XA 상태와 detached 트랜잭션

조정자가 COMMIT을 영속 기록한 경우에만 XA COMMIT 'cs_order_7';로 확정합니다. ABORT 기록이면 XA ROLLBACK 'cs_order_7';로 취소합니다. 두 명령을 같은 XID에 차례로 실행하지 않습니다. 한 번 최종 적용한 XID는 XA RECOVER에서 사라집니다. 다시 같은 COMMIT을 보내면 XAER_NOTA 1397이 날 수 있고 이 오류만으로 과거 결정을 알아낼 수는 없습니다. 결정 로그와 업무 결과를 대조합니다.

실제 엔진 종료 후 PREPARED 복구 실험

운영 서버의 서비스를 종료해 실험하지 않습니다. 저장소 docs/cs-textbooks/db-os-labs/mysql_xa_crash.py는 Windows MySQL 8.4 바이너리로 새 datadir·고유 named pipe를 만듭니다. skip-networking, mysqlx OFF로 TCP 포트를 열지 않으며 --no-monitor와 자체 PID 파일을 대조합니다. 이미 있는 데이터 폴더는 받지 않고 새 결과 폴더만 만듭니다. Python 3 표준 라이브러리와 mysql.exe/mysqld.exe가 필요합니다.

저장소 루트: 준비된 MySQL bin 경로와 새 결과 경로
python docs/cs-textbooks/db-os-labs/mysql_xa_crash.py --bin-dir "C:\확인한경로\mysql-8.4\bin" --out ".\xa-lab-new"

경로 예시는 자신의 바이너리 위치로 바꿉니다. named pipe 접근이 오류 2017로 거절되면 서버 시작·pipe 이름·Windows 실행 자격부터 확인합니다. 관리자/클라이언트의 실행 자격이 다른 환경에서 인증 플러그인을 바꾸거나 기존 서비스 권한을 넓혀 실험하지 않습니다.

실험은 한 MySQL 참여자에 서로 다른 글로벌 XID 두 개를 준비합니다. deep_commit은 계정 1을 100→70, deep_abort는 계정 2를 100→50으로 바꿉니다. 외부 제어 파일에 각 XID의 독립 COMMIT/ABORT 결정을 저장하고 flush/fsync를 완료한 뒤, 자체 mysqld 프로세스만 강제 종료합니다. 두 XID가 같은 글로벌 작업의 서로 다른 투표라는 뜻이 아닙니다.

관찰 순서예상
PREPARE 뒤 XA RECOVER두 XID 존재
다른 연결의 일반 읽기계정 1·2 모두 100
계정 1 locking read, wait timeout 1초ERROR 1205 — 준비된 잠금 때문에 대기
자체 엔진 종료·같은 새 datadir 재시작 후 XA RECOVER두 PREPARED XID 여전히 존재
외부 기록의 COMMIT/ABORT 적용계정 1은 70, 계정 2는 100
최종 XA RECOVER목록 비어 있음

종료 직전 외부 before-engine-kill.json에 PID/datadir·입력·독립 예상·관측을 남기며 coordinator-decisions.json은 결정 근거입니다. 결과 JSON과 재시작 로그를 함께 읽습니다. 프로세스 장애 뒤 PREPARED가 자동으로 일반 미커밋처럼 모두 취소된다고 가정하지 않습니다. MySQL crash recovery와 XA PREPARED

확인한 것과 남은 경계

이 실행은 실제 MySQL XA 준비 상태의 엔진 프로세스 장애/복구를 확인합니다. 조정자 결정은 같은 컴퓨터의 영속 파일로 표현하며 실제 네트워크 메시지나 다중 서버 조정자를 실행하지 않습니다. 두 DB의 원자 커밋, 네트워크 분할, 전원·저장장치 손실, 복제된 조정자 복구는 별도 검증 대상입니다. 단일 DB의 효과와 완료 기록을 묶는 경우는 지속 재시도의 로컬 트랜잭션만으로 해결할 수 있고, 이를 2PC라고 부를 필요는 없습니다.

재시작 후 질문은 “프로세스가 다시 떴나?”에서 끝나지 않습니다. 남은 PREPARED는 무엇인가, 최종 결정의 영속 근거는 어디 있는가, 아직 모르는 동안 어떤 작업이 막히는가까지 추적해야 합니다.

작은 상태 추적

참여자 A·B가 모두 PREPARED인데 조정자의 응답이 끊겼습니다. A는 timeout만 보고 ROLLBACK해도 될까요? 재시작한 조정자 파일에 COMMIT이 있고 B만 이미 확정했다면 다음 전달은 무엇인가요?

힌트

A가 보지 못한 결정이 B에게 도착했을 가능성과, 저장한 결정의 변경 가능성을 따로 봅니다.

풀이

PREPARED인 A가 timeout만으로 임의 취소하면 B가 이미 커밋한 경우 원자성이 깨집니다. 결정 근거를 조회할 수 없으면 불확실한 채 기다릴 수 있습니다. 복구한 조정자는 저장한 COMMIT을 A에게 다시 전달하고 B의 완료 상태를 대조합니다. 같은 XID를 ABORT로 바꾸지 않습니다.