PostgreSQL 교재 · 22편 / 24편

파티셔닝 — RANGE·LIST·HASH

큰 테이블을 잘게. 시계열·다국가·해시 분산의 표준.

고급읽는 시간 7분2026-05-17
큰 테이블이 파티션으로 분할되어 쿼리가 일부만 스캔하는 도식

1억 행 테이블에서 "이번 달 데이터만" 을 매번 검색하면 인덱스가 있어도 무겁습니다. 파티셔닝은 테이블을 물리적으로 여러 조각으로 나누어, 쿼리가 필요한 조각만 만지게 합니다. 22편은 PG 의 3가지 파티션 방식과 시계열 데이터 패턴.

왜 파티셔닝? — 4가지 이유

  • 쿼리 성능 — 필요한 파티션만 스캔(partition pruning).
  • 유지보수 — 옛 파티션 통째로 DROP (1초). DELETE 보다 압도적.
  • 인덱스 크기 — 파티션별 인덱스가 작아 캐시 효율 ↑.
  • 병렬 처리 — 파티션 단위로 병렬 스캔·VACUUM.

3가지 파티션 방식

방식분할 기준주 용도
RANGE범위 (날짜·숫자 구간)시계열·로그
LIST값 집합 (국가·지역·카테고리)멀티 테넌트·다국가
HASH해시 (균등 분산)행 분산 (특정 키 핫스팟 없음)

RANGE — 월별 시계열

-- 1) 파티션 부모 테이블
CREATE TABLE events (
  id          BIGSERIAL,
  user_id     BIGINT,
  event_type  TEXT,
  payload     JSONB,
  created_at  TIMESTAMPTZ NOT NULL,
  PRIMARY KEY (id, created_at)         -- 파티션 키를 PK 에 포함 필수
) PARTITION BY RANGE (created_at);

-- 2) 월별 파티션
CREATE TABLE events_2026_01 PARTITION OF events
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

CREATE TABLE events_2026_02 PARTITION OF events
  FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');

CREATE TABLE events_2026_03 PARTITION OF events
  FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

-- INSERT 는 부모에 — Postgres 가 자동으로 알맞은 파티션에
INSERT INTO events (...) VALUES (...);

-- SELECT 도 부모에
SELECT * FROM events WHERE created_at >= '2026-02-15';
-- → events_2026_02 와 그 이후만 스캔 (partition pruning)

LIST — 지역별

CREATE TABLE orders_regional (
  id      BIGSERIAL,
  region  TEXT NOT NULL,
  total   NUMERIC,
  PRIMARY KEY (id, region)
) PARTITION BY LIST (region);

CREATE TABLE orders_kr PARTITION OF orders_regional FOR VALUES IN ('KR');
CREATE TABLE orders_jp PARTITION OF orders_regional FOR VALUES IN ('JP');
CREATE TABLE orders_us PARTITION OF orders_regional FOR VALUES IN ('US', 'CA');

-- 나머지
CREATE TABLE orders_other PARTITION OF orders_regional DEFAULT;

HASH — 균등 분산

CREATE TABLE users_h (
  id    BIGINT NOT NULL,
  name  TEXT,
  PRIMARY KEY (id)
) PARTITION BY HASH (id);

-- 4개 파티션 (0, 1, 2, 3)
CREATE TABLE users_h_0 PARTITION OF users_h FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE users_h_1 PARTITION OF users_h FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE users_h_2 PARTITION OF users_h FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE users_h_3 PARTITION OF users_h FOR VALUES WITH (MODULUS 4, REMAINDER 3);

언제 HASH? 시간·값 분포 없이 무조건 균등 분산이 필요할 때. 다만 "특정 user_id 데이터만" 같은 쿼리도 파티션을 하나만 만지게 됨 — id 분포가 골고루면 효과적. 단점: 범위 쿼리는 모든 파티션 스캔.

파티션 인덱스 — 부모에 만들면 모두 상속

-- 부모에 인덱스 → 모든 파티션에 자동
CREATE INDEX ix_events_user ON events (user_id);

-- 또는 파티션별 개별 인덱스
CREATE INDEX ix_events_2026_01_user ON events_2026_01 (user_id);

-- UNIQUE 제약은 파티션 키를 포함해야 함
CREATE UNIQUE INDEX uq_events_id ON events (id, created_at);

partition pruning 확인 — EXPLAIN

EXPLAIN SELECT * FROM events WHERE created_at >= '2026-03-01';

-- 결과
-- Append
--   -> Seq Scan on events_2026_03   ← 이것만!
-- (다른 파티션은 안 나타남 → pruning 성공)

파티션 관리 — 추가·삭제

-- 새 달 파티션 추가
CREATE TABLE events_2026_04 PARTITION OF events
  FOR VALUES FROM ('2026-04-01') TO ('2026-05-01');

-- 옛 파티션 분리 (백업·아카이브용)
ALTER TABLE events DETACH PARTITION events_2025_01;
-- 분리된 테이블은 일반 테이블 — 백업 후 DROP

-- 또는 즉시 삭제
DROP TABLE events_2025_01;   -- 1초 (DELETE 와 비교 불가)

pg_partman — 파티션 자동 관리

CREATE EXTENSION pg_partman;

-- 자동으로 매월 새 파티션 만들고 옛 것 정리
SELECT partman.create_parent(
  p_parent_table  := 'public.events',
  p_control       := 'created_at',
  p_type          := 'range',
  p_interval      := '1 month',
  p_premake       := 4              -- 4개월 미리 만들기
);

-- cron 또는 pg_cron 으로 주기 실행
SELECT partman.run_maintenance();   -- 새 파티션 + 옛 것 정리

pg_partman 의 가치. 수동으로 매달 파티션 만들기 → 잊어버리면 사고. pg_partman 이 일정 주기로 미리 만들고 옛 것 보관(detach 또는 drop). 시계열 데이터에는 거의 필수.

실전 — 시계열 데이터 패턴

-- 1) 월별 RANGE 파티션
CREATE TABLE metrics (
  ts      TIMESTAMPTZ NOT NULL,
  device  TEXT NOT NULL,
  value   DOUBLE PRECISION
) PARTITION BY RANGE (ts);

-- 2) BRIN 인덱스 (16편 — 시계열에 최적)
CREATE INDEX ix_metrics_ts ON metrics USING BRIN (ts);

-- 3) pg_partman 자동 관리

-- 4) 90일 보관 후 자동 삭제
SELECT partman.create_parent(
  ...,
  p_premake := 3,
  p_retention := '90 days',
  p_retention_keep_table := false   -- detach 가 아니라 drop
);

제약 — 알아두면 좋은 5가지

1. 파티션 키는 PK·UNIQUE 에 포함이 필수. UNIQUE 검사를 파티션 안에서만 하니까.

2. FK 가 파티션 테이블을 가리키는 것 은 가능하지만, 파티션 테이블에서 다른 테이블로 FK 는 PG 11+ 부터.

3. ALTER TABLE 의 일부 명령은 파티션마다. 부모 → 자식 일괄 적용은 제한적.

4. 너무 작은 파티션(수만 행) 은 오버헤드만 큼. 보통 100만~1억 행 정도 단위.

5. 너무 많은 파티션(수천 개) 은 플래닝 비용 ↑. enable_partition_pruning = on·partition_prune_skip 효과 확인.

23편 — 성능 튜닝 (shared_buffers·work_mem·VACUUM)

설정 파라미터·autovacuum·bloat 잡기.

📚 PostgreSQL 배우기 교재
이전: 21편 복제 · 현재: 22편 (고급) · 다음 → 23편 성능 튜닝 · 진행: 22/24

© 2026 주나이테크(주) @JUNAITECH