백엔드 데이터 인프라 37편 — MVCC 소개

2026-05-17백엔드 데이터 인프라

백엔드 데이터 인프라 37편. MVCC 의 큰 그림 — 읽는 사람과 쓰는 사람이 서로 막지 않는 PG 핵심 메커니즘 풀어쓴 학습 노트.

📚 백엔드 데이터 인프라 · 37편 — MVCC 소개

이 글은 백엔드 데이터 인프라 시리즈 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(MySQL 기본 스토리지 엔진)·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 은 아직 아무도 이 행을 무효화하지 않았다는 의미예요.

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 로 풀어내요. MySQL 같은 다른 DB 와 결이 다른 지점입니다.

VACUUM 의 필요성

MVCC 를 쓰는 대가로 옛 버전 행들이 차곡차곡 쌓입니다. 27편 DELETE 깊이 에서 다뤘던 bloat(쌓인 옛 버전이 디스크를 부풀리는 현상) 가 바로 이 부산물이에요.

INSERT  → 행 1개
UPDATE  → 옛 버전 표시 + 새 버전 (행 2개)
DELETE  → 표시만 (행 1개 여전히 디스크)

→ 시간 지나면 디스크 부풀음

여기서 옛 버전을 회수해 주는 게 autovacuum(PG 가 백그라운드로 옛 행을 정리하는 데몬) 이고, PG 운영의 한가운데에 있는 친구입니다.

트랜잭션 ID 의 한계 — Wraparound

PG 트랜잭션 ID 는 32bit, 곧 40억 정도가 한계예요. 이 숫자를 다 쓰면 처음으로 돌아오는데, 이게 Wraparound(트랜잭션 ID 가 한 바퀴 돌아 옛 ID 와 충돌하는 사고) 입니다.

autovacuum 에는 옛 버전 회수 말고 두 번째 일이 더 있어요 — 바로 이 Wraparound 방지. 옛 트랜잭션 ID 의 행을 "frozen"(영구히 보이는 행으로 고정하는 표시) 으로 찍어 영원히 살아남게 합니다.

PG 운영에서 가장 큰 위험 중 하나가 이 Transaction ID Wraparound 예요. autovacuum 이 못 따라가는 순간 데이터베이스가 강제로 멈출 수도 있어서, 모니터링은 선택이 아닙니다.

MVCC 와 ACID

7편 관계형 모델 의 ACID(트랜잭션이 지켜야 할 네 가지 보증):

  • A 원자성 — 트랜잭션 묶음
  • C 일관성 — 제약 조건
  • I 격리성 — MVCC 가 핵심
  • D 영속성 — WAL(Write-Ahead Log, 변경을 먼저 기록해 영속화)

ACID 의 I, 즉 격리성을 PG 가 풀어내는 방식이 바로 MVCC 예요. 락이 거의 없으니 높은 동시성과 일관된 읽기가 같이 따라옵니다.

MVCC 의 장단점

장점 단점
읽기·쓰기 안 막음 디스크 비용 (옛 버전)
높은 동시성 autovacuum 필수
일관된 스냅샷 Wraparound 위험
락 거의 없음 bloat 모니터링

장점이 단점을 압도해요. PG 가 고동시성 OLTP(Online Transaction Processing, 짧은 트랜잭션 위주 업무 처리) 에 강한 이유가 여기 있습니다.

함정 5가지

(1) MVCC 의 동작 모름

UPDATE 는 새 행을 추가하고 옛 행에 표시를 다는 동작이지, INSERT 처럼 단순하지 않아요. 이 그림을 모르면 운영 디버깅이 막힙니다.

(2) autovacuum 비활성화

옛 버전이 회수되지 않으니 bloat 가 폭증해요. 비활성화는 절대 금물.

(3) 큰 트랜잭션 오래 열어 둠

BEGIN;
-- ... 1시간 작업
COMMIT;

이 1시간 동안 다른 트랜잭션이 만든 옛 버전들이 회수되지 못해 bloat 가 크게 쌓입니다.

(4) Wraparound 모니터링 X

SELECT datname, age(datfrozenxid) FROM pg_database; 로 확인해 보고, 값이 크면 위험 신호예요.

(5) MVCC = 락 없음 오해

대부분의 SELECT·UPDATE 는 락이 없지만, SELECT FOR UPDATE·DDL(Data Definition Language, 테이블 구조를 바꾸는 SQL)·EXCLUSIVE LOCK 같은 경우는 락을 잡습니다. 38편 격리 수준에서 깊이 다룰 부분.

🎯 MVCC 5단계

(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 모니터링

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!