18장. 경계를 넘는 약속 — 분산 트랜잭션

만화로 보는 요약 — 먼저 읽어보세요
하나의 약속이 여러 DB에 걸친다 → 2PC는 원자성을 강제하지만 blocking에 약하다 → Saga는 보상과 멱등성으로 결국 일관성에 도달한다.
면접 실전 질문: ① 2PC의 prepare와 commit 단계 ② YES 이후 코디네이터 장애가 blocking을 만드는 이유 ③ Saga에서 보상 트랜잭션과 멱등성이 필요한 이유
배경 — 약속이 경계를 넘을 때
지금까지의 모든 이야기 — ACID, 락, MVCC, WAL, @Transactional — 는 하나의
데이터베이스 안에서 벌어졌어요. 트랜잭션이 하나의 DB를 전제로 했기 때문에, DB가
커밋·롤백·락·로그를 혼자서 책임질 수 있었죠.
그런데 요즘 시스템은 쪼개집니다. 주문 서비스, 결제 서비스, 재고 서비스가 각자 다른 DB를 가져요(MSA). 이제 “주문 하나”라는 약속이 세 개의 DB에 걸칩니다. 누구도 혼자서는 전체를 커밋하거나 롤백할 수 없어요. 여기서 트랜잭션의 마지막 난제, 분산 트랜잭션이 시작됩니다.
스토리 — 모두가 동의해야 하는 커밋
여러 DB에 걸친 트랜잭션을 원자적으로 묶는 고전적 방법이 2단계 커밋(Two-Phase Commit, 2PC) 입니다. 이름 그대로 커밋을 두 단계로 나눠요. 한 명의 코디네이터가 지휘합니다.
[1단계 · 준비(prepare)]
코디네이터 → 모든 DB에게: "커밋할 준비 됐어?"
각 DB → "응, 준비됐어(YES)" 또는 "안 돼(NO)"
[2단계 · 확정(commit)]
전원이 YES → 코디네이터: "다 같이 커밋!" → 모두 커밋
하나라도 NO → 코디네이터: "다 같이 취소!" → 모두 롤백결혼식 주례 같아요. “이 결혼에 반대하는 분?” 하고 물어(prepare), 아무도 반대 안 하면 그때 성사를 선언(commit)하는 거죠. 모두가 동의해야만 전체가 확정됩니다. 원자성이 여러 DB에 걸쳐 지켜져요.
깔끔해 보이죠? 그런데 2PC엔 치명적인 약점이 있습니다. 기다림이에요. 1단계에서 YES를 답한 DB는, 코디네이터의 최종 결정이 올 때까지 자원을 잠근 채 기다려야 합니다. 만약 이때 코디네이터가 죽으면? 그 DB는 커밋해야 할지 롤백해야 할지 모른 채 락을 쥐고 무한정 멈춰 있어요(blocking). 16장 데드락, 17장 커넥션 고갈에서 봤던 그 “붙잡고 멈추는” 문제가, 이번엔 네트워크 너머에서 벌어집니다. 그래서 2PC는 느린 데다, 코디네이터 한 명에게 전체 운명이 걸립니다(단일 장애점). 그래서 MSA에서 2PC는 잘 안 씁니다.
핵심 — 완벽을 포기하고 현실을 택하다
2PC가 “모두를 묶어 완벽한 원자성”을 노렸다면, 현대적 접근은 다르게 생각합니다. “완벽하게 한 번에 묶지 말고, 단계별로 진행하되 실패하면 되돌리는 보상을 준비하자.” 이게 사가(Saga) 패턴이에요.
주문을 예로 들면, 하나의 큰 트랜잭션 대신 작은 로컬 트랜잭션들의 사슬로 만듭니다.
결제 성공 → 재고 차감 성공 → 배송 요청... 실패!
↓
(보상) 재고 복구 ← (보상) 결제 취소 ← 거꾸로 되감기각 단계는 자기 DB에서 평범한 로컬 트랜잭션으로 커밋해요(우리가 1~17장 내내 배운
그 트랜잭션!). 대신 중간에 실패하면, 이미 성공한 단계들을 거꾸로 되돌리는 보상
트랜잭션(compensating transaction) 을 실행합니다. 결제를 취소하고, 차감한 재고를
되돌리고. ROLLBACK 대신 “반대 작업으로 갚는” 거죠.
여기서 꼭 필요한 게 멱등성(idempotency) 입니다. 네트워크가 불안한 분산 환경에선 응답을 못 받아 재시도하는 사이 같은 메시지가 두 번 도착할 수 있어요(“재고 차감” 요청이 중복 전달). 그래서 각 작업은 몇 번을 실행해도 결과가 같도록 설계해야 합니다. “이 주문 번호는 이미 처리했으면 무시” 같은 장치로요. 안 그러면 재고가 두 번 깎이는 새로운 사고가 나죠.
정리하면, 분산 트랜잭션은 단일 DB의 강한 원자성을 포기하는 대신, 로컬 트랜잭션 + 보상 + 멱등성으로 결국 일관성(eventual consistency) 에 도달하는 현실적 타협 입니다. 완벽함보다 회복 가능함을 택한 거예요.
정리
- 서비스가 쪼개지면(MSA) 하나의 약속이 여러 DB에 걸친다 → 분산 트랜잭션.
- 2PC(2단계 커밋): 코디네이터가 prepare(준비됐어?) → commit(다 같이!)로 원자성을 강제. 안전하지만 blocking(YES 후 결정 기다리며 락 유지) + 코디네이터 단일 장애점 → MSA에서 기피.
- 사가(Saga): 큰 트랜잭션을 로컬 트랜잭션 사슬로. 실패 시 보상 트랜잭션으로
거꾸로 되감기.
ROLLBACK대신 “반대 작업으로 갚기”. - 멱등성 필수: 메시지 중복 도착에도 결과가 같도록. → 결국 일관성(eventual consistency).
- 핵심 전환: 강한 원자성을 포기하고 회복 가능함을 택한다.
강한 원자성을 포기하고 회복 가능함을 택하는 것 — 이게 트랜잭션이 경계를 넘는 방식입니다. 2PC든 사가든, 결국 묻는 질문은 1장의 회계사와 똑같아요. “어떻게 하면 약속이 절반만 지켜지는 일을 막을까?” 근본을 아는 사람에게 새로운 기술은 낯선 게 아니라, 같은 질문의 새로운 답일 뿐입니다.
그리고 이 여정의 진짜 끝은, 우리가 시작했던 그곳입니다.