파티션 프루닝이란 무엇인가
선언적 파티션(Declarative Partitioning)에서 프루닝(Pruning)은 쿼리 조건에 맞지 않는 파티션을 실행 대상에서 제외하는 최적화다. 1000개 파티션 중 1개만 스캔하면 수백 배 빠르다. 하지만 "파티션을 만들었으니 자동으로 빨라지겠지"라는 기대는 자주 배신당한다. 프루닝은 조건이 맞아야만 동작하고, 그 조건은 생각보다 까다롭다.
왜 프루닝이 안 되는가: 대표 함정
가장 흔한 원인은 파티션 키와 조건의 데이터 타입 불일치다. 키가 timestamptz인데 조건에 timestamp나 문자열을 쓰면 캐스팅이 개입해 프루닝이 깨진다. 다음은 실제로 재현되는 예다.
-- 파티션 키: timestamptz
CREATE TABLE events (id bigint, at timestamptz) PARTITION BY RANGE (at);
CREATE TABLE events_2026_09 PARTITION OF events
FOR VALUES FROM ('2026-09-01') TO ('2026-10-01');
-- 나쁨: 함수가 키를 감싸면 프루닝 불가 → 전 파티션 스캔
EXPLAIN SELECT * FROM events WHERE date(at) = '2026-09-15';
-- 좋음: 키를 그대로 두고 경계 조건으로 표현
EXPLAIN SELECT * FROM events
WHERE at >= '2026-09-15' AND at < '2026-09-16';
첫 번째는 date(at)로 키를 가공했기 때문에 플래너가 어떤 파티션이 필요한지 판단하지 못한다. 두 번째처럼 키 컬럼을 가공하지 않고 범위로 표현해야 한다.
플랜 타임 프루닝 vs 런타임 프루닝
프루닝에는 두 종류가 있다. 플랜 타임은 상수 조건으로 계획 수립 시 파티션을 잘라낸다. 런타임은 실행 중 값이 정해지는 경우(바인드 파라미터, 서브쿼리 결과, NestLoop의 조인 키)에 동작한다. 런타임 프루닝은 PostgreSQL 11+에서 지원되며, EXPLAIN ANALYZE에 Subplans Removed로 표시된다.
| 구분 | 동작 시점 | EXPLAIN 표시 |
|---|---|---|
| 플랜 타임 | 계획 수립 | 대상 파티션만 나열 |
| 런타임(초기) | 실행 시작 | Subplans Removed: N |
| 런타임(실행 중) | 루프마다 | never executed |
Prepared Statement의 제네릭 플랜 함정
PreparedStatement를 5회 이상 실행하면 PostgreSQL은 파라미터 값을 무시한 제네릭 플랜(generic plan)으로 전환할 수 있다. 이때 플랜 타임 프루닝이 사라지고 런타임 프루닝에 의존하게 된다. 대부분은 런타임 프루닝으로 커버되지만, 파티션 수가 수천 개면 모든 서브플랜을 계획하느라 계획 비용 자체가 폭증한다.
-- 계획 방식 강제 확인/제어
SET plan_cache_mode = 'force_custom_plan'; -- 매번 커스텀 플랜
-- 세션 기본값으로도 조정 가능
-- auto(기본) | force_custom_plan | force_generic_plan
파티션이 많고 값 분포가 편향됐다면 force_custom_plan이 유리할 때가 많다. 단, 계획 재수립 비용이 늘어나므로 벤치마크로 확인해야 한다.
now(), CURRENT_DATE 같은 함수 조건
WHERE at >= now() - interval '1 day'는 now()가 안정(stable) 함수라 플랜 타임에는 값이 없지만, 실행 시 상수로 평가되어 런타임 프루닝이 동작한다. 반면 volatile 함수나 다른 컬럼과의 연산이 끼면 프루닝이 깨진다. 조건은 항상 "파티션 키 연산자 상수화가능값" 형태를 유지하라.
조인과 파티션와이즈 옵션
파티션 테이블끼리 조인할 때 같은 경계를 공유해도 기본 설정으로는 파티션 단위 조인이 비활성이다. 다음을 켜야 한다.
SET enable_partition_pruning = on; -- 기본 on, 꺼져있는지 확인
SET enable_partitionwise_join = on; -- 기본 off
SET enable_partitionwise_aggregate = on; -- 기본 off
partitionwise 옵션은 계획 시간과 메모리를 늘리므로 파티션이 극단적으로 많으면 오히려 손해다. OLTP 단건 조회에는 프루닝만, 대량 분석 쿼리에는 partitionwise를 선택적으로 적용하는 편이 안전하다.
정리와 점검 체크리스트
- 조건에서 파티션 키를 함수·캐스팅으로 감싸지 말 것
- 조건 값의 타입을 키 타입과 정확히 맞출 것
EXPLAIN (ANALYZE)로 실제 스캔된 파티션과Subplans Removed를 확인할 것- 파티션 수천 개 + Prepared Statement 조합은
plan_cache_mode를 벤치마크할 것 - partitionwise 옵션은 무조건 켜지 말고 워크로드별로 검증할 것
프루닝은 자동이 아니라 조건을 만족해야 켜지는 최적화다. 파티션 설계보다 쿼리 작성 습관이 성능을 좌우하는 경우가 더 많다.