회사에서 파티셔닝을 자주 사용하는데, 막상 내 이해도가 따라가지 못하고 있다는 걸 깨달았다. FK를 아예 지원하지 않는다는 사실조차 모르고 있었다. 이번 기회에 제대로 정리해봤다. 참고로 MySQL은 글로벌 인덱스를 지원하지 않으며, 각 파티션 내에 로컬 인덱스가 독립적으로 생성된다.

수천만 건 이하라면 인덱스 최적화가 먼저다. 파티셔닝은 복잡성을 추가하는 만큼, 정말 필요할 때만 도입해야 한다.

파티셔닝이란?

거대한 테이블을 작은 덩어리로 쪼개어 물리적으로 나누어 저장하는 기술

사용자에게는 하나의 논리적 테이블로 보이지만, 내부적으로는 여러 파일로 나누어 관리된다.

전체 테이블
┌─────────────────────────┐
│ 2023년 데이터 → 파티션 A │
│ 2024년 데이터 → 파티션 B │
│ 2025년 데이터 → 파티션 C │
└─────────────────────────┘

파티셔닝이 도움이 되는 경우

장점설명
쿼리 성능 향상WHERE 조건에 맞는 파티션만 스캔
데이터 관리 용이오래된 파티션을 통째로 삭제 (DELETE보다 훨씬 빠름)
저장 용량 분산여러 디스크에 분산 저장 가능

핵심 제약: PK/Unique Key 규칙

파티션 키는 반드시 PK나 Unique Key에 포함되어야 한다.

이유: “모든 파티션을 뒤지지 않고도 유일성을 보장하기 위해서”

예를 들어 id가 PK인 테이블을 created_at 기준으로 파티셔닝했다고 가정해보자:

id=10 삽입 시도
  ↓
이 id가 유일한지 확인해야 함
  ↓
created_at이 파티션 키이므로 id=10이 어느 파티션에 있는지 알 수 없음
  ↓
모든 파티션을 다 뒤져야 함 → 파티셔닝 의미 없음

파티셔닝 종류

RANGE 파티셔닝 (가장 흔함)

날짜/숫자 범위로 분할:

CREATE TABLE orders (
    id INT NOT NULL,
    order_date DATE NOT NULL,
    amount DECIMAL(10,2),
    PRIMARY KEY (id, order_date)  -- 파티션 키 포함!
)
PARTITION BY RANGE (YEAR(order_date)) (
    PARTITION p2023 VALUES LESS THAN (2024),
    PARTITION p2024 VALUES LESS THAN (2025),
    PARTITION p2025 VALUES LESS THAN (2026),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

적합한 경우: 날짜별 로그, 월별 매출 데이터

LIST 파티셔닝

미리 정해진 값 목록으로 분할:

CREATE TABLE users (
    id INT NOT NULL,
    country_code VARCHAR(2) NOT NULL,
    PRIMARY KEY (id, country_code)
)
PARTITION BY LIST COLUMNS(country_code) (
    PARTITION p_kr VALUES IN ('KR'),
    PARTITION p_us VALUES IN ('US'),
    PARTITION p_jp VALUES IN ('JP')
);

적합한 경우: 국가 코드, 카테고리 ID

HASH 파티셔닝

균등하게 분산:

CREATE TABLE sessions (
    id INT NOT NULL PRIMARY KEY
)
PARTITION BY HASH(id)
PARTITIONS 4;

적합한 경우: 특정 그룹핑이 어려운 경우

KEY 파티셔닝

HASH와 유사하지만 MySQL 내부 해시 함수(MD5 또는 PASSWORD())를 사용한다. PK 기반으로 균등 분산할 때 유용하며, 파티션 키에 별도 표현식을 쓸 필요가 없다.

CREATE TABLE logs (
    id INT NOT NULL PRIMARY KEY
)
PARTITION BY KEY(id)
PARTITIONS 4;

파티션 프루닝 (Partition Pruning)

파티셔닝의 핵심 성능 포인트다.

옵티마이저가 WHERE 절을 보고 불필요한 파티션을 제외한다.

프루닝이 동작하는 경우

EXPLAIN SELECT * FROM orders WHERE order_date = '2024-06-01';
partitions: p2024  ← 이 파티션만 스캔

프루닝이 동작하지 않는 경우

EXPLAIN SELECT * FROM orders WHERE amount > 1000;
partitions: p2023,p2024,p2025,p_future  ← 모든 파티션 스캔

WHERE 절에 파티션 키가 없으면 모든 파티션을 스캔한다.

FK(Foreign Key) 미지원

MySQL InnoDB는 파티셔닝된 테이블에 FK를 지원하지 않는다.

대안

방법설명
애플리케이션 레벨 검증코드에서 직접 데이터 존재 여부 확인
역정규화필요한 정보를 중복 저장하여 JOIN 감소
설계 재검토FK가 꼭 필요하다면 파티셔닝이 최선인지 재고
# 애플리케이션 레벨 검증 예시
def delete_user(user_id: int):
    if has_orders(user_id):
        raise Error("관련 주문이 존재합니다")
    delete_user_from_db(user_id)

자주 하는 실수

실수결과
파티션 키 없이 쿼리모든 파티션 풀 스캔
PK에 파티션 키 미포함테이블 생성 자체가 안 됨
FK가 필요한 테이블에 적용FK 제약 사용 불가
너무 많은 파티션관리 복잡성 증가

파티셔닝은 대용량 데이터 관리에 강력한 도구지만, 파티션 키 선택프루닝 동작 여부가 성능의 핵심이다.


파티션 추가/재분배

새 파티션 추가 (RANGE)

-- 2026년 파티션 추가
ALTER TABLE orders ADD PARTITION (
    PARTITION p2026 VALUES LESS THAN (2027)
);

p_future가 있으면 먼저 재구성이 필요하다:

ALTER TABLE orders REORGANIZE PARTITION p_future INTO (
    PARTITION p2026 VALUES LESS THAN (2027),
    PARTITION p_future VALUES LESS THAN MAXVALUE
);

파티션 삭제 (오래된 데이터 정리)

-- 2023년 데이터 삭제 (DELETE보다 훨씬 빠름)
ALTER TABLE orders DROP PARTITION p2023;

주의: 파티션 삭제는 해당 데이터를 영구 삭제한다. 백업 확인 필수.


성능 벤치마크

아래 수치는 상대적인 차이를 보여주기 위한 참고치이며, 정확한 성능은 하드웨어, MySQL 버전, innodb_buffer_pool_size 등에 따라 달라진다. (1억 건, 연도별 RANGE 파티셔닝, 5개 파티션):

쿼리파티셔닝 전파티셔닝 후
특정 연도 조회8.2초0.4초 (프루닝)
전체 집계12.5초11.8초 (큰 차이 없음)
오래된 데이터 삭제45분 (DELETE)3초 (DROP PARTITION)

프루닝이 동작해야 효과가 있다. 파티션 키가 WHERE에 없으면 모든 파티션을 스캔한다.


참고자료