트랜잭션
개요
트랜잭션(transaction)은 관련된 데이터 조작을 하나의 작업 단위로 취급하는 개념이다. 계좌 이체에서 보내는 계좌의 잔액 감소와 받는 계좌의 잔액 증가를 따로 확정하면, 둘 사이의 장애로 돈이 사라지거나 늘어날 수 있다. 두 변경을 같은 작업의 일부로 묶으면 전부 확정하거나 전부 취소하는 기준을 세울 수 있다.
데이터베이스에서는 여러 문장으로 표현된 작업도 하나의 트랜잭션에 들어갈 수 있다. 작업이 성공해 결과를 확정하는 동작이 커밋(commit)이고, 진행 중인 변경을 취소하는 동작이 롤백(rollback)이다. 사용자에게 보이는 주문 한 건이 DB 트랜잭션 하나와 정확히 일치하는지는 애플리케이션의 경계 설정에 달려 있다.
묶어서 확정하는 성질만으로는 동시 실행까지 설명할 수 없다. 같은 좌석을 두 사람이 동시에 예약하거나, 서로 다른 행을 바꾸는 두 작업이 함께 하나의 규칙을 어기는 문제도 있다. 트랜잭션은 복구할 작업 단위와 함께, 여러 작업이 겹칠 때 어떤 실행을 허용할지도 다루며 이 둘은 원자성과 격리성이라는 다른 질문으로 나뉜다.
이 문서는 트랜잭션을 특정 DB의 BEGIN 문법에서 출발한 기능으로 보지 않고, 장애를 견디는 데이터 처리의 단위를 어떻게 정의해 왔는지부터 살펴본다. SQL 격리 수준과 MVCC는 그 개념을 구현하고 조정하는 과정에서 만나는 용어다.
1. 기록을 복구하는 문제에서 작업 단위로
처리가 여러 번의 저장으로 이루어지면 장애가 발생한 지점에 따라 일부 변경만 남을 수 있다. 복구 시스템에는 어느 변경들이 같은 작업에 속하고, 어디까지 성공으로 받아들였는지를 알아낼 정보가 필요하다. 트랜잭션 경계와 커밋 기록은 이 판단의 기준이 된다.
1976년 Eswaran·Gray·Lorie·Traiger의 논문은 트랜잭션과 실행 순서, 일관성과 잠금의 관계를 형식화했다. 여러 사용자가 공유 데이터를 동시에 다루는 상황에서, 개별 작업의 올바름을 어떤 실행 규칙으로 유지할지를 다룬 연구다.1
Jim Gray의 1981년 「The Transaction Concept: Virtues and Limitations」는 트랜잭션을 상태의 변환으로 설명하며 원자성·일관성·내구성과 적용 범위를 논의했다. 논문은 DB 안에서 보호되는 동작과 외부 세계에서 일어난 동작을 구별한다. 변경 기록을 되돌리는 일과 실제로 배출한 현금을 회수하는 일의 차이처럼, 복구의 경계를 개념의 일부로 보았다.2
2. 1983년 ACID가 정리한 성질
Theo Härder와 Andreas Reuter는 1983년 데이터베이스 복구 논문에서 네 성질을 ACID라는 이름으로 묶어 설명했다. 이는 앞서 발전하던 트랜잭션 개념의 성질을 정리한 기록이다.3
성질 | 묻는 내용 | 이체 예제에서의 의미 |
|---|---|---|
Atomicity, 원자성 | 작업의 일부만 확정되는가 | 출금과 입금을 함께 확정하거나 함께 취소한다 |
Consistency, 일관성 | 작업이 정한 무결성 규칙을 유지하는가 | 금액과 잔액에 관한 규칙을 만족하도록 변환한다 |
Isolation, 격리성 | 함께 실행되는 작업이 어떻게 영향을 주는가 | 중간 상태와 충돌을 어떤 실행 규칙으로 다룬다 |
Durability, 내구성 | 확정한 결과가 장애 뒤에도 남는가 | 커밋한 이체 기록을 저장·복구 계약에 따라 유지한다 |
- 성질
Atomicity, 원자성
- 묻는 내용
작업의 일부만 확정되는가
- 이체 예제에서의 의미
출금과 입금을 함께 확정하거나 함께 취소한다
- 성질
Consistency, 일관성
- 묻는 내용
작업이 정한 무결성 규칙을 유지하는가
- 이체 예제에서의 의미
금액과 잔액에 관한 규칙을 만족하도록 변환한다
- 성질
Isolation, 격리성
- 묻는 내용
함께 실행되는 작업이 어떻게 영향을 주는가
- 이체 예제에서의 의미
중간 상태와 충돌을 어떤 실행 규칙으로 다룬다
- 성질
Durability, 내구성
- 묻는 내용
확정한 결과가 장애 뒤에도 남는가
- 이체 예제에서의 의미
커밋한 이체 기록을 저장·복구 계약에 따라 유지한다
일관성의 규칙에는 DB의 제약 조건뿐 아니라 애플리케이션이 구현한 업무 조건도 포함된다. 트랜잭션은 잘못 작성한 금액 계산의 의도를 알아내지 못한다. 어떤 상태가 올바른지는 스키마와 프로그램이 표현해야 한다.
3. 커밋과 복구는 어떻게 연결되는가
대표적인 구현에서는 로그에 변경과 확정 여부를 남기고, 장애 뒤 그 기록을 사용해 커밋한 작업을 복원하거나 미완료 작업을 처리한다. write-ahead logging은 데이터 페이지보다 먼저 필요한 로그를 보존하는 규칙을 중심에 둔다. 체크포인트는 복구할 때 어느 지점부터 살펴볼지 줄이는 데 사용된다.3
로그가 있다고 해서 모든 제품이 같은 복구 알고리즘이나 내구성 설정을 사용하는 것은 아니다. 중요한 연결은, 성공 응답을 어느 시점의 기록 보존과 결부시키느냐다. 복구 전략과 저장 장치의 계약이 내구성을 구체화한다.
4. 격리 수준은 왜 여러 개인가
겹친 작업을 전부 직렬로 실행하면 이해하기는 쉽지만, 독립적인 작업도 기다리게 된다. 데이터베이스는 더 많은 작업을 함께 처리하면서 허용할 관찰 결과를 구분해 왔다. SQL의 격리 수준은 dirty read, nonrepeatable read, phantom 같은 이상 현상과 연결해 설명된다.
Berenson 등의 1995년 「A Critique of ANSI SQL Isolation Levels」는 당시 정의의 모호함을 분석하고, 스냅샷 격리와 쓰기 왜곡(write skew)을 다뤘다. 같은 스냅샷을 읽는다는 사실과 전체 실행이 어떤 직렬 순서와 같다는 성질 사이의 차이를 보여준 연구다.4
예를 들어 Alice와 Bob이 모두 당직 중이고 최소 한 명이 남아야 한다고 하자. 두 트랜잭션이 각각 당직자가 두 명이라고 읽고 자신의 행만 근무 종료로 바꾸면, 같은 행을 덮어쓰지 않았어도 최종 당직자는 0명이 된다. 한 작업씩 끝냈다면 두 번째 작업은 남은 사람이 자신뿐임을 보았을 것이다.
직렬가능성(serializability)은 성공한 작업들의 효과가 어떤 직렬 실행 순서와 같아야 한다는 요구다. 실제 구현은 작업을 겹쳐 실행하면서 충돌하는 경우를 대기시키거나 중단해 이 요구를 맞출 수 있다.
5. MVCC는 격리를 구현하는 재료다
다중 버전 동시성 제어(MVCC)는 여러 버전의 데이터를 유지해 읽는 작업이 적절한 버전을 선택하도록 한다. 어떤 시점의 버전을 읽고 어떤 의존성 충돌을 허용하는지는 격리 규칙이 정한다.
PostgreSQL 18의 일반 SELECT에서는 Read Committed가 문장마다 스냅샷을 얻고, Repeatable Read가 트랜잭션에서 유지하는 스냅샷을 사용한다. 이 제품의 Repeatable Read는 팬텀을 방지하지만 쓰기 왜곡 같은 직렬화 이상을 허용할 수 있다. Serializable은 직렬화할 수 없는 실행을 중단할 수 있으므로 호출자에게 실패한 작업을 처음부터 다시 판단하는 처리가 필요하다.5
이 사례에서 구분할 축은 데이터 버전의 보관, 읽을 버전의 선택, 충돌 검출, 중단 이후의 처리다. MVCC라는 이름 하나에 이 결정들이 모두 들어 있지는 않다.
6. 가장 작은 롤백 예제
다음 PostgreSQL 예제는 주문 기록과 재고 변경의 확정 범위를 보여준다. 동시 주문 처리를 구현하려는 코드가 아니라, 두 쓰기를 같은 작업으로 묶는 의미를 확인하기 위한 예제다.
CREATE TEMP TABLE stock (id integer PRIMARY KEY, quantity integer NOT NULL);
CREATE TEMP TABLE orders (id integer PRIMARY KEY, item_id integer NOT NULL);
INSERT INTO stock VALUES (1, 1);
BEGIN;
UPDATE stock SET quantity = quantity - 1 WHERE id = 1;
INSERT INTO orders VALUES (100, 1);
ROLLBACK;
SELECT quantity FROM stock WHERE id = 1;
SELECT count(*) FROM orders;
마지막 조회는 재고 1, 주문 수 0을 돌려준다. 두 변경을 확정하지 않고 함께 취소했기 때문이다. 같은 위치에서 COMMIT하면 두 변경을 확정한다.
7. 데이터베이스 밖으로 경계가 넓어질 때
한 DB에 주문과 재고가 있으면 같은 로컬 트랜잭션에 넣을 수 있다. 주문 DB의 커밋과 다른 서비스의 메일 발송을 함께 다루려면 저장소의 경계를 넘어선 조정이 필요하다. 분산 트랜잭션, 사가, 트랜잭셔널 아웃박스가 다루는 문제는 여기서 갈라진다.
아웃박스는 업무 변경과 발행할 사건을 같은 DB에 기록하고 전달을 뒤로 보낸다. 사가는 여러 단계의 장기 작업을 보상 동작과 함께 구성한다. 외부 시스템의 효과까지 로컬 롤백과 같은 의미로 부르면 각 방식이 책임지는 범위를 놓치기 쉽다.
동시 실행의 순서와 충돌은 「동시성 제어」에서, 업무 변경을 메시지로 이어가는 구조는 「트랜잭셔널 아웃박스」에서 더 자세히 다룬다. 트랜잭션의 단위가 무엇인지 먼저 정해야 이 구현들의 보장도 비교할 수 있다.
각주
- K. P. Eswaran·J. N. Gray·R. A. Lorie·I. L. Traiger, 「The Notions of Consistency and Predicate Locks in a Database System」, CACM 19(11), 1976. 저자 기관 서지와 초록. ↩
- Jim Gray, 「The Transaction Concept: Virtues and Limitations」, VLDB, 1981. 저자 제공 원문. ↩
- Theo Härder·Andreas Reuter, 「Principles of Transaction-Oriented Database Recovery」, ACM Computing Surveys 15(4), 1983, pp.287–317. 원문 사본. ↩
- Hal Berenson 외, 「A Critique of ANSI SQL Isolation Levels」, SIGMOD, 1995, §4.2 및 A5B. 연구기관 원문. ↩
- PostgreSQL 18, §13.2 “Transaction Isolation”. 공식 문서. ↩