트랜잭션 경계

Nest 트랜잭션 경계

트랜잭션은 데이터 정합성을 지킨다는 말보다 언제 시작하고, 어떤 세션이나 query runner를 공유하며, 어떤 예외에서 rollback하고, 동시성 충돌을 어떻게 다시 시도할지가 중요하다.

업무 명령 한 요청 안에서 반드시 함께 성공해야 하는 쓰기 묶음
공유 세션 repository, model, 외부 호출을 같은 트랜잭션 컨텍스트에 묶음
결정 지점 모든 쓰기 성공이면 commit, 하나라도 실패하면 rollback
01

업무 단위 선택

주문 생성과 재고 차감처럼 모두 성공하거나 모두 실패해야 하는 작업을 묶는다.

긴 외부 API 호출은 트랜잭션 안에 넣지 않는다
02

공유 세션 전달

TypeORM QueryRunner나 Mongoose session을 모든 repository 호출에 같은 경계로 전달한다.

하나만 세션 밖이면 정합성이 깨진다
03

Commit 조건

모든 검증과 쓰기가 끝난 뒤에만 commit하고, 이후 부작용은 outbox 같은 별도 경로를 검토한다.

메일 발송은 DB commit과 원자적으로 묶기 어렵다
04

Rollback 처리

예외, timeout, 검증 실패에서 rollback이 실행되고 connection/session이 해제되는지 보장한다.

finally 정리가 핵심이다
05

동시성 충돌 대응

deadlock, serialization failure, optimistic lock 실패를 재시도하거나 사용자 오류로 돌린다.

격리 수준에 따라 증상이 달라진다
TypeORM
QueryRunner/manager 공유 트랜잭션 manager로 모든 쓰기를 수행해야 같은 connection에 묶인다.
기본 repository를 섞지 않는다
Mongoose
Session 공유 모든 모델 작업에 같은 session을 전달해야 transaction에 포함된다.
Replica set 요구를 확인한다
Isolation
동시성 가시성 dirty read, non-repeatable read, phantom read 허용 범위를 업무에 맞춘다.
강할수록 비용이 커질 수 있다
Retry
충돌 복구 deadlock과 serialization 실패는 안전한 재시도 조건을 따로 둔다.
idempotency가 필요하다

트랜잭션 확인

중간 실패 두 번째 쓰기에서 예외를 던져 첫 번째 쓰기가 rollback되는지 확인한다.
세션 누락 트랜잭션 안의 모든 repository/model 호출이 같은 세션을 쓰는지 본다.
동시 요청 같은 자원을 동시에 수정해 lock과 retry 동작을 확인한다.