트랜잭션
- 8.1 ACID
- 8.2 Isolation
- 8.3 Atomicity
- 8.4 NewSQL
- 8.5 Outbox pattern
- 8.6 Sagas
- 8.7 Saga의 격리
분산 시스템에서 여러 서비스에 걸친 데이터를 원자적으로 다루는 건 진짜 고통이야. 크게 두 가지 접근법이 있거든 — 동기적 분산 트랜잭션(강한 보장, 하지만 블로킹)과 비동기 트랜잭션(약한 격리, 하지만 가용성이 높아). 둘 다 알아야 상황에 맞는 선택을 할 수 있지.
먼저 ACID부터 짚자. Atomicity는 전부 성공 아니면 전부 실패, Consistency는 유효한 상태 간 전이만 허용, Isolation은 동시 트랜잭션 간 간섭 차단, Durability는 커밋된 데이터의 장애 후 생존 보장이야. 이 네 개가 한 세트지.
격리 수준은 정확성과 성능의 트레이드오프 스펙트럼이야. Read uncommitted는 커밋 안 된 데이터도 읽어서 dirty read가 발생하고, Read committed는 커밋된 것만 읽지만 같은 데이터를 두 번 읽으면 값이 달라질 수 있어(non-repeatable read). Repeatable read는 그걸 막지만 새 행이 갑자기 나타나는 phantom read가 생기고, 가장 강력한 Serializable은 트랜잭션이 순서대로 실행된 것처럼 보여. 실무에서는 Snapshot isolation이 실용적 중간점이지 — 트랜잭션 시작 시점의 스냅샷을 보고 커밋 시 충돌을 감지해.
분산 환경에서 atomicity를 보장하는 핵심은 **2PC(Two-Phase Commit)**야. Prepare 단계에서 코디네이터가 모든 참여자한테 "커밋할 수 있어?" 물어보고, 다 Yes면 Commit, 하나라도 No면 롤백. 단순하지? 근데 치명적 약점이 있어 — 블로킹 프로토콜이라는 거야. 코디네이터가 도중에 죽으면 참여자들이 대기 상태에 갇혀. 이 가용성 문제를 개선한 게 NewSQL이야. Spanner, CockroachDB 같은 녀석들이 분산 ACID를 더 나은 가용성으로 제공하지. 특히 Spanner는 TrueTime API — GPS와 원자 시계로 전역 타임스탬프를 만들어서 분산 스냅샷 격리를 구현해. 물론 일관성-가용성 트레이드오프가 마법처럼 사라지는 건 아니야.
여기까지가 동기적 접근이고, 이제 반대편을 보자. 2PC가 블로킹이라는 한계를 우회하려면 ACID의 격리를 일부 희생하는 대신 가용성을 높이는 비동기 기법이 필요해. 마이크로서비스에서 특히 실용적이거든. 먼저 이중 쓰기(dual write) 문제 — DB 업데이트와 메시지 발행을 원자적으로 할 수가 없어. Outbox pattern이 이걸 해결하지. DB 트랜잭션 안에서 비즈니스 데이터 업데이트와 outbox 테이블에 메시지 삽입을 같이 하는 거야. DB 트랜잭션의 원자성이 둘을 묶어주잖아. 별도 프로세스가 outbox를 폴링해서 메시지 브로커에 발행하고 삭제해.
Saga는 2PC의 비블로킹 대안이야. 하나의 큰 분산 트랜잭션 대신 로컬 트랜잭션의 연쇄로 처리하는 거지. 각 단계가 로컬 트랜잭션을 실행하고 다음 단계를 트리거해. 한 단계가 실패하면 이전 단계의 **보상 트랜잭션(compensating transaction)**으로 되돌려. 주문 → 결제 → 배송 흐름에서 배송이 실패하면 결제 취소, 주문 취소 순이야. 블로킹이 없는 대신 보상 트랜잭션 구현이 까다롭지.
Saga의 가장 큰 약점은 격리 부재야. 각 단계 사이에 다른 트랜잭션이 중간 상태를 볼 수 있거든. 완화 전략이 있어 — **의미적 잠금(semantic lock)**으로 "처리 중" 플래그를 세우거나, **교환적 업데이트(commutative updates)**로 순서에 무관한 연산을 설계하거나, 비관적/낙관적 동시성 제어를 쓰는 거지. 완전한 ACID 격리는 불가능하지만 애플리케이션 수준에서 충분한 격리를 설계할 수 있어.
정리
8장 읽고 기억할 거 세 가지:
- 2PC는 블로킹, Saga는 비블로킹 — 강한 격리가 필요하면 2PC/NewSQL, 가용성이 우선이면 Saga. 트레이드오프를 이해하고 골라야 해
- Outbox pattern은 이중 쓰기 문제를 해결해 — DB 트랜잭션으로 데이터 변경과 메시지 발행을 원자적으로 묶는 거야
- Saga의 격리 부재는 의미적 잠금, 교환적 업데이트 같은 애플리케이션 수준 전략으로 완화할 수 있어 — 완벽하진 않지만 실용적이지