본문으로 건너뛰기

7.4 파티션 가지치기

파티셔닝의 효과는 partition pruning으로 결정됩니다. 쿼리의 WHERE 절을 보고 건드릴 필요 없는 파티션을 제외하는 메커니즘입니다. pruning이 잘 되면 한 파티션만 스캔, 안 되면 모든 파티션을 스캔 — 결과는 같지만 비용은 100배 이상 차이날 수 있습니다.

두 단계​

PostgreSQL은 두 시점에 pruning을 시도합니다.

파티션 가지치기 두 시점

단계시점조건
plan-time pruning옵티마이저가 plan 만들 때WHERE에 상수 또는 안전한 식
execution-time pruning실행 직전prepared statement의 파라미터·generic plan
-- plan-time: WHERE의 값이 상수 → 즉시 가지치기
EXPLAIN SELECT * FROM events WHERE created_at >= '2026-05-01';
-- 결과: 2026-05 이후 파티션만 스캔

-- execution-time: 파라미터가 prepare 시점에 모름
PREPARE q AS SELECT * FROM events WHERE created_at >= $1;
EXPLAIN EXECUTE q ('2026-05-01');
-- 결과: "Subplans Removed: N" 형태로 실행 시점에 가지치기

EXPLAIN에서 확인​

Append (cost=...)
Subplans Removed: 11 -- 12개 중 11개 가지치기
-> Seq Scan on events_2026_05 -- 남은 1개만 스캔

Subplans Removed가 보이면 execution-time pruning이 동작한 것입니다.

plan-time이면 아예 다른 파티션 노드가 plan에 안 나타납니다.

동작하는 WHERE​

pruning은 옵티마이저가 파티션 경계와 WHERE를 직접 비교 가능할 때만 동작합니다.

WHEREpruning
created_at = '2026-05-15'가능
created_at BETWEEN ... AND ...가능
created_at > now() - interval '1 day'가능 (stable 식)
country IN ('KR', 'JP') (LIST)가능
WHERE date_trunc('day', created_at) = '2026-05-15'불가 (함수 결과)
WHERE created_at::date = '2026-05-15'불가 (cast 함수)
WHERE created_at = (SELECT max(...) FROM ...)execution-time만

파티션 키를 그대로 비교할 때와 함수나 캐스트를 적용할 때의 가지치기 차이

함수가 들어가면 옵티마이저가 결과를 미리 모르므로 모든 파티션을 스캔합니다. WHERE에 파티션 키가 그대로 들어가는 형태가 가장 안전합니다.

동작 사례​

range 파티션 + 상수​

EXPLAIN SELECT * FROM events
WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01';

Seq Scan on events_2026_05 -- 단일 파티션만

list 파티션 + IN​

EXPLAIN SELECT * FROM orders WHERE country IN ('KR', 'JP');

Append
-> Seq Scan on orders_kr
-> Seq Scan on orders_jp

hash 파티션 + 등호​

EXPLAIN SELECT * FROM users WHERE id = 42;
Seq Scan on users_p3 -- 해시 결과로 한 파티션만

JOIN에서의 pruning​

JOIN 키가 파티션 키면 파티션-와이즈 조인이 가능합니다(PG 11+). 같은 파티션끼리만 join합니다.

SET enable_partitionwise_join = on; -- 기본 off (PostgreSQL 11 도입 이후 현재까지)
Append
-> Hash Join
-> Seq Scan on a_2026_05
-> Hash on b_2026_05
-> Hash Join
-> Seq Scan on a_2026_06
-> Hash on b_2026_06

비슷하게 enable_partitionwise_aggregate = on은 파티션별 부분 집계 후 합치는 plan을 활성화합니다.

GROUP BY 가지치기​

SET enable_partitionwise_aggregate = on;

SELECT user_id, count(*)
FROM events
WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01'
GROUP BY user_id;

-- 한 파티션 안에서 집계 → 결과 작음

함정 1 — now()·current_date​

WHERE created_at > now() - interval '1 day'

now()는 STABLE 함수라 한 트랜잭션에서 고정. 옵티마이저가 plan-time에 값을 미리 모름 → execution-time pruning만 가능합니다.

실용적으로는 execution-time이면 충분히 빠릅니다. 하지만 EXPLAIN이 generic plan을 만들 때 모든 파티션이 plan에 들어있어 plan 시간이 길어질 수 있습니다.

대응: 애플리케이션에서 상수로 만들어 보냄.

since = datetime.now() - timedelta(days=1)
cur.execute("SELECT ... WHERE created_at > %s", (since,))
-- 이 식은 prepared statement의 $1 → execution-time pruning

함정 2 — 캐스팅·함수 호출​

WHERE date_trunc('day', created_at) = '2026-05-15' -- 모든 파티션 스캔
WHERE created_at::date = '2026-05-15' -- 같음
WHERE EXTRACT(month FROM created_at) = 5 -- 모든 파티션 스캔

대응: 파티션 키를 그대로 둔 범위 비교로 재작성합니다.

WHERE created_at >= '2026-05-15' AND created_at < '2026-05-16' -- 가지치기 OK

함정 3 — 파티션 너무 많음​

100+ 파티션이 되면 plan time 자체가 길어집니다. 5ms 옵티마이징이 50ms로 자라는 식.

완화메모
활성 파티션 수 제한5년치 월 파티션이면 60개 — OK
오래된 파티션은 분기/년 단위로 합침 (rolling)pg_partman의 retention 정책
plan_cache_mode = force_generic_plan매번 plan 안 하고 caching

함정 4 — IN (subquery)​

WHERE created_at IN (SELECT max(t) FROM ...)

서브쿼리 결과는 옵티마이저가 미리 모르므로 모든 파티션을 스캔합니다. 가능하면 상수나 변수로 분리합니다.

EXPLAIN 활용 패턴​

운영 중 슬로우 쿼리를 진단할 때 pruning 동작 확인:

EXPLAIN (ANALYZE, BUFFERS)
SELECT ...
;

체크:

신호의미
Append 아래에 한 자식만plan-time pruning 성공
Subplans Removed: Nexecution-time pruning
모든 파티션이 자식으로 나열됨pruning 실패 — WHERE 식 재검토
Workers Planned: 0parallel partition scan이 안 됨 (작은 파티션)

정리​

pruning은 plan-time과 execution-time 두 단계로 동작하며, WHERE에 파티션 키가 그대로 들어가야 안전합니다. 함수나 cast를 씌우면 가지치기가 깨지고, now()·current_date는 execution-time에만 동작하지만 보통은 충분히 빠릅니다.

  • partition-wise join/aggregate: PostgreSQL 11+ 옵션, 기본 off
  • 파티션 100개 이상이면 plan time 자체가 비용 — 활성 파티션 수 관리
  • 진단은 EXPLAIN의 Subplans Removed와 Append 자식 수