2026. 3. 31. 17:19ㆍCS/DB
이번 글에서는 사내에서 높은 신뢰성과 확장성, 그리고 강력한 데이터 무결성을 위해 널리 채택하고 있는 PostgreSQL에 대해 학습한 내용을 공유하고자 합니다. 특히 PostgreSQL의 핵심 메커니즘 중 하나인 MVCC(Multi-Version Concurrency Control)의 동작 원리를 깊이 있게 파악하는 데 초점을 맞추었습니다
1. PostgreSQL의 특징
PostgreSQL는 다른 RDBMS와 비교했을 때 가장 큰 장점은 '타협하지 않는 엄격함'입니다. 이와 관련된 여러가지 특징이 있습니다.
- 향상된 체크 제약 조건(Check Constraints): not null 뿐만아니라 아주 복잡한 조건을 DB 레벨에서 강제할 수 있습니다.
예를 들어 "재고 수량은 반드시 0보다 커야 하며, 입고일은 출고일보다 빨라야 한다"는 조건을 설정하면, 어떤 애플리케이션 버그로 인해 잘못된 값이 들어와도 DB가 잘못된 값이 들어오지 못하도록 막습니다.
- 원자성 보장(Write-Ahead Logging): 데이터를 디스크에 실제로 쓰기 전에, 변경 이력을 로그에 먼저 기록합니다.
만약 데이터를 저장하는 도중에 갑자기 서버 전원이 나가더라도, 로그를 보고 어디까지 저장됐는지를 복구할 수 있어 데이터 유실 가능성을 낮출 수 있습니다.
- 동시성 제어 (MVCC)의 정교함: PostgreSQL은 데이터를 수정할 때 원본을 바로 덮어쓰지 않고 새로운 버전을 만듭니다.
덕분에 읽기 작업이 진행중일 때 쓰기 작업이 들어와도 서로를 기다리지 않고 자신이 작업을 시작한 시점에 일관된 데이터를 볼 수 있습니다.
이 글에서는 3가지 큰 특징 중에서도 MVCC에 대해서 심층도 있게 학습한 내용을 공유 해보려고 합니다.
2. PostgreSQL의 MVCC
PostgreSQL의 MVCC 핵심 메커니즘을 이해하기 위해서는 3가지 요소에 대해서 이해해야합니다.
- 스냅샵(SnapShot)과 튜플(Tuple)의 구조
- 수정(update)시 발생하는 과정: "insert +delete"
- 청소부 역할: VACCUM
먼저 스냅샷과 튜플의 구조에 대해서 이해해보겠습니다. PostgreSQL에서 테이블의 한 행을 저장하는 단위를 튜플이라고 부릅니다. 각 튶플에는 눈에 보이지 않는 두 가지 중요한 타임스탬프 정보가 숨어있습니다.
- xmin (insert Id): 이 데이터를 생성한 트랜잭션의 ID
- xmax (delete Id): 이 데이터를 삭제하거나 수정한 트랜잭션의 ID (수정하지 않았다면 0)
xmin, xmax는 PostgreSQL의 오픈소스에서 확인해볼수 도 있는데 구조체 내의 멤버변수를 의미합니다. (네이밍에 대해서 궁금해서 찾아봤습니다. )
두번째는 수정시 발생하는 과정(VACCUM 포함)에 대해서 심층도 있게 보려고합니다. PostgreSQL는 데이터를 수정할 때 기존 데이터를 직접 고치지 않고 다음 4단계를 거칩니다.
1단계: 초기 상태 (데이터 삽입 직후)
가장 먼저 데이터가 처음 저장된 상태입니다. 모든 데이터 행(Tuple)은 눈에 보이지 않는 xmin(생성 트랜잭션 ID)과 xmax(삭제/수정 트랜잭션 ID) 정보를 가지고 있습니다.
- xmin: 100 (100번 트랜잭션이 만들었음)
- xmax: 0 (아직 삭제되거나 수정되지 않은 유효한 데이터)

2단계: 데이터 수정 (UPDATE) 발생
이제 101번 트랜잭션이 Alice의 잔액을 200으로 수정하는 UPDATE 명령을 내립니다.
그림을 보면 원본(Tuple A)은 그대로 두고, 옆에 새 데이터(Tuple B)를 추가합니다.
- Tuple A (원본): xmax가 101로 바뀝니다. "101번 트랜잭션에 의해 삭제 예정"이라는 뜻입니다.
- Tuple B (신규): xmin이 101인 새로운 행이 생깁니다. 잔액은 200입니다.

3단계: 커밋 후 가시성 (조회 시점의 차이)
101번 트랜잭션이 성공적으로 완료(Commit)되었습니다. 이제 디스크에는 두 개의 버전(A, B)이 공존합니다.
이 부분이 MVCC의 핵심입니다. 조회하는 사람이 언제 작업을 시작했느냐에 따라 보는 데이터가 다릅니다.
- 사용자 A (102번): 데이터 수정 전에 이미 조회를 시작했으므로, 구버전인 Tuple A를 봅니다.
- 사용자 B (103번): 수정이 끝난 후 조회를 시작했으므로, 신버전인 Tuple B를 봅니다.
결과적으로 수정 중에도 읽기 작업이 멈추지 않고 대기 없이 바로 데이터를 볼 수 있습니다.

4단계: 사후 정리 (VACUUM 실행)
시간이 지나서 더 이상 구버전(Tuple A)을 참조하는 트랜잭션이 하나도 없게 되었습니다. 이때 VACUUM 프로세스가 등장합니다.
그림처럼 VACUUM은 쓸모없어진 구버전을 지우고, 그 자리를 나중에 다시 사용할 수 있도록 만듭니다.
- Tuple A (삭제): 물리적으로 지워집니다.
- 빈 공간 (Reusable Space): 새로운 데이터가 들어올 수 있는 빈자리로 변합니다.

마무리
PostgreSQL는 MVCC 덕분에 대규모 트래픽에서도 데이터의 일관성을 유지할 수 있도록 지원해 많은 개발자로부터 선택되어 사용되고 있습니다. 이번에 처음으로 PostgreSQL를 사용하게 됬지만, "왜 PostgreSQL이어야 하지?"라는 개인적인 물음에 답을 줄수 있는 공부였던것 같습니다.
'CS > DB' 카테고리의 다른 글
| LAG 함수 사용과 쿼리 실행 계획 및 성능 검증 (0) | 2024.11.17 |
|---|---|
| 인덱스로 성능 개선하기 (feat. B-Tree 인덱스) (0) | 2024.08.11 |
| 프로시저(Procedure) 동작 방식과 동시성 문제 (1) | 2024.07.08 |