백엔드 데이터 인프라 37편. MVCC 의 큰 그림 — 읽는 사람과 쓰는 사람이 서로 막지 않는 PG 핵심 메커니즘 풀어쓴 학습 노트.
이 글은 백엔드 데이터 인프라 시리즈 70편 중 37편이에요. 17편 트랜잭션 ·24편 DML 개요 에서 잠깐 본 MVCC 의 큰 그림.
MVCC 란
MVCC = Multi-Version Concurrency Control. "같은 행의 여러 버전을 동시에 유지" 해 — 읽기·쓰기 충돌을 락 없이 해결.
핵심 원칙: 읽는 사람은 쓰는 사람을 막지 않고, 쓰는 사람은 읽는 사람을 막지 않는다.
-- 트랜잭션 A (쓰는 사람)
BEGIN;
UPDATE users SET name = 'New' WHERE id = 1;
-- 트랜잭션 B (읽는 사람, 동시)
SELECT name FROM users WHERE id = 1;
-- 'Old' 가 보임 — A 의 변경은 아직 안 보임 (A 가 COMMIT 안 함)
-- 트랜잭션 A COMMIT
COMMIT;
-- B 가 같은 SELECT 다시
SELECT name FROM users WHERE id = 1;
-- 'New' 보임 (격리 수준에 따라 — READ COMMITTED 면)
MySQL 의 InnoDB·Oracle 도 비슷한 MVCC. SQL Server 도 옵션으로. DB 의 동시성 핵심 도구.
락 기반 vs MVCC
[락 기반 (옛 RDBMS)]
WRITE READ
↓ ↓
[테이블 락] [대기]
↓ ↓
완료 ←── 읽기 가능
→ READ 가 WRITE 끝까지 대기. 시스템 정지.
[MVCC]
WRITE READ (동시)
↓ ↓
[새 버전 생성] [옛 버전 읽기]
↓ ↓
완료 완료
→ 서로 안 막음. 시스템 흐름 유지.
MVCC 의 혁명. PG·MySQL InnoDB·Oracle 모두 비슷한 원리.
xmin·xmax — 행 버전 식별
PG 각 행은 숨겨진 시스템 컬럼을 가짐.
| 컬럼 | 의미 |
|---|---|
xmin |
이 행을 만든 트랜잭션 ID |
xmax |
이 행을 무효화한 트랜잭션 ID (살아있으면 0) |
ctid |
물리 위치 |
SELECT xmin, xmax, ctid, * FROM users WHERE id = 1;
xmin | xmax | ctid | id | name
------+------+-------+----+-------
5000 | 0 | (1,3) | 1 | Alice
xmin=5000 = 트랜잭션 5000이 이 행 만듦. xmax=0 = 아직 무효화 X.
UPDATE 가 만드는 두 버전
UPDATE users SET name = 'Bob' WHERE id = 1; -- 트랜잭션 5001
SELECT xmin, xmax, * FROM users WHERE id = 1;
xmin | xmax | name
------+------+-------
5001 | 0 | Bob ← 새 버전 (살아있음)
-- 옛 버전 (트랜잭션 5001이 무효화)
xmin | xmax | name
------+------+-------
5000 | 5001 | Alice ← (다른 트랜잭션 시점에서만 보임)
같은 id=1 행이 두 버전. 각 트랜잭션은 "자기 시작 시점" 의 버전 봄.
스냅샷 — 트랜잭션의 시점
각 트랜잭션 = "자기만의 데이터베이스 스냅샷". 시작 시점에 "이 트랜잭션 ID 까지의 커밋된 행" 만 보임.
시간: ──→
트랜잭션 100 시작: 봅니다 트랜잭션 1~99 의 커밋된 행
트랜잭션 200 시작: 봅니다 트랜잭션 1~199 의 커밋된 행
트랜잭션 300 시작: 봅니다 1~299 의 커밋된 행
이게 PG MVCC 의 핵심.
락 vs MVCC 의 격리
17편 의 격리 수준 4개:
| 수준 | 구현 |
|---|---|
| READ UNCOMMITTED | PG 지원 X (READ COMMITTED 처리) |
| READ COMMITTED (기본) | 각 SELECT 마다 새 스냅샷 |
| REPEATABLE READ | 트랜잭션 시작 시점 스냅샷 일관 |
| SERIALIZABLE | + 직렬화 충돌 검출 |
PG 의 모든 격리 = 락 없이 MVCC 로. 다른 DB(MySQL 등) 와 차이.
VACUUM 의 필요성
MVCC 의 부산물 — "옛 버전 행들이 누적". 27편 DELETE 깊이 에서 다룬 bloat.
INSERT → 행 1개
UPDATE → 옛 버전 표시 + 새 버전 (행 2개)
DELETE → 표시만 (행 1개 여전히 디스크)
→ 시간 지나면 디스크 부풀음
autovacuum 이 옛 버전 회수. PG 운영의 핵심.
트랜잭션 ID 의 한계 — Wraparound
PG 트랜잭션 ID = 32bit (~40억). 40억 까지 도달하면 — 처음으로 돌아옴 (Wraparound).
autovacuum 의 두 번째 역할 — Wraparound 방지. 옛 트랜잭션 ID 의 행을 "frozen" 표시 → 영구.
PG 운영의 큰 위험 = Transaction ID Wraparound — autovacuum 이 못 따라가면 데이터베이스 강제 종료 가능. 운영 모니터링 필수.
MVCC 와 ACID
7편 관계형 모델 의 ACID:
- A 원자성 — 트랜잭션 묶음
- C 일관성 — 제약 조건
- I 격리성 — MVCC 가 핵심
- D 영속성 — WAL
MVCC 가 "I (격리성)" 의 PG 구현 방식. 락이 거의 없어 — "높은 동시성" + "일관된 읽기".
MVCC 의 장단점
| 장점 | 단점 |
|---|---|
| 읽기·쓰기 안 막음 | 디스크 비용 (옛 버전) |
| 높은 동시성 | autovacuum 필수 |
| 일관된 스냅샷 | Wraparound 위험 |
| 락 거의 없음 | bloat 모니터링 |
장점이 압도적. PG 가 "고동시성 OLTP" 에 강한 이유.
함정 5가지
(1) MVCC 의 동작 모름
UPDATE = 새 행 추가·옛 표시. INSERT 만 단순. 모르면 운영 디버깅 불가.
(2) autovacuum 비활성화
옛 버전 누적 → bloat 폭증. 절대 비활성화 X.
(3) 큰 트랜잭션 오래 열어 둠
BEGIN;
-- ... 1시간 작업
COMMIT;
그 1시간 동안 — 다른 트랜잭션이 만든 옛 버전들이 회수 안 됨. 큰 bloat.
(4) Wraparound 모니터링 X
SELECT datname, age(datfrozenxid) FROM pg_database; — 큰 값이면 위험.
(5) MVCC = 락 없음 오해
대부분의 SELECT·UPDATE 는 락 없음. 단 — SELECT FOR UPDATE·DDL·EXCLUSIVE LOCK 등은 락. 38편 격리 수준에서 깊이.
(1) 각 트랜잭션 = 스냅샷. (2) UPDATE = 새 버전 + 옛 표시. (3) 읽기·쓰기 안 막음. (4) autovacuum 이 회수. (5) Wraparound 모니터링. PG 운영의 토대.
한 줄 정리 — MVCC = PG 동시성 핵심. 각 트랜잭션 자기 스냅샷. xmin·xmax 로 버전 관리. autovacuum 으로 옛 버전 회수. Wraparound 모니터링 필수. 락 거의 없는 높은 동시성이 PG 강점.
시험 직전 한 번 더 — MVCC 입문자가 매번 헷갈리는 것
- MVCC = 다중 버전 동시성 제어
- 읽기·쓰기 서로 안 막음
- xmin = 만든 트랜잭션
- xmax = 무효화 트랜잭션 (0 = 살아있음)
- ctid = 물리 위치
- UPDATE = 새 행 + 옛 표시
- 트랜잭션 = 자기만의 스냅샷
- READ COMMITTED = 각 SELECT 새 스냅샷
- REPEATABLE READ = 시작 시점 일관
- SERIALIZABLE = + 직렬화 충돌 검출
- autovacuum 필수 (옛 버전 회수)
- bloat = 옛 버전 누적
- 큰 트랜잭션 = bloat 폭증
- Wraparound = 트랜잭션 ID 40억 한계
- autovacuum 이 frozen 표시
- pg_database.datfrozenxid 모니터
- MVCC = ACID 의 I (격리성)
- 락 vs MVCC = 옛 RDBMS vs 모던
- MySQL InnoDB·Oracle 도 비슷
SELECT FOR UPDATE= 명시적 락- DDL = EXCLUSIVE 락
- 운영 = autovacuum 튜닝 + bloat 모니터링
시리즈 다른 편
- Part 2 SQL Language 깊이: 34편 인덱스 소개 · 35편 인덱스 종류 · 36편 인덱스 종합 · 37편 (현재 글)
시리즈 다음 글
다음 글(38편)에서는 MVCC 격리 수준 깊이 — READ COMMITTED·REPEATABLE READ·SERIALIZABLE 차이.
공식 문서: PostgreSQL 18 — MVCC에서 더 자세한 사양을 확인할 수 있어요.