파티셔닝 — 대용량 테이블을 나누는 전략
테이블에 행이 수억 개가 되면 인덱스를 걸어도 느려집니다. 테이블 자체를 나눌 수는 없을까요?
파티셔닝이란
파티셔닝(Partitioning)은 하나의 논리적 테이블을 파티션 키 기준으로 여러 물리적 조각(파티션)으로 나누는 기능입니다.
orders 테이블 (파티셔닝 전)
┌───────────────────────────┐
│ 2023년 데이터 + 2024년 + 2025년 │ ← 전체를 스캔해야 함
└───────────────────────────┘
orders 테이블 (연도별 RANGE 파티셔닝)
┌──────────┬──────────┬──────────┐
│ p_2023 │ p_2024 │ p_2025 │ ← 해당 파티션만 스캔
└──────────┴──────────┴──────────┘
애플리케이션 입장에서는 여전히 하나의 테이블로 보입니다. SQL을 변경할 필요가 없습니다.
파티셔닝의 종류
RANGE 파티셔닝
컬럼 값의 범위로 파티션을 나눕니다. ** 날짜 기반 파티셔닝 **에 가장 많이 사용됩니다.
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT,
customer_id INT,
amount DECIMAL(10,2),
created_at DATE,
PRIMARY KEY (id, created_at) -- 파티션 키가 PK에 포함되어야 함
) PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_2023 VALUES LESS THAN (2024),
PARTITION p_2024 VALUES LESS THAN (2025),
PARTITION p_2025 VALUES LESS THAN (2026),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
-- 이 쿼리는 p_2025 파티션만 스캔 (파티션 프루닝)
SELECT * FROM orders WHERE created_at >= '2025-01-01' AND created_at < '2026-01-01';
-- 오래된 데이터 삭제가 빠름 (파티션 단위로 DROP)
ALTER TABLE orders DROP PARTITION p_2023;
-- DELETE보다 훨씬 빠르고 Undo 로그가 생기지 않음
RANGE COLUMNS
함수 없이 컬럼 값 자체로 범위를 지정합니다. 여러 컬럼도 사용 가능합니다.
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT,
created_at DATE,
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE COLUMNS (created_at) (
PARTITION p_2023 VALUES LESS THAN ('2024-01-01'),
PARTITION p_2024 VALUES LESS THAN ('2025-01-01'),
PARTITION p_2025 VALUES LESS THAN ('2026-01-01'),
PARTITION p_future VALUES LESS THAN (MAXVALUE)
);
LIST 파티셔닝
특정 값 목록으로 파티션을 나눕니다.
CREATE TABLE logs (
id BIGINT AUTO_INCREMENT,
level VARCHAR(10),
message TEXT,
created_at DATETIME,
PRIMARY KEY (id, level)
) PARTITION BY LIST COLUMNS (level) (
PARTITION p_error VALUES IN ('ERROR', 'FATAL'),
PARTITION p_warn VALUES IN ('WARN'),
PARTITION p_info VALUES IN ('INFO', 'DEBUG', 'TRACE')
);
HASH 파티셔닝
해시 함수를 사용하여 데이터를 균등하게 분배합니다.
CREATE TABLE sessions (
id BIGINT AUTO_INCREMENT,
user_id INT,
data TEXT,
PRIMARY KEY (id, user_id)
) PARTITION BY HASH (user_id)
PARTITIONS 8;
-- user_id % 8 값으로 파티션 결정
데이터가 균등하게 분배되지만, 범위 쿼리에서는 프루닝이 되지 않습니다.
KEY 파티셔닝
MySQL의 내부 해시 함수를 사용합니다. HASH와 유사하지만 문자열 등 다양한 타입을 지원합니다.
CREATE TABLE data (
id BIGINT AUTO_INCREMENT,
name VARCHAR(100),
PRIMARY KEY (id, name)
) PARTITION BY KEY (name)
PARTITIONS 4;
파티션 프루닝 (Partition Pruning)
파티셔닝의 핵심 성능 이점입니다. 옵티마이저가 WHERE 조건을 보고 **불필요한 파티션을 건너뜁니다 **.
-- RANGE(YEAR(created_at))로 파티셔닝된 테이블
EXPLAIN SELECT * FROM orders WHERE created_at = '2025-06-15';
-- partitions: p_2025 ← p_2025만 스캔
EXPLAIN SELECT * FROM orders WHERE created_at >= '2024-06-01';
-- partitions: p_2024,p_2025,p_future ← 해당 범위의 파티션만 스캔
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;
-- partitions: p_2023,p_2024,p_2025,p_future ← 프루닝 불가! 전체 파티션 스캔
프루닝이 동작하려면 WHERE 절에 파티션 키가 포함 되어야 합니다.
파티션 관리
파티션 추가
-- RANGE에서 새 파티션 추가 (MAXVALUE 파티션이 있으면 REORGANIZE 필요)
ALTER TABLE orders REORGANIZE PARTITION p_future INTO (
PARTITION p_2026 VALUES LESS THAN (2027),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
파티션 삭제
-- 오래된 파티션 삭제 (데이터도 함께 삭제됨)
ALTER TABLE orders DROP PARTITION p_2023;
파티션 DROP은 DELETE보다 훨씬 빠릅니다. 인덱스 업데이트나 Undo 로그 생성이 없기 때문입니다.
파티션 분할
-- 하나의 파티션을 둘로 나눔
ALTER TABLE orders REORGANIZE PARTITION p_2025 INTO (
PARTITION p_2025_h1 VALUES LESS THAN (2025.5),
PARTITION p_2025_h2 VALUES LESS THAN (2026)
);
파티션 병합
-- 두 파티션을 하나로 합침
ALTER TABLE orders REORGANIZE PARTITION p_2025_h1, p_2025_h2 INTO (
PARTITION p_2025 VALUES LESS THAN (2026)
);
파티셔닝의 제약사항
MySQL 파티셔닝에는 중요한 제약이 있습니다.
1. 파티션 키가 PK/UK에 포함되어야 함
-- 에러! 파티션 키(created_at)가 PK에 없음
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
created_at DATE
) PARTITION BY RANGE (YEAR(created_at)) (...);
-- 정상: 파티션 키를 PK에 포함
CREATE TABLE orders (
id BIGINT,
created_at DATE,
PRIMARY KEY (id, created_at) -- created_at 포함
) PARTITION BY RANGE (YEAR(created_at)) (...);
이 제약 때문에 id 단독 PK를 사용할 수 없게 되어 애플리케이션 설계에 영향을 줄 수 있습니다.
2. 외래 키 사용 불가
파티셔닝된 테이블에서는 외래 키를 정의할 수 없습니다.
3. 최대 파티션 수
테이블당 최대 8192개 파티션을 생성할 수 있습니다.
4. 사용 가능한 함수 제한
파티션 표현식에서 사용할 수 있는 함수가 제한됩니다. YEAR(), TO_DAYS(), TO_SECONDS(), UNIX_TIMESTAMP() 등 일부 함수만 사용 가능합니다.
파티셔닝 vs 샤딩
| 구분 | 파티셔닝 | 샤딩 |
|---|---|---|
| 분할 범위 | 단일 서버 내 | 여러 서버에 걸쳐 |
| 투명성 | SQL 변경 불필요 | 애플리케이션 또는 미들웨어 필요 |
| 확장성 | 단일 서버 한계 | 서버 추가로 수평 확장 |
| 복잡도 | 낮음 | 높음 (분산 트랜잭션, 조인 등) |
파티셔닝은 단일 서버의 한계 내에서 성능을 개선하는 방법이고, 샤딩은 그 한계를 넘어서기 위한 방법입니다.
파티셔닝이 효과적인 경우
-
** 시계열 데이터 **: 로그, 이벤트, 주문 등 시간순 데이터
- 오래된 데이터 삭제가 빈번 (파티션 DROP)
- 최근 데이터 위주로 조회 (프루닝)
-
** 대용량 테이블의 관리 작업**
- 파티션 단위로
OPTIMIZE TABLE수행 - 파티션 단위로 백업/복원
- 파티션 단위로
-
** 특정 범위의 데이터만 자주 조회하는 경우**
- 프루닝으로 불필요한 스캔 방지
파티셔닝이 효과적이지 않은 경우
- 파티션 키가 WHERE 절에 포함되지 않는 쿼리가 많을 때
- 테이블 크기가 작을 때 (파티셔닝 오버헤드만 증가)
- 대부분의 쿼리가 PK로 단일 행을 조회할 때 (이미 충분히 빠름)
주의할 점
파티션 키가 WHERE에 없으면 프루닝이 안 된다
파티셔닝의 핵심 이점은 프루닝입니다. WHERE 절에 파티션 키가 포함되지 않으면 모든 파티션을 스캔하므로 오히려 오버헤드만 증가합니다. 대부분의 쿼리에 파티션 키가 포함되는지 먼저 확인해야 합니다.
파티션 키가 PK에 포함되어야 하는 제약이 설계를 제한한다
PRIMARY KEY (id) 대신 PRIMARY KEY (id, created_at)처럼 복합 PK가 필요합니다. 이 변경이 ORM이나 애플리케이션 코드에 영향을 줄 수 있습니다.
파티셔닝된 테이블에서는 외래 키를 사용할 수 없다
참조 무결성이 필요한 테이블에는 파티셔닝을 적용할 수 없습니다. 애플리케이션 레벨에서 참조 무결성을 관리해야 합니다.
정리
| 항목 | 설명 |
|---|---|
| 파티셔닝 | 하나의 테이블을 파티션 키 기준으로 논리적 분할 |
| RANGE (가장 많이 사용) | 날짜/시간 기반 분할에 적합 |
| LIST | 특정 값 목록으로 분할 |
| HASH/KEY | 해시 함수로 균등 분배 |
| 파티션 프루닝 | WHERE에 파티션 키 포함 시 불필요한 파티션 스킵 |
| 핵심 제약 | 파티션 키가 PK/UK에 포함, 외래 키 불가, 최대 8192개 |
| 파티션 DROP | DELETE보다 압도적으로 빠른 데이터 삭제 |