PostgreSQL 트랜잭션 격리와 MVCC 핵심 이해
PostgreSQL의 MVCC 작동 원리와 각 트랜잭션 격리 수준이 데이터 일관성에 미치는 영향을 사례 중심으로 설명하는 기술 자료
목차
소개
데이터베이스에서 동시성 제어는 성능과 일관성 사이의 균형을 요구한다. PostgreSQL은 MVCC(Multi-Version Concurrency Control)를 기반으로 하며, 서로 다른 트랜잭션이 충돌 없이 데이터를 읽고 쓸 수 있게 설계되어 있다. 이 글에서는 MVCC의 기본 개념과 PostgreSQL에서 제공하는 트랜잭션 격리 수준의 차이를 처음 접하는 사람도 이해하기 쉽게 정리한다.
MVCC 기본 개념
MVCC는 각 행의 여러 버전을 관리함으로써 읽기 작업이 쓰기 작업을 블록하지 않도록 한다. 트랜잭션이 시작되면 스냅샷을 얻고, 그 시점의 데이터 상태를 기준으로 읽기를 수행한다. 쓰기 작업은 새로운 버전을 생성하며 기존 버전은 유효성 검사와 가비지 수집을 위해 유지된다.
버전 식별자와 가시성
각 행 버전은 생성 트랜잭션과 삭제 트랜잭션 정보를 가진다. 트랜잭션이 어떤 버전을 볼 수 있는지는 그 트랜잭션의 스냅샷 타임스탬프와 버전의 생성/삭제 시점에 따라 결정된다. 이렇게 하면 오래 실행되는 읽기 작업이 짧은 쓰기 작업을 방해하지 않는다.
트랜잭션 격리 수준과 특징
SQL 표준은 네 가지 격리 수준을 정의한다. PostgreSQL은 내부적으로 이들 중 일부를 구현하며, 각 수준은 동시성 문제(더티 리드, 반복 불가능한 읽기, 팬텀)를 다르게 허용하거나 차단한다.
Read Uncommitted
PostgreSQL은 사실상 Read Committed와 동일한 동작을 제공하므로 Read Uncommitted의 더티 리드를 허용하지 않는다.
Read Committed
각 쿼리는 커밋된 최신 데이터를 본다. 같은 트랜잭션 내에서 쿼리를 여러 번 실행하면 그때그때 최신 커밋 상태가 반영될 수 있어 반복 불가능한 읽기가 발생할 수 있다. 기본 모드로 많은 OLTP 워크로드에 적합하다.
Repeatable Read
트랜잭션 시작 시점의 스냅샷을 유지한다. 같은 트랜잭션 내에서 반복 쿼리는 일관된 결과를 제공한다. PostgreSQL은 MVCC 구현을 통해 이 수준에서 팬텀과 반복 불가능한 읽기를 방지한다.
Serializable
가장 엄격한 수준으로, 동작은 직렬화된 실행과 동일해야 한다. PostgreSQL은 실제로는 낙관적 동시성 제어와 충돌 검출을 사용한다. 충돌이 감지되면 트랜잭션을 강제로 롤백시켜 직렬화 일관성을 보장한다.
실제 동작 예시
간단한 예로 잔액 전송 시나리오를 보자. 두 트랜잭션이 동시에 같은 계좌를 읽고 갱신하면 격리 수준에 따라 결과가 달라진다.
예제 SQL
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
동일한 시간에 다른 트랜잭션이 동일한 행을 업데이트하면 Read Committed에서는 잠금으로 인해 순차적으로 실행될 수 있다. 하지만 Serializable에서는 낙관적 충돌 검출로 인해 충돌하면 한 쪽 트랜잭션이 롤백된다.
serializable postgres 동작 상세
Serializable 모드에서 PostgreSQL은 트랜잭션 간의 의존성을 추적한다. 순환 의존성이 발견되면 하나를 실패시키는 방식으로 직렬화가 유지된다. 따라서 애플리케이션은 트랜잭션이 실패했을 때 재시도를 설계해야 한다.
운영 관점에서 고려사항
- 성능 vs 일관성: 높은 격리 수준은 롤백과 재시도가 잦아져 처리량이 낮아질 수 있다.
- 가비지 컬렉션: MVCC는 오래된 버전을 유지하므로 VACUUM을 적절히 실행해야 한다.
- 잠금과 충돌: 빈번한 업데이트 대상에는 충돌 가능성이 높으므로 설계를 단순화하거나 파티셔닝을 고려한다.
결론
PostgreSQL의 MVCC는 읽기 성능을 높이면서도 일관성을 유지하는 강력한 메커니즘이다. 트랜잭션 격리 수준은 데이터 일관성 요구와 성능 제약을 고려해 선택해야 한다. 특별히 직렬화가 필요하면 Serializable을 사용하되 재시도 로직을 준비하는 것이 안전하다. 이 글은 postgres mvcc 설명과 트랜잭션 격리 수준 postgres, serializable postgres 동작에 대한 이해를 돕는 기초 자료로 활용될 수 있다.