PostgreSQL에서 Tuple(튜플)은 테이블의 각 행(Row)을 의미하며, MVCC(Multi-Version Concurrency Control)와 VACUUM과 밀접한 관계를 가진다.
본 내용에서는 Tuple의 구조, 동작 방식, Dead Tuple 문제, VACUUM과의 관계, 성능 최적화 전략을 다뤄보고자 한다.
1️⃣ Tuple이란?
- PostgreSQL에서 Tuple은 테이블의 한 행(Row)을 의미
- MVCC(다중 버전 동시성 제어)를 지원하기 위해 하나의 논리적인 행이 여러 개의 버전(Old & New Tuple)을 가질 수 있음
- 데이터는 Heap Page(8KB 블록) 단위로 저장되며, 하나의 Page에 여러 개의 Tuple이 포함됨
✅ PostgreSQL은 기존 데이터를 직접 수정하지 않고, 새로운 버전을 생성하여 MVCC를 구현함.
✅ 이 방식은 동시성을 높이는 대신, Dead Tuple이 증가하여 VACUUM이 필요함.
MVCC에 대한 내용은 이전 장에서 확인 → PostgreSQL MVCC (Multi-Version Concurrency Control)
2️⃣ Tuple의 내부 구조
각 Tuple(레코드)에는 사용자가 정의한 데이터 외에도 PostgreSQL이 관리하는 메타데이터가 포함된다.
| 컬럼 | 설명 |
| ctid | 페이지(Page) 내에서 Tuple의 위치를 나타내는 물리적 주소 (Page ID, Tuple Offset) |
| xmin | 이 Tuple을 생성한 트랜잭션 ID |
| xmax | 이 Tuple을 삭제하거나 업데이트한 트랜잭션 ID |
| t_xmin_commit_ts | xmin 트랜잭션의 커밋 타임스탬프 |
| t_ctid | 업데이트된 Tuple의 새로운 위치(UPDATE 발생 시 사용) |
✅ Tuple의 기본 구조
| ctid | xmin | xmax | column1 | column2 | ...
🔹 Tuple의 위치 정보 (ctid)
PostgreSQL은 테이블의 각 튜플에 ctid라는 물리적인 위치 정보를 저장한다.
SELECT ctid, * FROM user;
ctid | id | name | email
-------+----+--------+---------------
(0,1) | 1 | Alice | alice@email.com
(0,2) | 2 | Bob | bob@email.com
✅ ctid는 테이블이 업데이트되거나 VACUUM이 실행될 때 변경됨.
3️⃣ Tuple의 동작 방식 (INSERT, UPDATE, DELETE)
PostgreSQL에서 INSERT, UPDATE, DELETE가 실행될 때 어떻게 동작하는지를 살펴보자.
(1) INSERT (새로운 Tuple 생성)
INSERT INTO user (id, name) VALUES (1, 'Alice');
- 새로운 **Tuple(레코드)**이 Heap Page 내부에 저장됨
- xmin에는 해당 Tuple을 생성한 트랜잭션 ID가 기록됨
| ctid | id | name | xmin | xmax |
| (0,1) | 1 | Alice | 1001 | NULL |
✅ xmin(1001)은 트랜잭션 ID를 의미하며, 이 트랜잭션이 커밋되면 데이터가 확정됨
(2) UPDATE (새로운 Tuple 생성 & 기존 Tuple은 Dead Tuple)
UPDATE user SET name = 'Alice Smith' WHERE id = 1;
- PostgreSQL의 MVCC 특성상 UPDATE는 기존 데이터를 수정하지 않고 새로운 Tuple을 생성
- 기존 Tuple은 xmax가 설정되어 "Dead Tuple"이 됨
| ctid | id | name | xmin | xmax |
| (0,1) | 1 | Alice | 1001 | 1002 |
| (0,2) | 1 | Alice Smith | 1002 | NULL |
✅ UPDATE는 DELETE + INSERT와 유사한 방식으로 동작
✅ Dead Tuple이 증가할수록 VACUUM 필요성이 커짐
(3) DELETE (Dead Tuple 생성)
DELETE FROM user WHERE id = 1;
- PostgreSQL에서 DELETE는 데이터를 실제로 삭제하지 않고 xmax 값을 업데이트하여 "삭제됨"으로 표시함.
| ctid | id | name | xmin | xmax |
| (0,2) | 1 | Alice Smith | 1002 | 1003 |
✅ 데이터는 즉시 삭제되지 않으며, VACUUM을 실행해야 완전히 제거됨
4️⃣ Dead Tuple 문제와 VACUUM의 필요성
Dead Tuple이 많아지면 다음과 같은 문제가 발생
- 테이블 크기 증가 → 디스크 공간 낭비
- 쿼리 성능 저하 → SELECT 시 Dead Tuple도 검사해야 함
- 인덱스 크기 증가 → 불필요한 인덱스 업데이트로 성능 저하
- VACUUM이 필요함 → Dead Tuple을 정리해야 함
✅ Dead Tuple 상태 확인
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_all_tables
WHERE schemaname = 'public'
ORDER BY n_dead_tup DESC;
✅ Dead Tuple을 정리하는 VACUUM 실행
VACUUM ANALYZE user;
5️⃣ Fillfactor와 HOT(Heap-Only Tuple) 최적화
✅ HOT(Heap-Only Tuple)란?
- UPDATE 시 기존 페이지에서 데이터를 수정하는 최적화 기법
- 인덱스를 수정하지 않고 Heap 내에서만 변경 가능
- Fillfactor를 80~90으로 설정하면 HOT 적용 가능
✅ 결론
- PostgreSQL의 Tuple은 MVCC를 구현하기 위해 다중 버전(Dead & Live Tuple)으로 관리됨
- UPDATE 및 DELETE 시 Dead Tuple이 생성되며, 이를 정리하지 않으면 성능 저하 발생
- VACUUM을 실행하여 Dead Tuple을 제거해야 성능 유지 가능
- Fillfactor와 HOT Optimization을 활용하면 UPDATE 성능을 향상시킬 수 있음
🚀 PostgreSQL에서 Tuple을 최적화하면 INSERT, UPDATE, SELECT 성능을 극대화할 수 있음!
'PostgreSQL' 카테고리의 다른 글
| PostgreSQL - VACUUM (0) | 2025.02.23 |
|---|---|
| PostgreSQL - MVCC (Multi-Version Concurrency Control) (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 |