Theme:

테이블에 행이 수억 개가 되면 인덱스를 걸어도 느려집니다. 테이블 자체를 나눌 수는 없을까요?

파티셔닝이란

파티셔닝(Partitioning)은 하나의 논리적 테이블을 파티션 키 기준으로 여러 물리적 조각(파티션)으로 나누는 기능입니다.

PLAINTEXT
orders 테이블 (파티셔닝 전)
┌───────────────────────────┐
│ 2023년 데이터 + 2024년 + 2025년 │  ← 전체를 스캔해야 함
└───────────────────────────┘

orders 테이블 (연도별 RANGE 파티셔닝)
┌──────────┬──────────┬──────────┐
│ p_2023   │ p_2024   │ p_2025   │  ← 해당 파티션만 스캔
└──────────┴──────────┴──────────┘

애플리케이션 입장에서는 여전히 하나의 테이블로 보입니다. SQL을 변경할 필요가 없습니다.

파티셔닝의 종류

RANGE 파티셔닝

컬럼 값의 범위로 파티션을 나눕니다. 날짜 기반 파티셔닝에 가장 많이 사용됩니다.

SQL
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
);
SQL
-- 이 쿼리는 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

함수 없이 컬럼 값 자체로 범위를 지정합니다. 여러 컬럼도 사용 가능합니다.

SQL
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 파티셔닝

특정 값 목록으로 파티션을 나눕니다.

SQL
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 파티셔닝

해시 함수를 사용하여 데이터를 균등하게 분배합니다.

SQL
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와 유사하지만 문자열 등 다양한 타입을 지원합니다.

SQL
CREATE TABLE data (
    id BIGINT AUTO_INCREMENT,
    name VARCHAR(100),
    PRIMARY KEY (id, name)
) PARTITION BY KEY (name)
PARTITIONS 4;

파티션 프루닝 (Partition Pruning)

파티셔닝의 핵심 성능 이점입니다. 옵티마이저가 WHERE 조건을 보고 불필요한 파티션을 건너뜁니다.

SQL
-- 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 절에 파티션 키가 포함되어야 합니다.

파티션 관리

파티션 추가

SQL
-- 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
);

파티션 삭제

SQL
-- 오래된 파티션 삭제 (데이터도 함께 삭제됨)
ALTER TABLE orders DROP PARTITION p_2023;

파티션 DROP은 DELETE보다 훨씬 빠릅니다. 인덱스 업데이트나 Undo 로그 생성이 없기 때문입니다.

파티션 분할

SQL
-- 하나의 파티션을 둘로 나눔
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)
);

파티션 병합

SQL
-- 두 파티션을 하나로 합침
ALTER TABLE orders REORGANIZE PARTITION p_2025_h1, p_2025_h2 INTO (
    PARTITION p_2025 VALUES LESS THAN (2026)
);

파티셔닝의 제약사항

MySQL 파티셔닝에는 중요한 제약이 있습니다.

1. 파티션 키가 PK/UK에 포함되어야 함

SQL
-- 에러! 파티션 키(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 변경 불필요애플리케이션 또는 미들웨어 필요
확장성단일 서버 한계서버 추가로 수평 확장
복잡도낮음높음 (분산 트랜잭션, 조인 등)

파티셔닝은 단일 서버의 한계 내에서 성능을 개선하는 방법이고, 샤딩은 그 한계를 넘어서기 위한 방법입니다.

파티셔닝이 효과적인 경우

  1. 시계열 데이터: 로그, 이벤트, 주문 등 시간순 데이터

    • 오래된 데이터 삭제가 빈번 (파티션 DROP)
    • 최근 데이터 위주로 조회 (프루닝)
  2. 대용량 테이블의 관리 작업

    • 파티션 단위로 OPTIMIZE TABLE 수행
    • 파티션 단위로 백업/복원
  3. 특정 범위의 데이터만 자주 조회하는 경우

    • 프루닝으로 불필요한 스캔 방지

파티셔닝이 효과적이지 않은 경우

  1. 파티션 키가 WHERE 절에 포함되지 않는 쿼리가 많을 때
  2. 테이블 크기가 작을 때 (파티셔닝 오버헤드만 증가)
  3. 대부분의 쿼리가 PK로 단일 행을 조회할 때 (이미 충분히 빠름)

정리

  • 파티셔닝은 큰 테이블을 파티션 키 기준으로 나누어 특정 파티션만 스캔하게 하는 기능입니다
  • RANGE 파티셔닝이 가장 많이 사용되며, 날짜 기반 데이터에 효과적입니다
  • 파티션 프루닝이 동작하려면 WHERE 절에 파티션 키가 포함되어야 합니다
  • 파티션 키는 반드시 PK/UK에 포함되어야 하는 제약이 있습니다
  • 오래된 데이터 삭제 시 파티션 DROP은 DELETE보다 압도적으로 빠릅니다
댓글 로딩 중...