11장. 파티셔닝과 병렬 처리
10억 건짜리 테이블을 다루는 두 가지 무기 — 읽을 범위를 잘라내는 파티셔닝, 읽는 손을 늘리는 병렬 처리. 대용량 Oracle 운영의 양대 축입니다.
① Range/List/Hash/Composite 파티셔닝의 선택 기준 ② Partition Pruning으로 스캔 범위를 잘라내는 원리 ③ Local vs Global 인덱스와 파티션 유지보수(DROP/EXCHANGE) ④ Parallel Query의 PX 서버·granule 동작과 병렬의 비용
11.1 파티셔닝 — 하나의 테이블, 여러 개의 서랍
파티셔닝은 논리적으로는 하나인 테이블을 물리적으로 여러 조각(파티션 = 각각 독립된 세그먼트)으로 나누어 저장하는 기능입니다(Enterprise Edition 옵션). 응용 프로그램은 여전히 테이블 하나로 보고 쿼리하지만, Oracle은 내부적으로 필요한 서랍만 열 수 있게 됩니다.
| 방식 | 나누는 기준 | 대표 용도 |
|---|---|---|
| Range | 값의 범위 (VALUES LESS THAN) | 날짜·기간 데이터. 가장 널리 쓰임. 오래된 파티션 DROP으로 보관주기 관리 |
| List | 명시한 값 목록 | 지역 코드, 국가, 지점처럼 이산적인 값 |
| Hash | 해시 함수로 균등 분산 | 자연스러운 기준이 없을 때 I/O·경합 분산 (파티션 수는 2의 거듭제곱 권장) |
| Composite | 2단 조합 (파티션 → 서브파티션) | Range-Hash(월별 + 고객 분산), Range-List(월별 + 지역별) 등 |
| Interval | Range의 자동화 버전 | 새 범위의 데이터가 들어오면 파티션을 자동 생성 (11g+) |
-- 월별 Range 파티셔닝의 전형
CREATE TABLE sales (
sale_id NUMBER,
sale_date DATE NOT NULL,
amount NUMBER(12,2)
)
PARTITION BY RANGE (sale_date) (
PARTITION p2026_01 VALUES LESS THAN (DATE '2026-02-01'),
PARTITION p2026_02 VALUES LESS THAN (DATE '2026-03-01'),
PARTITION p2026_03 VALUES LESS THAN (DATE '2026-04-01')
-- ... 월마다 계속
);
11.2 Partition Pruning — 안 읽는 것이 가장 빠르다
파티셔닝의 성능 효과는 대부분 pruning(가지치기)에서 나옵니다. WHERE 절에 파티션 키 조건이 있으면 옵티마이저는 대상이 아닌 파티션을 아예 접근 대상에서 제외합니다. 실행계획의 Pstart/Pstop 컬럼에서 몇 번 파티션만 읽는지 확인할 수 있습니다.
sales 테이블은 월별 12개 파티션, 총 9.6억 행입니다. 쿼리는 3월 매출 합계를 구합니다.
옵티마이저가 WHERE의 파티션 키 조건을 파티션 경계와 비교합니다. 결과는 실행계획의 Pstart/Pstop으로 나타납니다 — "3번 파티션에서 시작해 3번에서 끝".
나머지 11개 파티션은 아예 열지도 않습니다(pruned). 읽고 버리는 것이 아니라 처음부터 대상에서 제외하는 것이라 비용이 0입니다.
스캔량이 1/12로. 다만 파티션 키를 함수로 가공하면 pruning이 깨져 전 파티션 스캔이 됩니다 — 인덱스 컬럼 가공 금지 원칙(튜닝 팁 페이지)과 같은 이유입니다.
PostgreSQL도 선언적 파티셔닝과 pruning을 지원하지만(PG 12장 파티셔닝) Oracle처럼 EXCHANGE 같은 풍부한 유지보수 명령은 제한적입니다. ClickHouse의 파티션(CH 5장)은 MergeTree의 물리 디렉터리 단위로, "파티션 DROP으로 보관주기 관리"라는 운영 패턴은 세 DB가 놀랍도록 닮았습니다.
11.3 Local vs Global 인덱스
파티션 테이블의 인덱스는 두 종류입니다. 이 선택이 유지보수 편의성을 좌우합니다.
🗂 Local Index
테이블과 같은 기준으로 잘린 인덱스 — 파티션마다 자기만의 작은 인덱스를 가집니다. 파티션을 DROP/EXCHANGE해도 해당 조각만 함께 처리되어 유지보수가 깔끔합니다. DW·이력성 테이블의 기본 선택.
🌐 Global Index
파티션 경계와 무관하게 테이블 전체를 하나로 커버하는 인덱스(자체적으로 다르게 파티셔닝할 수도 있음). 파티션 키가 조건에 없는 조회(예: PK 단건 조회)에 유리하지만, 파티션 DDL 시 인덱스가 통째로 UNUSABLE이 될 수 있습니다.
-- local: 파티션마다 하나씩, 테이블과 같은 기준으로 분할
CREATE INDEX sales_cust_ix ON sales(cust_id) LOCAL;
-- 파티션 DDL 시 global 인덱스를 살리려면 UPDATE INDEXES를 붙인다 (대신 DDL이 느려짐)
ALTER TABLE sales DROP PARTITION p2025_01 UPDATE INDEXES;
11.4 파티션 유지보수 — DROP과 EXCHANGE
파티셔닝의 진짜 매력은 조회보다 운영에 있습니다. 1억 건 삭제를 DELETE로 하면 몇 시간 + 대량 undo/redo가 들지만, DROP PARTITION은 딕셔너리 작업 수준으로 끝납니다. 대량 적재의 비밀 병기는 EXCHANGE PARTITION입니다.
운영 테이블에는 7월용 빈 파티션을 준비해 두고, 신규 데이터 1억 건은 서비스와 무관한 임시 테이블에 느긋하게 적재합니다. 인덱스 생성·통계 수집·검증까지 여기서 끝냅니다.
준비가 끝나면 단 한 줄 — EXCHANGE PARTITION을 실행합니다.
Oracle이 하는 일은 데이터 복사가 아니라 데이터 딕셔너리에서 두 세그먼트의 소속을 맞바꾸는 것뿐입니다. "임시 테이블의 세그먼트"가 "파티션의 세그먼트"로 명찰만 바뀝니다.
1억 건이 순간에 운영 테이블 소속이 됩니다. 블록 이동이 없으니 undo/redo 부담도 거의 없습니다. 반대 방향(오래된 파티션을 테이블로 빼내 아카이브)에도 같은 기법을 씁니다.
11.5 Parallel Query — PX 서버와 granule
파티셔닝이 "읽을 양"을 줄인다면, 병렬 처리(Parallel Execution)는 "읽는 속도"를 늘립니다. 세션의 서버 프로세스가 QC(Query Coordinator)가 되고, PX 서버라는 일꾼 프로세스들이 작업을 나눠 맡습니다. 동시에 일하는 일꾼 수가 DOP(Degree of Parallelism)입니다.
작업 분배 단위는 granule입니다. 풀스캔이라면 테이블을 블록 범위 granule로 잘게 나누고, PX 서버들이 하나씩 가져가 처리한 뒤 끝나면 다음 것을 가져갑니다 — 먼저 끝난 일꾼이 놀지 않는 동적 분배입니다(파티션 단위 granule이 쓰이는 연산도 있습니다).
병렬 힌트나 테이블 설정을 만난 QC가 DOP=4로 결정합니다. QC 자신은 일하지 않고 지휘만 합니다.
PX 서버 4개가 배정되고, 테이블은 블록 범위 granule들로 분할됩니다. granule 수는 보통 DOP보다 많게 잘립니다 — 균등 분배를 위해서입니다.
각 PX 서버가 granule을 하나씩 잡고 동시에 스캔합니다. 먼저 끝낸 서버는 남은 granule을 가져가므로 일부 구간이 무겁더라도 일꾼이 놀지 않습니다.
부분 결과(예: 부분 SUM)가 QC로 모여 최종 결과가 됩니다. 시간은 최대 1/DOP로 줄지만 자원 소비 총량은 그대로(오히려 조정 비용만큼 증가) — 병렬은 "공짜 속도"가 아닙니다.
-- 문장 단위로 병렬 지시 (가장 안전한 사용법)
SELECT /*+ PARALLEL(s 8) */ SUM(amount)
FROM sales s
WHERE sale_date >= DATE '2026-01-01';
-- 실행계획에는 PX COORDINATOR / PX BLOCK ITERATOR 등이 나타난다
11.6 병렬의 비용 — 언제 쓰고 언제 참는가
병렬 처리는 한 쿼리가 시스템 자원을 독식하도록 허가하는 것입니다. DOP 8이면 CPU 코어 8개와 8배의 I/O 대역폭을 그 쿼리 하나가 씁니다. 야간 배치·DW 집계처럼 "이 쿼리가 지금 시스템의 주인"일 때는 훌륭하지만, 동시 사용자가 많은 OLTP 낮 시간대에 남용하면 전체 서비스가 함께 느려집니다.
- 쓰기 좋은 곳: 대용량 풀스캔 집계, CTAS(
CREATE TABLE AS SELECT), 인덱스 생성/재생성, 야간 배치. - 피할 곳: 소량 조회(병렬 준비 비용이 더 큼), 동시성 높은 OLTP 시간대, PX 서버 풀이 고갈될 만큼의 무분별한 테이블 단위 PARALLEL 설정.
- 주의: PX 서버는 인스턴스 전체가 공유하는 유한한 풀입니다. 고갈되면 뒤에 온 쿼리는 낮은 DOP로 강등되거나 직렬로 실행됩니다.
ALTER TABLE ... PARALLEL 8처럼 테이블 속성으로 병렬도를 설정하면 그 테이블을 읽는 모든 쿼리가 병렬 후보가 됩니다. 낮 시간 OLTP 쿼리까지 PX 서버를 삼키는 사고의 단골 원인입니다. 병렬이 필요한 문장에만 힌트로 지정하는 것이 안전합니다.
✍️ 이해도 체크
WHERE TO_CHAR(sale_date,'YYYYMM') = '202603' 조건으로 조회했더니 매우 느립니다. 원인은?sale_date >= DATE '2026-03-01' AND sale_date < DATE '2026-04-01'처럼 컬럼을 그대로 두는 범위 조건으로 바꾸면 해당 월 파티션만 읽습니다.Oracle Database 19c 공식 문서: VLDB and Partitioning Guide — Partitioning Concepts, Introduction to Parallel Execution