UMC/study

[UMC_study] 트렌젝션의 상태와 전파에 대하여

sunm2n 2025. 9. 21. 03:50

데이터베이스 트랜잭션 상태 및 전파 모델에 대한 종합적 분석

I. 서론: 트랜잭션의 본질과 ACID 원칙

트랜잭션 정의: 논리적 작업 단위

데이터베이스 시스템의 맥락에서 트랜잭션(Transaction)은 단순히 여러 SQL 문의 집합이 아니라, 하나의 비즈니스 로직을 수행하기 위한 '논리적 작업 단위(Logical Unit of Work)'로 정의됩니다. 이러한 관점은 데이터베이스의 기계적 동작에서 비즈니스 무결성 보장이라는 더 높은 차원으로 초점을 이동시키기 때문에 매우 중요합니다. 예를 들어, 은행 계좌 이체는 출금(UPDATE)과 입금(UPDATE)이라는 두 개 이상의 물리적 데이터베이스 변경 작업을 포함하지만, 비즈니스 관점에서는 분리할 수 없는 단일 논리적 연산입니다.   

 

트랜잭션의 핵심적인 약속은 시스템 오류, 정전, 동시 접근과 같은 예측 불가능한 상황에서도 데이터베이스가 하나의 일관된 상태(Consistent State)에서 다른 일관된 상태로 안전하게 전환됨을 보장하는 것입니다. 이 보증은 애플리케이션 개발을 극적으로 단순화합니다. 개발자는 복잡한 실패 시나리오에 대한 수동 복구 코드를 작성하는 대신, 데이터베이스의 트랜잭션 관리 기능에 의존하여 데이터 무결성을 유지할 수 있습니다.   

 

데이터 무결성을 위한 ACID 보증 심층 분석

ACID 원칙은 원자성(Atomicity), 일관성(Consistency), 고립성(Isolation), 지속성(Durability)의 약자로, 관계형 데이터베이스 관리 시스템(RDBMS)과 같은 시스템에서 신뢰할 수 있는 트랜잭션 처리를 위한 초석을 이룹니다. 이 네 가지 속성은 독립적인 기능이 아니라, 데이터 무결성을 보장하기 위해 상호 의존적으로 작용하는 하나의 약속 집합입니다.   

 

원자성 (Atomicity): 'All-or-Nothing' 원칙

원자성은 트랜잭션을 구성하는 모든 연산이 하나의 분리 불가능한 단위로 취급됨을 보장합니다. 즉, 트랜잭션 내의 모든 연산이 성공적으로 완료되거나, 단 하나라도 실패할 경우 모든 연산이 취소되어 아무것도 실행되지 않은 것처럼 되돌려져야 합니다.   

 

이러한 'All-or-Nothing' 동작은 주로 트랜잭션 로그(Transaction Log) 및 롤백 세그먼트(Rollback Segment)와 같은 메커니즘을 통해 구현됩니다. 트랜잭션 실행 중 오류가 발생하면, 시스템은 이 로그를 사용하여   

 

ROLLBACK 명령을 통해 부분적으로 적용된 모든 변경 사항을 취소하고 데이터베이스를 트랜잭션 시작 이전의 상태로 완벽하게 복원합니다. 이는 한 계좌에서 돈이 출금되었으나 다른 계좌로 입금되지 않는 것과 같은 치명적인 데이터 불일치 상태를 방지합니다.   

 

일관성 (Consistency): 유효한 상태로의 전환

일관성은 트랜잭션이 데이터베이스를 하나의 유효한(valid) 상태에서 또 다른 유효한 상태로만 전환시켜야 함을 보장합니다. 여기서 '유효한 상태'란 기본 키(Primary Key), 외래 키(Foreign Key)와 같은 무결성 제약 조건, 캐스케이드(Cascades), 트리거(Triggers) 등 데이터베이스에 정의된 모든 규칙과 조건을 만족하는 상태를 의미합니다.   

 

이 속성은 원자성보다 한 단계 높은 비즈니스 규칙의 유효성을 보장하는 역할을 합니다. 예를 들어, 계좌 이체 트랜잭션이 원자적으로 실행되어 출금과 입금이 모두 성공하거나 모두 실패하더라도, 그 결과로 특정 계좌의 잔액이 음수가 된다면 이는 비즈니스 규칙(잔고는 0 이상이어야 함)을 위반하는 것입니다. 데이터베이스 관리 시스템은 트랜잭션을 커밋하기 전에 이러한 제약 조건들을 검증함으로써 일관성을 강제합니다.   

 

고립성 (Isolation): 동시성 제어의 핵심

고립성은 여러 트랜잭션이 동시에 실행될 때 서로의 작업에 간섭하지 않도록 보장하는 원칙입니다. 각 트랜잭션의 관점에서는 마치 시스템에서 자기 자신만 단독으로 실행되는 것처럼 보여야 합니다.   

 

고립성은 일반적으로 잠금(Locking) 메커니즘(공유 잠금, 배타적 잠금)이나 다중 버전 동시성 제어(MVCC, Multiversion Concurrency Control)를 통해 구현됩니다. 데이터베이스는   

 

READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SERIALIZABLE과 같은 다양한 격리 수준(Isolation Level)을 제공하며, 이를 통해 개발자는 데이터 일관성과 시스템 성능 사이의 균형을 조절할 수 있습니다. 적절한 고립성이 보장되지 않으면 다른 트랜잭션이 커밋하지 않은 데이터를 읽는 '더티 리드(Dirty Read)', 한 트랜잭션 내에서 같은 데이터를 두 번 읽었을 때 결과가 다르게 나타나는 '반복 불가능한 읽기(Non-repeatable Read)', 특정 범위의 데이터를 조회했을 때 이전에 없던 새로운 데이터가 나타나는 '유령 읽기(Phantom Read)'와 같은 동시성 문제가 발생할 수 있습니다.   

 

지속성 (Durability): 영구적인 결과 보장

지속성은 일단 트랜잭션이 성공적으로 커밋(Commit)되면, 그 결과는 영구적으로 데이터베이스에 저장되어야 함을 의미합니다. 이는 이후에 시스템에 정전이나 충돌과 같은 장애가 발생하더라도 커밋된 데이터는 손실되지 않아야 함을 보장합니다.   

 

이 속성은 트랜잭션의 변경 내용을 비휘발성 저장 장치(예: 하드 디스크)에 위치한 트랜잭션 로그에 기록한 후에야 애플리케이션에 COMMIT 성공을 반환하는 방식으로 구현됩니다. 만약 시스템 장애가 발생하면, 데이터베이스 복구 관리자는 이 로그를 사용하여 아직 주 데이터 파일에 반영되지 않은 커밋된 변경 사항들을 재실행(   

 

REDO)함으로써 데이터의 영속성을 보장합니다.   

 

ACID 원칙은 단순한 기술 명세를 넘어, 분산 시스템의 CAP 이론 및 많은 NoSQL 데이터베이스에서 채택하는 BASE(Basically Available, Soft state, Eventually consistent) 모델과 근본적인 트레이드오프 관계를 형성합니다. CAP 이론에 따르면 분산 시스템은 일관성(Consistency), 가용성(Availability), 분할 허용성(Partition tolerance) 중 최대 두 가지만을 동시에 보장할 수 있습니다. ACID를 엄격하게 준수하는 시스템은 일반적으로 네트워크 분할 상황에서 가용성보다 일관성을 우선시하는 아키텍처(CP 또는 CA)를 선택합니다. 반면, BASE 모델을 따르는 시스템은 일관성을 다소 완화(최종적 일관성)하는 대신 높은 가용성을 우선합니다.   

 

이러한 관계를 이해하는 것은 데이터베이스 선택이 곧 시스템의 일관성 모델을 선택하는 중대한 아키텍처적 결정임을 시사합니다. 금융 거래, 재고 관리, 전자상거래 주문 처리와 같이 데이터의 정합성이 비즈니스의 핵심인 시스템은 ACID의 엄격한 보증에 크게 의존합니다. 반면, 소셜 미디어 피드나 실시간 분석 시스템과 같이 일시적인 데이터 불일치를 감수하더라도 항상 서비스가 제공되는 것이 더 중요한 경우에는 BASE 모델이 더 적합할 수 있습니다. 이처럼 데이터베이스의 저수준 특성인 ACID가 시스템 전체의 아키텍처와 신뢰성 모델에 미치는 영향은 매우 지대합니다.   

 

II. 트랜잭션 생명주기: 상태 전환 모델 분석

트랜잭션 상태의 정의

트랜잭션은 단일 이벤트가 아니라, 생명주기 동안 명확하게 정의된 일련의 상태들을 거치는 프로세스입니다. 이 상태들을 이해하는 것은 데이터베이스의 내부 복구 및 동시성 제어 메커니즘을 파악하고 디버깅하는 데 필수적입니다. 트랜잭션의 주요 상태는 다음과 같습니다: 활동(Active), 부분 완료(Partially Committed), 완료(Committed), 실패(Failed), 철회(Aborted). 일부 모델에서는 최종 상태인 종료(Terminated)를 포함하기도 합니다.   

 

생명주기의 시각화: 상세 상태 전환 다이어그램

이러한 상태들 간의 관계는 상태 전환 다이어그램(State Transition Diagram)을 통해 가장 효과적으로 표현될 수 있습니다. 이 다이어그램은 트랜잭션이 시작되어 종료에 이르기까지의 흐름을 시각적으로 보여줍니다. 각 상태는 노드로, 상태 전환을 유발하는 연산이나 이벤트는 방향성 있는 엣지(edge)로 표시됩니다.   

 

!(https://i.imgur.com/uI9vL2j.png)

그림 1: 트랜잭션의 상태 전환 다이어그램. 트랜잭션은 활동 상태에서 시작하여 성공 시 완료 상태로, 실패 시 철회 상태로 전환됩니다.

각 상태에 대한 심층 분석

  • 활동 상태 (Active State) 트랜잭션이 실행을 시작한 초기 상태입니다. 모든 읽기(read) 및 쓰기(write) 연산은 이 상태에서 수행됩니다. 이 단계에서 발생한 데이터 변경 사항은 일반적으로 메인 메모리의 버퍼에 보관되며, 아직 영구적으로 저장되지 않은 상태입니다.   
  •  
  • 부분 완료 상태 (Partially Committed State) 트랜잭션의 마지막 연산까지 성공적으로 실행되었지만, 변경 사항이 데이터베이스에 영구적으로 반영되기 직전의 중요한 임시 상태입니다. 시스템은 이 시점에서 트랜잭션이 성공할 수 있다고 판단했지만, 만약 이 순간에 정전과 같은 시스템 장애가 발생하면 커밋이 완료되지 못할 수 있습니다. 이 상태는 지속성(Durability) 메커니즘이 해결해야 하는 취약 구간을 명확히 보여줍니다.   
  •  
  • 완료 상태 (Committed State) 트랜잭션이 모든 연산을 성공적으로 완료하고, COMMIT 연산을 통해 그 변경 사항이 데이터베이스에 영구적으로 반영된 상태입니다. 이제 트랜잭션의 결과는 영구적이며, 설정된 격리 수준에 따라 다른 트랜잭션에게도 보이게 됩니다.   
  •  
  • 실패 상태 (Failed State) 트랜잭션 실행 중 연산을 정상적으로 수행할 수 없거나, 무결성 제약 조건을 위반하는 등의 문제가 발생하면 실패 상태로 전환됩니다. 이 상태에 들어간 트랜잭션은 더 이상 정상적인 진행이 불가능합니다.   
  •  
  • 철회 상태 (Aborted State / Rolled Back) 트랜잭션이 실패 상태에 도달한 후, 데이터베이스 관리 시스템은 해당 트랜잭션이 수행한 모든 변경 사항을 취소(UNDO)하여 데이터베이스를 이전의 일관된 상태로 되돌려야 합니다. 이 ROLLBACK 연산이 완료되면 트랜잭션은 철회 상태가 됩니다. 이 상태는 실패한 모든 트랜잭션의 최종 종착점이며, 원자성 원칙을 실현하는 핵심적인 단계입니다.   
  •  

상태 전환을 유발하는 연산들

  • BEGIN TRANSACTION: 새로운 트랜잭션을 시작하며 시스템을 활동(Active) 상태로 전환시킵니다.   
  •  
  • 읽기/쓰기 연산: 활동(Active) 상태 내에서 발생합니다.
  • 마지막 연산 실행: 활동(Active) 상태에서 부분 완료(Partially Committed) 상태로 전환됩니다.
  • COMMIT: 부분 완료(Partially Committed) 상태에서 완료(Committed) 상태로 전환됩니다. 이 시점부터는 되돌릴 수 없습니다.
  • ROLLBACK 또는 오류 감지: 활동(Active) 또는 부분 완료(Partially Committed) 상태에서 실패(Failed) 상태로 전환된 후, 시스템이 변경 사항을 되돌리면서 철회(Aborted) 상태로 최종 전환됩니다.

'부분 완료' 상태의 존재는 정교한 데이터베이스 복구 메커니즘이 왜 필수적인지를 설명하는 핵심적인 이유입니다. 이 상태는 논리적인 작업은 모두 끝났지만, 지속성에 대한 보증은 아직 충족되지 않은 '위험 구간'을 나타냅니다. 만약 데이터베이스 시스템이 단순히 변경 사항을 데이터 파일에 직접 쓰고 커밋하는 단순한 방식을 사용한다면, '부분 완료' 상태에서 시스템 장애가 발생했을 때 데이터베이스는 복구 불가능한 비일관적 상태에 빠지게 될 것입니다.

이러한 잠재적 불일치 문제를 해결하기 위해, 현대의 데이터베이스 관리 시스템은 WAL(Write-Ahead Logging)과 같은 엄격한 프로토콜을 구현합니다. WAL 원칙은 트랜잭션의 변경 사항에 대한 로그 레코드를 비휘발성 저장소에 먼저 기록한 후에야, 실제 데이터 파일에 변경 사항을 반영하도록 강제합니다. 따라서 '부분 완료' 상태는 단순히 이론적인 개념이 아니라, 현대 데이터베이스 관리 시스템의 전체 트랜잭션 로깅 및 복구 서브시스템의 존재 이유와 설계를 직접적으로 이끌어낸 원인입니다. 이는 논리적 완료와 물리적 지속성을 분리하는 핵심 개념이며, 내결함성(fault-tolerant)을 갖춘 데이터베이스를 구축하는 기반이 됩니다. 결과적으로, 트랜잭션 상태 다이어그램은 데이터베이스 복구 알고리즘의 청사진 역할을 합니다.

III. 트랜잭션 전파의 이해: 물리적 트랜잭션과 논리적 트랜잭션

전파의 필요성: 중첩된 서비스 호출에서의 데이터 일관성

현대의 엔터프라이즈 애플리케이션은 대부분 컨트롤러(Controller), 서비스(Service), 리포지토리(Repository) 등으로 구성된 계층형 아키텍처(Layered Architecture)를 따릅니다. 이 구조에서 하나의 비즈니스 기능은 여러 서비스 계층의 메서드를 연쇄적으로 호출하여 완성될 수 있으며, 각 서비스 메서드는 독립적으로 트랜잭션 단위로 선언될 수 있습니다.   

 

이때 핵심적인 질문이 발생합니다: 트랜잭션이 설정된 메서드 A가 또 다른 트랜잭션 설정 메서드 B를 호출할 때, 메서드 B는 기존의 트랜잭션 A에 참여해야 하는가, 아니면 완전히 새로운 독립적인 트랜잭션을 시작해야 하는가?. 이 동작 방식을 결정하고 제어하는 규칙을 **트랜잭션 전파(Transaction Propagation)**라고 합니다.   

 

트랜잭션 전파를 올바르게 관리하지 못하면 심각한 데이터 무결성 문제를 야기할 수 있습니다. 예를 들어, 내부의 핵심적인 작업(메서드 B)이 실패하여 롤백되었음에도 불구하고, 이를 호출한 외부 작업(메서드 A)이 자신의 일부 작업만을 커밋해버린다면, 전체 비즈니스 프로세스 관점에서의 원자성이 깨지게 됩니다.   

 

Spring 프레임워크의 접근법: 물리적 및 논리적 트랜잭션 개념

이러한 복잡성을 관리하기 위해, Spring 프레임워크는 두 가지 핵심 개념을 도입하여 추상화 계층을 제공합니다.   

 
  • 물리적 트랜잭션 (Physical Transaction): 이는 실제 데이터베이스 커넥션과 그 커넥션의 트랜잭션 상태를 의미합니다. 데이터베이스에 setAutoCommit(false)를 호출하여 시작되며, COMMIT 또는 ROLLBACK을 통해 종료되는 실체입니다. 일반적으로 하나의 데이터베이스 커넥션 당 하나의 물리적 트랜잭션이 존재합니다.   
  •  
  • 논리적 트랜잭션 (Logical Transaction): 이는 Spring이 관리하는 트랜잭션 범위로, 각각의 @Transactional어노테이션이 붙은 메서드와 연관됩니다. 여러 개의 논리적 트랜잭션이 단 하나의 물리적 트랜잭션에 매핑될 수 있습니다. 이것이 바로 중첩된 트랜잭션 동작을 가능하게 하는 핵심적인 추상화입니다.   
  •  

전파의 기본 원칙 (Spring의 REQUIRED 모델 기준)

여러 논리적 트랜잭션이 하나의 물리적 트랜잭션에 매핑될 때, Spring은 다음과 같은 두 가지 단순하지만 강력한 원칙을 적용합니다.   

 
  1. 모든 논리적 트랜잭션이 커밋을 원해야만 물리적 트랜잭션이 커밋된다. 실제 데이터베이스 커밋은 가장 바깥쪽의 논리적 트랜잭션(즉, 물리적 트랜잭션을 시작시킨 트랜잭션)이 성공적으로 완료될 때 단 한 번만 발생합니다.
  2. 단 하나의 논리적 트랜잭션이라도 롤백을 원하면, 전체 물리적 트랜잭션은 롤백된다. 이는 전체 작업 단위의 원자성을 보장하기 위한 필수적인 규칙입니다.

'물리적 대 논리적' 트랜잭션 모델은 객체 지향적이고 계층화된 애플리케이션 설계 방식과, 데이터베이스 트랜잭션의 평면적이고 커넥션 중심적인 특성 사이의 근본적인 불일치를 해결하는 독창적인 추상화입니다. 이 모델 덕분에 개발자는 객체 지향 프로그래밍에 자연스러운 '메서드 범위' 단위로 트랜잭션을 사고할 수 있으며, 프레임워크는 이러한 논리적 범위들을 단일 원자적 데이터베이스 연산으로 매핑하는 복잡한 세부 사항을 내부적으로 처리합니다.

이 과정을 단계별로 살펴보면, 비즈니스 로직은 재사용성과 관심사 분리를 위해 자연스럽게 여러 메서드와 서비스로 분해됩니다(예: placeOrder()가 updateInventory()와 processPayment()를 호출). 데이터베이스 관점에서는 이 모든 연산이 올바르기 위해 단일 원자 단위, 즉 하나의 물리적 트랜잭션으로 묶여야 합니다. 만약 각 서비스 메서드가 순진하게 자신만의 트랜잭션을 시작하고 커밋한다면, updateInventory()가 성공적으로 커밋된 후 processPayment()가 실패하는 상황이 발생하여 전체적인 원자성이 깨지게 됩니다.

Spring의 논리적 트랜잭션 개념은 이 문제를 해결합니다. placeOrder()가 호출되어 트랜잭션이 시작될 때, Spring은 물리적 트랜잭션을 시작합니다. 이후 placeOrder()가 updateInventory()(기본 전파 속성인 REQUIRED로 설정된)를 호출하면, Spring은 이미 진행 중인 물리적 트랜잭션을 감지하고, 데이터베이스에 새로운 BEGIN TRANSACTION을 보내는 대신, 기존 물리적 트랜잭션에 참여하는 새로운 논리적 범위만을 생성합니다. 이처럼 여러 논리적 범위를 하나의 물리적 단위에 매핑하는 것이 핵심 메커니즘입니다. 이 추상화는 복잡한 호출 스택 전반에 걸쳐 'All-or-Nothing' 원칙을 강제함으로써, 애플리케이션의 구조와 데이터베이스의 트랜잭션 요구사항 사이의 아키텍처적 간극을 효과적으로 메웁니다. 모든 트랜잭션 전파 동작은 이 기반 위에서 구축됩니다.

IV. Spring 트랜잭션 전파 속성 심층 분석

이 섹션에서는 Spring 프레임워크의 @Transactional 어노테이션이 제공하는 7가지 트랜잭션 전파 속성을 심층적으로 분석합니다. 각 속성은 특정 비즈니스 시나리오와 아키텍처 요구사항을 해결하기 위해 설계되었습니다.

A. 핵심 전파 속성: 트랜잭션 경계 관리

REQUIRED (기본값)

  • 동작 방식: 메서드 호출 시점에 이미 진행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 생성합니다. 이는 가장 보편적으로 사용되는 기본 전파 속성입니다.   
  •  
  • 메커니즘: 여러 논리적 트랜잭션을 단일 물리적 트랜잭션에 매핑하는 방식으로 동작합니다. 가장 바깥쪽에서 시작된 트랜잭션이 물리적 트랜잭션의   COMMIT 또는 ROLLBACK을 최종적으로 제어합니다.
  •  
  • 롤백 시나리오: 만약 내부 메서드(참여하는 논리적 트랜잭션)에서 예외가 발생하여 롤백이 필요하게 되면, 해당 논리적 트랜잭션은 공유된 물리적 트랜잭션을 '롤백 전용(rollback-only)' 상태로 표시합니다. 이후 외부 메서드의 로직이 모두 끝나고 COMMIT을 시도하면, 트랜잭션 관리자는 이 롤백 전용 플래그를 확인합니다. 플래그가 설정되어 있으면, 실제로는 ROLLBACK을 수행하고 호출자에게 UnexpectedRollbackException을 던집니다. 이 예외는 호출자에게   COMMIT이 요청되었으나 실제로는 롤백이 수행되었음을 명확히 알려주어, 성공했다고 착각하는 상황을 방지하는 중요한 역할을 합니다.
  •  

REQUIRES_NEW

  • 동작 방식: 항상 새로운, 독립적인 트랜잭션을 생성합니다. 만약 이미 진행 중인 트랜잭션이 있다면, 기존 트랜잭션은 새로운 트랜잭션이 완료될 때까지 일시 중단(suspend)됩니다.   
  •  
  • 메커니즘: 이 속성은 항상 새로운 물리적 트랜잭션을 시작합니다. 이를 위해 데이터베이스 커넥션 풀에서 새로운 커넥션을 획득하며, 기존 트랜잭션과 연결된 커넥션은 잠시 보류됩니다.   
  •  
  • 롤백 시나리오: 내부 트랜잭션과 외부 트랜잭션은 완전히 분리되어 독립적으로 동작합니다. 만약 REQUIRES_NEW로 실행된 내부 트랜잭션이 롤백되더라도, 이는 외부 트랜잭션에 아무런 영향을 미치지 않습니다. 외부 트랜잭션은 내부 메서드 호출에서 발생한 예외를 적절히 처리(catch)한다면, 자신의 작업을 계속 진행하고 성공적으로 COMMIT할 수 있습니다. 이 속성은 주 비즈니스 로직의 성공 여부와 관계없이 반드시 독립적으로 성공 또는 실패해야 하는 감사(auditing) 로그 기록, 알림 발송과 같은 부가적인 작업에 이상적입니다.   
  •  

NESTED

  • 동작 방식: 이미 진행 중인 트랜잭션이 있으면 '중첩된 트랜잭션(nested transaction)'을 생성합니다. 진행 중인 트랜잭션이 없으면 REQUIRED와 동일하게 새로운 트랜잭션을 생성합니다.   
  •  
  • 메커니즘: 이 속성은 REQUIRES_NEW와 달리 단일 물리적 트랜잭션 내에서 동작하지만, 데이터베이스의 세이브포인트(Savepoint) 기능을 사용합니다. 중첩 트랜잭션이 시작될 때 세이브포인트가 설정됩니다.   
  •  
  • 롤백 시나리오: 만약 NESTED로 실행된 내부 트랜잭션이 롤백되면, 데이터베이스 상태는 해당 중첩 트랜잭션이 시작될 때 설정된 세이브포인트 시점으로만 되돌아갑니다. 따라서 외부 트랜잭션은 내부 트랜잭션의 실패와 상관없이 자신의 작업을 계속 진행하고 COMMIT할 수 있습니다. 하지만 만약   외부 트랜잭션이 롤백되면, 전체 물리적 트랜잭션이 롤백되므로 성공적으로 커밋된 내부 중첩 트랜잭션의 작업까지 모두 취소됩니다. 이는   REQUIRES_NEW처럼 새로운 커넥션을 사용하는 부담 없이 REQUIRED보다 더 세밀한 롤백 제어를 제공합니다. 단, 이 기능은 JDBC 드라이버와 데이터베이스가 세이브포인트를 지원해야만 사용 가능하며, 모든 환경에서 보편적으로 지원되지는 않습니다.   
  •  
  •  
  •  

REQUIRES_NEW와 NESTED 사이의 선택은 단순히 롤백 동작의 차이를 넘어, 리소스 관리와 데이터베이스 종속성에 대한 깊은 아키텍처적 결정을 의미합니다. REQUIRES_NEW는 성능(추가 커넥션 오버헤드)과 교착 상태(deadlock) 발생 가능성을 희생하는 대신 완전한 격리와 데이터베이스 독립성을 제공합니다. 반면, NESTED는 더 가볍고 효율적인 부분 롤백을 제공하지만, 애플리케이션의 트랜잭션 로직을 특정 데이터베이스 기능(세이브포인트)에 종속시켜 잠재적으로 이식성을 저해할 수 있습니다.

이 선택 과정을 구체적으로 살펴보면, 개발자가 부분 롤백이 필요한 상황에 직면했을 때 두 가지 옵션을 고려하게 됩니다. REQUIRES_NEW는 새로운 독립 트랜잭션을 생성하여 직관적으로 보입니다. 이 방식의 장점은 내부 트랜잭션의 잠금(lock)이 해당 트랜잭션 종료 시 즉시 해제되어 외부 트랜잭션에 영향을 주지 않는다는 점입니다. 하지만 그 대가로 실행 시간 동안 커넥션 풀에서 두 번째 데이터베이스 커넥션을 점유하게 됩니다. 동시 사용자가 많은 시스템에서 다수의 외부 트랜잭션이   

 

REQUIRES_NEW 내부 트랜잭션을 기다리며 일시 중단되면, 커넥션 풀이 고갈되어 시스템 전체 장애로 이어질 수 있습니다.

반면, NESTED는 동일한 커넥션과 물리적 트랜잭션을 재사용하여 리소스 측면에서 더 효율적으로 보입니다. 하지만 이 방식은 모든 데이터베이스나 JDBC 드라이버에서 표준적으로 지원하는 기능이 아닙니다.   

 

NESTED를 선택한다는 것은, 만약 나중에 데이터베이스를 PostgreSQL에서 세이브포인트 지원이 미흡한 다른 시스템으로 변경할 경우 애플리케이션의 트랜잭션 로직이 깨질 수 있는 위험을 감수하는 것입니다. 따라서 이 결정은 REQUIRES_NEW가 성능 및 교착 상태 위험과 데이터베이스 독립성을 맞바꾸는 것이고, NESTED가 리소스 효율성과 데이터베이스 이식성을 맞바꾸는 고전적인 아키텍처 트레이드오프 문제입니다.

표 2: 핵심 전파 속성 롤백 동작 및 메커니즘 분석 (REQUIRED, REQUIRES_NEW, NESTED)

속성 물리적 트랜잭션 관리 커밋/롤백 범위 내부 롤백이 외부에 미치는 영향 기반 메커니즘
REQUIRED 단일 물리적 트랜잭션 공유 전체 물리적 트랜잭션 단위 전체 물리적 트랜잭션을 롤백시킴. 외부 호출자는 커밋 시도 시 UnexpectedRollbackException을 받음. 논리적 트랜잭션 스코프, 롤백 전용 플래그
REQUIRES_NEW 항상 새로운 물리적 트랜잭션 생성 (기존 트랜잭션은 일시 중단) 각 물리적 트랜잭션이 독립적 외부 트랜잭션에 영향을 주지 않음. 외부 트랜잭션은 독립적으로 커밋 또는 롤백 가능. 새로운 DB 커넥션 획득
NESTED 단일 물리적 트랜잭션 공유 외부 트랜잭션은 전체, 내부는 세이브포인트까지 외부 트랜잭션에 영향을 주지 않음 (부분 롤백). 단, 외부 트랜잭션이 롤백되면 내부도 함께 롤백됨. JDBC 세이브포인트

B. 보조 전파 속성: 유연한 트랜잭션 지원

SUPPORTS

  • 동작 방식: 이미 진행 중인 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 코드를 실행합니다.   
  •  
  • 사용 사례: 주로 읽기 전용 로직이나 트랜잭션이 선택 사항인 경우에 사용됩니다. 예를 들어, 데이터를 조회하는 서비스 메서드는 트랜잭션 오버헤드 없이 더 빠르게 실행될 수 있지만, 만약 다른 트랜잭션 내에서 호출될 경우 해당 트랜잭션에 참여하여 일관성 있는 데이터를 읽도록 보장할 수 있습니다.   
  •  

NOT_SUPPORTED

  • 동작 방식: 항상 트랜잭션 없이 코드를 실행합니다. 만약 현재 진행 중인 트랜잭션이 있다면, 해당 메서드가 실행되는 동안 일시 중단됩니다.   
  •  
  • 사용 사례: 트랜잭션 컨텍스트에 포함되어서는 안 되는 작업을 수행할 때 유용합니다. 예를 들어, 외부 시스템 API 호출, 이메일 알림 발송, 비-트랜잭션 리소스에 접근하는 작업 등이 해당됩니다. 이를 통해 장시간 소요될 수 있는 비-데이터베이스 작업을 위해 외부 트랜잭션이 불필요하게 오래 유지되는 것을 방지할 수 있습니다.   
  •  

C. 제한적 전파 속성: 트랜잭션 정책 강제

MANDATORY

  • 동작 방식: 반드시 이미 진행 중인 트랜잭션 내에서 호출되어야 합니다. 만약 활성 트랜잭션이 없는 상태에서 호출되면 IllegalTransactionStateException 예외를 발생시킵니다.   
  •  
  • 사용 사례: 설계상의 계약을 강제하는 데 사용됩니다. 이는 서비스 메서드가 "나는 트랜잭션을 시작할 책임이 없으며, 나를 호출하는 쪽에서 반드시 트랜잭션을 제공해야 한다"고 선언하는 것과 같습니다. 더 큰 비즈니스 트랜잭션의 일부로만 사용되도록 설계된 유틸리티나 리포지토리 메서드에 적합합니다.   
  •  

NEVER

  • 동작 방식: 반드시 트랜잭션이 없는 상태에서 호출되어야 합니다. 만약 활성 트랜잭션이 있는 상태에서 호출되면 IllegalTransactionStateException 예외를 발생시킵니다.   
  •  
  • 사용 사례: MANDATORY의 논리적 반대 개념입니다. 배치 처리 초기화나 정적 데이터 로딩과 같이, 트랜잭션 컨텍스트에 의도치 않게 포함될 경우 불필요한 잠금을 유발하거나 다른 부작용을 일으킬 수 있는 특정 작업들이 트랜잭션 내에서 실행되는 것을 원천적으로 차단하기 위해 사용됩니다.   
  •  

표 1: Spring 트랜잭션 전파 속성 종합 비교

전파 속성 설명 활성 트랜잭션 존재 시 활성 트랜잭션 부재 시 주요 사용 사례
REQUIRED 트랜잭션이 필요함 (기본값) 기존 트랜잭션에 참여 새로운 트랜잭션 생성 일반적인 CRUD 서비스 메서드
REQUIRES_NEW 항상 새로운 트랜잭션이 필요함 기존 트랜잭션을 일시 중단하고 새로운 트랜잭션 생성 새로운 트랜잭션 생성 감사 로그, 알림 등 독립적 작업
NESTED 중첩 트랜잭션을 생성함 중첩 트랜잭션 생성 (세이브포인트) 새로운 트랜잭션 생성 복잡한 비즈니스 로직 내 부분 롤백
SUPPORTS 트랜잭션이 있으면 지원함 기존 트랜잭션에 참여 트랜잭션 없이 실행 읽기 전용 또는 선택적 트랜잭션 작업
NOT_SUPPORTED 트랜잭션을 지원하지 않음 기존 트랜잭션을 일시 중단하고 트랜잭션 없이 실행 트랜잭션 없이 실행 외부 API 호출, 비-트랜잭션 리소스 접근
MANDATORY 트랜잭션이 의무적으로 필요함 기존 트랜잭션에 참여 IllegalTransactionStateException예외 발생 반드시 트랜잭션 내에서 실행되어야 하는 헬퍼 메서드
NEVER 트랜잭션을 사용하지 않음 IllegalTransactionStateException예외 발생 트랜잭션 없이 실행 트랜잭션 컨텍스트에서 실행되면 안 되는 초기화 작업

V. 결론: 트랜잭션 상태 및 전파 속성의 전략적 적용

주요 분석 결과 요약

본 보고서는 데이터베이스 트랜잭션의 생명주기와 전파 모델에 대한 심층적인 분석을 제공했습니다. 트랜잭션의 상태 전환 모델은 데이터베이스의 신뢰성과 복구 능력의 근간을 이루며, 특히 '부분 완료' 상태는 정교한 로깅 및 복구 메커니즘의 필요성을 명확히 보여줍니다. Spring 프레임워크가 제공하는 '물리적-논리적' 트랜잭션 추상화는 계층적 애플리케이션 아키텍처와 데이터베이스의 원자적 요구사항 사이의 간극을 효과적으로 메우는 강력한 솔루션입니다. 7가지 전파 속성은 개발자가 다양한 비즈니스 시나리오에 맞춰 트랜잭션의 경계와 동작을 세밀하게 제어할 수 있는 도구를 제공하며, 각 속성은 뚜렷한 목적과 트레이드오프를 가집니다.

사용 사례별 최적의 전파 속성 선택 가이드라인

  • REQUIRED: 표준적인 서비스 계층의 비즈니스 로직에 기본값으로 사용합니다. 이를 통해 관련된 모든 데이터베이스 작업을 단일 원자 단위로 묶어 일관성을 보장할 수 있습니다.
  • REQUIRES_NEW: 주 트랜잭션의 성공 여부와 관계없이 독립적으로 커밋 또는 롤백되어야 하는 부가적인 작업(예: 감사 로그 기록, 이벤트 발행, 알림 발송)에 사용합니다.
  • NESTED: 단일 원자적 작업 내에서 부분적인 롤백이 필요한 복잡한 비즈니스 로직에 고려할 수 있습니다. 단, 사용 전에 반드시 데이터베이스와 JDBC 드라이버의 세이브포인트 지원 여부를 확인해야 합니다.
  • SUPPORTS: 트랜잭션이 필수는 아니지만, 기존 트랜잭션에 참여함으로써 일관된 데이터 뷰를 얻을 수 있는 읽기 전용 메서드에 적합합니다.
  • NOT_SUPPORTED: 장시간 소요될 수 있는 비-데이터베이스 작업(외부 API 호출 등)을 트랜잭션 컨텍스트로부터 명시적으로 분리하여 불필요한 리소스 점유를 방지할 때 사용합니다.
  • MANDATORY  NEVER: 특정 서비스 메서드의 오용을 방지하고 아키텍처적 계약을 강제하기 위한 방어적 프로그래밍 도구로 활용합니다.

트랜잭션 설계 시 핵심 고려사항 및 권장사항

  • 자기 호출(Self-Invocation) 문제 주의: Spring의 AOP 프록시 기반 트랜잭션 관리는 클래스 내부에서 this.method()와 같이 자기 자신의 다른 메서드를 호출할 경우 프록시를 우회하게 만듭니다. 이 경우 호출된 메서드의 @Transactional 설정은 무시되므로, 이는 매우 흔하면서도 발견하기 어려운 버그의 원인이 됩니다.   
  •  
  • 커넥션 풀 크기 산정: REQUIRES_NEW 속성을 사용하면 단일 요청 처리 중에 여러 개의 데이터베이스 커넥션을 동시에 사용할 수 있습니다. 따라서 커넥션 풀의 크기는 동시에 실행될 수 있는 외부 트랜잭션과 그에 따른 내부 트랜잭션의 수를 모두 감당할 수 있도록 충분히 크게 설정해야 합니다.   
  •  
  • 예외 처리 전략: 기본적으로 Spring 트랜잭션은 확인되지 않은 예외(Unchecked Exception, 즉 RuntimeException의 하위 클래스)가 발생할 때만 롤백을 수행합니다. 확인된 예외(Checked Exception)에 대해서도 롤백을 수행하거나 특정 예외에 대해 롤백을 방지하려면 @Transactional의 rollbackFor  noRollbackFor 속성을 명시적으로 설정해야 합니다.   
  •  
  • 복잡성보다 명확성 추구: 다양한 전파 속성은 강력한 기능을 제공하지만, 과도하게 복잡한 규칙은 애플리케이션의 동작을 예측하고 디버깅하기 어렵게 만듭니다. 가능한 한 트랜잭션 경계를 단순하고 명확하게 유지하고, 고급 전파 속성은 신중하게, 명확한 문서화와 함께 사용하는 것이 바람직합니다.

 

이렇게 트렌젝션에 대해 조사해보았다.

그동안 데이터의 정합성을 어떻게 관리하지? -> 트렌젝션 관리를 통해서 한다 라고 알고만 있었지 이렇게 자세히 조사해본 적은 없었다.

이번 과제를 통해 트렌젝션이 정확히 뭔지 어떤 식으로 전파하는지 등에 대해 알 수 있었다.

아직 본문의 내용중에 이해가 안되는 부분도 많지만 계속 공부를 하면서 계속 읽다보면 이해가 되는 부분이 늘어갈 거라고 생각된다.