Skip to Content

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장의 회계사와 똑같아요. “어떻게 하면 약속이 절반만 지켜지는 일을 막을까?” 도구가 바뀌어도 질문은 그대로입니다.

그리고 이 여정의 진짜 끝은, 우리가 시작했던 그곳입니다.

▶ 닫는 글 — 다시, 커밋 앞에서

Last updated on