본문으로 건너뛰기

7.2 range / list / hash 파티션

PostgreSQL의 declarative partitioning은 세 가지 분할 방식, RANGE·LIST·HASH를 지원합니다. 각각 동작과 사용 사례, 문법이 다릅니다.

RANGE — 범위 분할

값을 연속된 범위로 나눕니다. 시계열·연속된 정수·날짜에 표준입니다.

-- 부모 테이블 (스키마만 정의, 데이터 없음)
CREATE TABLE events (
id bigserial,
created_at timestamptz NOT NULL,
payload jsonb NOT NULL,
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');

CREATE TABLE events_2026_06
PARTITION OF events
FOR VALUES FROM ('2026-06-01') TO ('2026-07-01');

-- DEFAULT 파티션 — 어디에도 안 맞는 row를 받음
CREATE TABLE events_default
PARTITION OF events
DEFAULT;

FROM은 포함, TO는 미포함 — half-open interval [from, to).

특징메모
범위 겹침 안 됨한 row가 정확히 한 파티션에 속함
빈 구간 가능DEFAULT 파티션이 받거나 INSERT 실패
다중 컬럼 키 가능PARTITION BY RANGE (year, month) 같은 식

사용 사례

  • 시계열 (created_at, event_date)
  • 정수 ID 범위 (1억 단위로 분할)
  • 가격·금액 범위

LIST — 명시적 목록 분할

각 파티션이 허용하는 값 목록을 명시합니다.

CREATE TABLE orders (
id bigserial,
country text NOT NULL,
amount numeric,
PRIMARY KEY (id, country)
) PARTITION BY LIST (country);

CREATE TABLE orders_kr PARTITION OF orders FOR VALUES IN ('KR');
CREATE TABLE orders_jp PARTITION OF orders FOR VALUES IN ('JP');
CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('US', 'CA'); -- 여러 값 한 파티션
CREATE TABLE orders_default PARTITION OF orders DEFAULT;

사용 사례

  • 국가·지역·tenant ID 같은 카테고리
  • 매우 적은 distinct 값을 갖는 컬럼

LIST는 값 추가 시 ALTER TABLE로 새 파티션을 만들거나 DEFAULT에 들어갑니다.

HASH — 해시 모듈로 분할

값을 해시한 뒤 모듈로 N으로 나눕니다.

CREATE TABLE users (
id bigint PRIMARY KEY,
email text,
...
) PARTITION BY HASH (id);

CREATE TABLE users_p0 PARTITION OF users FOR VALUES WITH (modulus 8, remainder 0);
CREATE TABLE users_p1 PARTITION OF users FOR VALUES WITH (modulus 8, remainder 1);
-- ... users_p7까지
CREATE TABLE users_p7 PARTITION OF users FOR VALUES WITH (modulus 8, remainder 7);
특징메모
균등 분포 보장해시이므로 모든 파티션에 비슷한 row 수
보존 정책에 부적합시간 순이 아니라 오래된 데이터 그룹이 없음
파티션 수 변경이 어려움modulus를 늘리면 모든 파티션 재구성 필요

사용 사례

  • 부하 분산이 필요한 큰 테이블 (시간 키가 없을 때)
  • 멀티 디스크에 데이터 균등 분포 (각 파티션을 다른 tablespace로)

어떤 타입을 고르나

서브파티셔닝 (2단계)

큰 시계열 + 멀티 테넌트는 RANGE 안에 LIST/HASH를 두 단계로.

CREATE TABLE events (
id bigserial,
tenant_id int NOT NULL,
created_at timestamptz NOT NULL,
PRIMARY KEY (id, tenant_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')
PARTITION BY LIST (tenant_id);

CREATE TABLE events_2026_05_t1 PARTITION OF events_2026_05 FOR VALUES IN (1);
CREATE TABLE events_2026_05_t2 PARTITION OF events_2026_05 FOR VALUES IN (2);
-- ...

3단계 이상은 운영 복잡도가 폭증합니다. 보통 2단계까지만 씁니다.

인덱스

파티션된 부모에 인덱스를 만들면 모든 자식에 자동으로 생성됩니다(PG 11+).

CREATE INDEX ON events (created_at);
-- 각 events_YYYY_MM에도 인덱스가 자동으로 만들어짐

부모 인덱스 자체는 가상이고 실제 데이터는 자식 인덱스에 있습니다. unique 인덱스는 파티션 키를 포함해야 가능:

CREATE UNIQUE INDEX ON events (id, created_at); -- OK
CREATE UNIQUE INDEX ON events (id); -- ERROR: must include partition key

파티션 라우팅

INSERT는 부모 이름으로 보내면 PostgreSQL이 알맞은 파티션에 라우팅합니다.

INSERT INTO events (created_at, payload)
VALUES (now(), '{"type":"click"}');
-- 자동으로 events_2026_05에 들어감

자식 테이블에 직접 INSERT하는 것도 가능합니다(성능 차이는 거의 없습니다).

DEFAULT 파티션의 함정

DEFAULT 파티션이 있으면 PostgreSQL은 새 파티션을 ATTACH할 때 DEFAULT의 row 중 새 파티션 범위에 속하는 게 없는지 검사합니다. 큰 DEFAULT 파티션이 있으면 이 검사가 매우 오래 걸립니다.

권장: DEFAULT 파티션은 빈 상태로 유지합니다. 새 범위가 들어오기 전에 파티션을 미리 만들어 둡니다.

파티션 정보 확인

-- 부모의 모든 파티션
SELECT inhrelid::regclass AS partition,
pg_get_expr(c.relpartbound, c.oid) AS bound
FROM pg_inherits i
JOIN pg_class c ON i.inhrelid = c.oid
WHERE inhparent = 'events'::regclass;

-- 각 파티션 크기
SELECT child::regclass AS partition,
pg_size_pretty(pg_total_relation_size(child)) AS size
FROM pg_partition_tree('events')
WHERE level > 0;

pg_partition_tree는 PG 12+의 편의 함수.

운영 시 주의

주의메모
파티션 키 변경 = 테이블 재구성도입 전 신중히 결정
파티션 키 컬럼은 NOT NULL 권장NULL은 DEFAULT 파티션이 받지만 의도치 않은 라우팅
RANGE 경계 겹침은 syntax error로 막힘안전
부모에 TRUNCATE는 모든 자식까지 적용영향 큰 명령
pg_dump는 기본적으로 부모만 dump해도 자식 데이터 다 포함자식 단위 dump 옵션 (-t)도 가능

정리

세 분할 방식은 쓰임이 갈립니다.

  • RANGE: 시간·연속값, 시계열의 표준
  • LIST: 카테고리·tenant·국가
  • HASH: 균등 분포가 필요할 때

부모에 인덱스를 만들면 자식에 자동 생성되고, unique 인덱스는 파티션 키를 포함해야 합니다. DEFAULT 파티션은 비워 두며, 서브파티셔닝은 2단계까지가 운영 한계입니다.