VACUUM과 Tuple을 이해하기 이전에 반드시 먼저 알아야 할 내용이다.
MVCC (Multi-Version Concurrency Control, 다중 버전 동시성 제어)는 PostgreSQL이 동시성을 관리하는 핵심 메커니즘이다.
- 트랜잭션이 서로 간섭하지 않도록 과거 버전을 유지하여, 락(Lock) 없이 동시성을 보장
- 읽기(SELECT)와 쓰기(UPDATE, DELETE)가 충돌하지 않음
- VACUUM과 함께 관리되며, 불필요한 데이터(Dead Tuple)를 자동 정리
1️⃣ MVCC란?
- PostgreSQL에서는 데이터 변경 시 기존 데이터를 덮어쓰지 않고 새로운 버전을 생성
- 과거 데이터를 유지하면서 새로운 트랜잭션이 최신 데이터를 조회할 수 있음
- 트랜잭션 격리 수준에 따라 과거 데이터 접근 방식이 달라짐
✅ 기존의 단일 버전 모델과의 차이점
| 전통적인 모델 | MVCC (PostgreSQL) |
| 데이터 수정 시 즉시 반영 | 데이터 수정 시 새로운 버전 생성 |
| SELECT 시 다른 트랜잭션과 충돌 가능 | SELECT 시 과거 버전을 참조하여 충돌 방지 |
| Lock 기반 동시성 제어 | Lock 없이 일관성 유지 가능 |
2️⃣ PostgreSQL의 MVCC 동작 원리
PostgreSQL에서 INSERT, UPDATE, DELETE가 실행될 때 어떻게 동작하는지를 살펴보자.
(1) INSERT (새로운 Tuple 생성)
INSERT INTO user (id, name) VALUES (1, 'Alice');
컬럼이 'id(식별번호)', 'name(이름)'을 가진 'user(사용자)' 라는 테이블에 '1'과 'Alice'를 입력한다.
✅ 새로운 Tuple(레코드)가 Page(8KB 블록) 내부에 저장되며, 트랜잭션 정보를 포함한다.
| id | name | xmin (생성 트랜잭션 ID) | xmax (삭제 트랜잭션 ID) |
| 1 | Alice | 1001 | NULL |
- xmin: 이 튜플을 생성한 트랜잭션 ID (1001)
- xmax: 이 튜플이 삭제될 트랜잭션 ID (NULL이면 아직 삭제되지 않음)
(2) UPDATE (새로운 Tuple 생성 & 기존 Tuple은 Dead Tuple)
UPDATE user SET name = 'Alice Smith' WHERE id = 1;
식별자(id)가 '1'인 행의 이름(name)의 값을 'Alice' 에서 'Alice Smith'로 변경함.
✅ PostgreSQL에서 UPDATE는 기존 데이터를 변경하지 않고 새로운 버전을 생성함.
| id | name | xmin (생성 트랜잭션 ID) | xmax (삭제 트랜잭션 ID) |
| 1 | Alice | 1001 | 1002 |
| 1 | Alice Smith | 1002 | NULL |
- 기존 버전의 xmax = 1002 (이제 이 튜플을 읽을 수 없음)
- 새로운 버전이 xmin = 1002로 추가됨
✅ UPDATE는 DELETE + INSERT와 유사한 동작을 하며, 새로운 버전을 생성하여 과거 데이터를 유지함.
(3) DELETE (Dead Tuple 생성)
DELETE FROM user WHERE id = 1;
✅ DELETE는 기존 튜플을 제거하는 것이 아니라, xmax를 업데이트하여 더 이상 조회되지 않도록 함.
| id | name | xmin | xmax |
| 1 | Alice Smith | 1002 | 1003 |
- 기존 튜플의 xmax = 1003으로 변경 (이제 이 튜플을 읽을 수 없음)
- 하지만 실제로 디스크에서 삭제된 것은 아님 (VACUUM이 필요함)
3️⃣ MVCC와 트랜잭션 격리 수준
PostgreSQL은 MVCC를 기반으로 다양한 트랜잭션 격리 수준을 지원한다.
| 격리 수준 | 설명 | 읽기 일관성 | Phantom Read 방지 |
| Read Uncommitted | 다른 트랜잭션의 미완료 변경 사항을 볼 수 있음 | ❌ | ❌ |
| Read Committed (기본값) | 커밋된 변경 사항만 볼 수 있음 | ✅ | ❌ |
| Repeatable Read | 트랜잭션 시작 시점의 데이터만 보임 (다른 트랜잭션이 변경한 내용 안 보임) | ✅ | ✅ |
| Serializable | 가장 강력한 격리 수준 (트랜잭션을 직렬화하여 실행) | ✅ | ✅ |
✅ PostgreSQL 기본값은 READ COMMITTED이며, 성능과 일관성을 고려한 최적의 선택
✅ REPEATABLE READ 이상을 사용하면 MVCC가 더욱 강력하게 동작하여 일관성을 보장
4️⃣ MVCC와 Vacuum
MVCC에서는 과거 버전의 튜플(Dead Tuple)이 계속 남아 있으므로, 주기적으로 정리해야 함.
- Dead Tuple이 많아지면 성능 저하 발생
- PostgreSQL은 이를 해결하기 위해 VACUUM 프로세스를 실행
✅ Vacuum의 역할
- Dead Tuple을 제거하여 디스크 공간을 확보
- 트랜잭션 ID(XID) Wraparound 방지
- Query Planner가 최신 통계를 사용할 수 있도록 갱신 (ANALYZE)
5️⃣ MVCC 성능 최적화 전략
✔ Dead Tuple 모니터링 및 Vacuum 튜닝
- autovacuum_vacuum_scale_factor = 0.05 (5% 변경 시 자동 Vacuum 실행)
- autovacuum_naptime = 30s (더 자주 실행)
✔ Fillfactor 조정
- UPDATE가 많은 테이블은 fillfactor = 80으로 설정하여 HOT (Heap-Only Tuple) 최적화
✔ 인덱스 정리
- UPDATE가 많은 테이블은 REINDEX TABLE을 주기적으로 실행하여 인덱스 크기 최적화
✔ HOT (Heap-Only Tuple) 최적화
- UPDATE가 인덱스 키를 변경하지 않으면 기존 페이지에서 데이터 수정 가능
- 이를 위해 인덱스 컬럼을 변경하는 UPDATE를 줄이는 것이 성능 최적화의 핵심!
✅ 결론
🔹 PostgreSQL의 MVCC는 동시성 제어를 위한 핵심 기술로, 락 없이 여러 트랜잭션이 독립적으로 실행 가능
🔹 UPDATE 및 DELETE는 Dead Tuple을 생성하므로, 정기적인 VACUUM이 필수
🔹 트랜잭션 격리 수준에 따라 MVCC 동작 방식이 다름 (READ COMMITTED가 기본값)
🔹 autovacuum을 튜닝하여 Dead Tuple을 최소화하면 성능 최적화 가능
✅ MVCC의 원리를 이해하고 튜닝하면 PostgreSQL의 성능을 극대화할 수 있음!
'PostgreSQL' 카테고리의 다른 글
| PostgreSQL - VACUUM (0) | 2025.02.23 |
|---|---|
| PostgreSQL - Tuple (0) | 2025.02.23 |
| PostgreSQL - Memory - Shared Buffer (0) | 2025.02.22 |
| PostgreSQL - Memory - Backend Buffer (0) | 2025.02.22 |
| PostgreSQL - Idle Session' Memory (0) | 2025.02.22 |