7.3 상속 기반 vs 선언적 파티션
PostgreSQL의 파티셔닝은 두 세대가 있습니다. 상속 기반(table inheritance, ~PG 9.x)과 선언적(declarative, PG 10+)입니다. 운영 중인 옛 시스템에서 상속 파티션을 마주칠 수 있어, 둘의 차이와 마이그레이션 방법을 알아 두는 게 좋습니다.
상속 기반 — PG 9.x 패턴
PG 10 이전에는 table inheritance와 check constraint, 트리거 또는 rule을 조합해 파티셔닝을 구현했습니다.
-- 부모 (CHECK constraint 없음)
CREATE TABLE events (
id bigserial,
created_at timestamptz,
payload jsonb
);
-- 자식 (INHERITS + 각자 CHECK)
CREATE TABLE events_2024_q1 (
CHECK (created_at >= '2024-01-01' AND created_at < '2024-04-01')
) INHERITS (events);
CREATE TABLE events_2024_q2 (
CHECK (created_at >= '2024-04-01' AND created_at < '2024-07-01')
) INHERITS (events);
-- INSERT 라우팅을 위한 트리거 (또는 rule)
CREATE FUNCTION events_insert_trigger() RETURNS TRIGGER AS $$
BEGIN
IF NEW.created_at >= '2024-04-01' THEN
INSERT INTO events_2024_q2 VALUES (NEW.*);
ELSE
INSERT INTO events_2024_q1 VALUES (NEW.*);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER events_insert
BEFORE INSERT ON events
FOR EACH ROW EXECUTE FUNCTION events_insert_trigger();문제점:
| 문제 | 영향 |
|---|---|
| INSERT 트리거 비용 | row마다 PL/pgSQL 호출 — 무거움 |
| 자동화 도구 부족 | 새 파티션 추가·라우팅 함수 갱신 모두 수동 |
EXPLAIN에서 pruning 약함 | constraint exclusion이 작동하려면 constraint_exclusion = on (느림) |
| Unique·FK 제약 글로벌 불가 | 자식마다 따로 |
선언적 파티션 — PG 10+
CREATE TABLE events (
id bigserial,
created_at timestamptz NOT NULL,
payload jsonb,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_05 PARTITION OF events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');차이:
| 항목 | 상속 기반 | 선언적 |
|---|---|---|
| 부모에 데이터 저장 | 가능 | 불가능 (스키마만) |
| INSERT 라우팅 | 트리거 | 코어 라우팅 |
| pruning | constraint exclusion (옛 메커니즘) | partition pruning (빠름) |
| 인덱스 자동 전파 | 안 됨 | PG 11+ 가능 |
| FK 지원 | 자식별 | PG 12+ 부모에 가능 |
| TRUNCATE | 부모는 자식 안 비움 (CASCADE 필요) | 부모 TRUNCATE = 모든 자식 |
| 부분 인덱스·다중 컬럼 키 | 자유 | 선언적도 가능 |
| ATTACH/DETACH | INHERIT/NO INHERIT 수동 | ATTACH PARTITION / DETACH PARTITION 공식 |
어디서 상속이 여전히 유용한가
| 사례 | 메모 |
|---|---|
| 개념적 상속 (객체 모델) | 한 부모를 여러 자식이 확장 — 파티셔닝 아닌 데이터 모델 |
| 매우 비균질한 자식 컬럼 | 자식마다 컬럼 구성이 다른 폴리모픽 모델 |
거의 모든 파티셔닝 목적에서는 선언적이 정답.
상속 → 선언적 마이그레이션
옛 상속 기반 시스템을 선언적으로 옮기는 일은 흔합니다. 운영자가 자주 마주치는 시나리오.
절차 (요약)
- 새 선언적 부모 테이블 생성
- 자식 테이블에서 트리거·rule 제거
- 자식의 CHECK constraint를 명시 (선언적 ATTACH에 필요)
- 자식을 새 부모에
ATTACH PARTITION - 옛 부모는 drop 또는 DEFAULT 파티션 대신 사용
-- 1. 새 부모
CREATE TABLE events_new (LIKE events INCLUDING ALL)
PARTITION BY RANGE (created_at);
-- 2~3. 자식에서 INHERITS 해제 + CHECK 명시
ALTER TABLE events_2024_q1 NO INHERIT events;
ALTER TABLE events_2024_q1
ADD CONSTRAINT events_2024_q1_created_at_check
CHECK (created_at >= '2024-01-01' AND created_at < '2024-04-01') NOT VALID;
ALTER TABLE events_2024_q1 VALIDATE CONSTRAINT events_2024_q1_created_at_check;
-- 4. ATTACH
ALTER TABLE events_new ATTACH PARTITION events_2024_q1
FOR VALUES FROM ('2024-01-01') TO ('2024-04-01');
-- 5. 자식 모두 옮긴 뒤 옛 부모 정리
DROP TABLE events;
ALTER TABLE events_new RENAME TO events;운영 중 무중단으로 하려면 트래픽을 새 부모로 점진적 전환하는 패턴이 필요합니다. 자세한 무중단 전환은 별도 작업 — 일반적으로 새 INSERT는 새 부모로, 옛 데이터는 ATTACH로 합치는 식.
ATTACH의 무중단성
ATTACH PARTITION은 자식의 CHECK constraint가 이미 valid 상태이면 빠르게 락 잡고 끝난다 — 운영 영향 최소.
-- 패턴: ADD CONSTRAINT ... NOT VALID → VALIDATE → ATTACH
-- ATTACH 자체는 짧은 락만PG 14+의 ALTER TABLE ... DETACH PARTITION ... CONCURRENTLY도 가능 — 자식 분리가 트래픽을 막지 않습니다.
핵심 비교
flowchart LR
OLD["상속 기반 (PG 9.x)<br/>+ INHERITS<br/>+ CHECK<br/>+ 트리거"]
NEW["선언적 (PG 10+)<br/>PARTITION BY"]
OLD -. 마이그레이션 .-> NEW
classDef old fill:#fed7aa,stroke:#c2410c,color:#7c2d12
classDef new fill:#d1fae5,stroke:#047857,color:#064e3b
class OLD old
class NEW new
| 결정 | 답 |
|---|---|
| 새 프로젝트 | 선언적 |
| 기존 상속 시스템 | 마이그레이션 검토 |
| PG 9.x 호환 필요 | 상속이 어쩔 수 없는 선택 (지원 끝남) |
| 데이터 모델로서의 상속 | 별개 문제 — 파티셔닝과 분리해 검토 |
상속이 살아 있는 코드를 만나면 우선 PG 메이저 버전을 확인하라. PG 10+에서 상속 기반 그대로면 옛 운영자의 결정이 굳어 있는 경우가 많습니다. 선언적으로 옮길지 검토 가치 있습니다.
정리
- 상속 기반 파티셔닝 = PG 9.x의 패턴입니다. INHERITS + CHECK + 트리거
- 선언적 파티셔닝 = PG 10+. 코어가 라우팅·pruning 모두 처리
- 거의 모든 파티셔닝 목적은 선언적이 정답
- 옛 상속 시스템은 ATTACH PARTITION으로 마이그레이션 가능
- 상속의 데이터 모델적 활용은 파티셔닝과 별개
다음 절(7.4)에서는 파티셔닝의 핵심 효과인 partition pruning을 봅니다.