왜 파티션 관리가 문제인가

시계열 로그, 이벤트, 주문 이력처럼 계속 쌓이기만 하는 테이블은 시간이 지나면 수억 건에 도달한다. 인덱스가 커지면 VACUUM과 autovacuum 비용이 급증하고, 오래된 데이터를 DELETE로 지우면 dead tuple이 대량 발생해 테이블 팽창(bloat)과 WAL 폭증을 유발한다. 반면 파티션 테이블은 오래된 구간을 DETACH·DROP으로 즉시 제거할 수 있어 DELETE 없이 데이터 보존기간을 관리할 수 있다.

문제는 파티션을 사람이 손으로 만들면 반드시 사고가 난다는 점이다. 내일 자정에 들어올 데이터의 파티션을 만들어두지 않으면 INSERT가 실패한다. 이 반복 작업을 자동화하는 것이 pg_partman의 역할이다.

선택지 비교

방식장점단점
수동 DDL + cron의존성 없음유지보수 부담, 누락 시 장애
pg_partman사전 생성·보존 자동화, 성숙확장 설치, pg_cron 권장
애플리케이션 로직DB 무관중복 구현, 경쟁 조건

설치와 부모 테이블 준비

pg_partman은 확장 형태로 설치한다. 파티셔닝 대상은 PostgreSQL 네이티브 선언적 파티션(range) 테이블이어야 한다. 아래는 이벤트 로그를 일 단위로 파티셔닝하는 예다.

CREATE SCHEMA partman;
CREATE EXTENSION pg_partman SCHEMA partman;

CREATE TABLE app.events (
    id       bigint      GENERATED ALWAYS AS IDENTITY,
    created  timestamptz NOT NULL,
    payload  jsonb       NOT NULL
) PARTITION BY RANGE (created);

자동 관리 설정

create_parent로 부모 테이블을 pg_partman에 등록한다. interval은 파티션 단위, premake는 미리 만들어 둘 파티션 개수다. 트래픽 급증이나 배치 시각을 감안해 premake는 넉넉히 잡는 편이 안전하다.

SELECT partman.create_parent(
    p_parent_table   => 'app.events',
    p_control        => 'created',
    p_type           => 'native',
    p_interval       => '1 day',
    p_premake        => 4
);

-- 보존 정책: 90일 지난 파티션은 DETACH 후 DROP
UPDATE partman.part_config
SET    retention          = '90 days',
       retention_keep_table = false,
       infinite_time_partitions = true
WHERE  parent_table = 'app.events';

retention_keep_table => false는 만료 파티션을 실제로 DROP한다. 감사 목적으로 원본을 남겨야 한다면 true로 두고 DETACH만 수행한 뒤 별도 아카이브로 옮긴다.

주기적 유지보수 실행

파티션 사전 생성과 보존 정리는 run_maintenance()가 담당한다. 이를 주기 실행해야 실제로 동작한다. pg_cron이 있으면 DB 내부에서 스케줄링할 수 있다.

-- pg_cron으로 10분마다 유지보수 실행
SELECT cron.schedule(
    'partman-maintenance',
    '*/10 * * * *',
    $$ SELECT partman.run_maintenance(p_analyze := false) $$
);

pg_cron을 못 쓰는 관리형 환경이라면 외부 스케줄러에서 호출한다.

#!/usr/bin/env bash
set -euo pipefail
psql "$DATABASE_URL" -c "SELECT partman.run_maintenance(p_analyze := false);"

운영 중 주의점

  • control 컬럼은 NOT NULL이어야 하고, 파티션 키에 해당하는 값이 범위를 벗어나면 INSERT가 실패한다. 미래 데이터가 들어올 수 있다면 premake를 늘린다.
  • run_maintenance가 멈추면 사일런트 장애가 된다. 스케줄러 실패 알림과 함께 partman.part_config 대비 실제 파티션 개수를 모니터링해 사전 생성 지연을 감지한다.
  • 대량 파티션 성능: 파티션이 수천 개로 늘면 플래너의 파티션 프루닝 비용이 커진다. 일 단위가 과하면 주 단위로 조정하거나 오래된 구간을 롤업한다.
  • p_analyze := true는 유지보수마다 ANALYZE를 돌려 부하를 준다. 대형 테이블에서는 false로 두고 통계 갱신은 autovacuum에 맡기는 편이 낫다.
  • 버전 차이: pg_partman 4.x와 5.x는 함수 시그니처가 다르다. p_type 인자 등은 설치된 버전 문서에 맞춰 확인한다.

정리하면, 네이티브 파티션으로 테이블을 나누고 pg_partman에 등록한 뒤 run_maintenance를 안정적으로 주기 실행하는 것이 핵심이다. 자동화 자체보다 그 자동화가 멈췄을 때를 감지하는 모니터링이 실무에서 더 중요하다.