테마
파티션 2
파티션에 이어 파티션(partition)을 설계할 때 걸리는 제약과 종류, 성능 함정을 다룬다. 제약은 대부분 "파티션마다 인덱스를 따로 가진다"는 한 가지 사실에서 나온다. 그래서 파티셔닝(partitioning)은 쿼리를 빠르게 하는 만능 도구가 아니라, 데이터를 조각 단위로 관리하려고 쓰는 도구에 가깝다.
제약 1: 파티션 키는 모든 유니크 인덱스에 들어가야 한다
행이 들어갈 파티션을 정하는 칼럼을 파티션 키(partition key)라 한다. MySQL 매뉴얼의 규칙은 이렇다. 파티션 식(PARTITION BY 뒤에 쓰는 칼럼이나 식, 예: YEAR(created_at))에 쓰인 칼럼은 테이블의 모든 유니크 키에 포함되어야 한다. 기본 키(primary key)도 유니크 키이므로 이 규칙을 따른다. 테이블에 유니크 인덱스(unique index)가 하나도 없을 때만 이 제약이 없다.
이유는 인덱스가 파티션마다 따로 있기 때문이다. email에 유니크 인덱스를 걸고 테이블을 가입일로 나눴다고 해 보자. 같은 이메일이 9월 파티션과 10월 파티션에 들어오면, 각 파티션의 인덱스는 자기 안만 보므로 중복을 알아채지 못한다. 유니크 키에 파티션 키가 들어 있으면, 같은 키 값을 가진 행은 늘 같은 파티션에 가므로 파티션 하나 안에서만 검사해도 된다.
sql
-- 에러: 파티션 키 created_at이 기본 키 (id)에 없다
CREATE TABLE t (id BIGINT PRIMARY KEY, created_at DATETIME NOT NULL)
PARTITION BY RANGE COLUMNS (created_at) (PARTITION p0 VALUES LESS THAN ('2027-01-01'));그래서 날짜로 나눈 테이블은 기본 키를 (id, created_at)처럼 만든다. 그 결과 "id만으로 유일"이나 "이메일은 전체에서 유일" 같은 규칙은 DB가 지켜 주지 못한다. 이런 보장이 꼭 필요하면 파티션을 쓰지 않거나, 유일성 검사를 다른 테이블이나 애플리케이션에서 해야 한다.
제약 2: 그 밖에 알아 둘 것
파티션을 지원하는 스토리지 엔진(storage engine)은 정해져 있다. 또 InnoDB 파티션 테이블은 외래 키(foreign key)를 쓸 수 없으므로, 없는 부모를 가리키는 행을 막는 참조 무결성(referential integrity)을 애플리케이션이 지켜야 한다(외래키와 잠금 대기 참고). 세션 동안만 쓰는 임시 테이블(temporary table)도 파티셔닝할 수 없다.
| 제약 | 내용(MySQL 8.4) |
|---|---|
| 스토리지 엔진 | InnoDB와 NDB(MySQL Cluster용 엔진)만 지원. MyISAM 테이블은 파티셔닝할 수 없다 |
| 외래 키 | InnoDB 파티션 테이블은 외래 키를 가질 수도, 참조될 수도 없다 |
| 인덱스·타입 | FULLTEXT 인덱스, 공간 데이터 칼럼 불가 |
| 임시 테이블 | 파티셔닝할 수 없다 |
| 파티션 수 | 서브파티션(subpartition)까지 합쳐 최대 8,192개 |
| 파티션 식 | 정수(또는 정수가 되는 식). 칼럼 값을 그대로 받는 KEY·COLUMNS 계열만 예외 |
파티션을 만든 뒤에는 서버 SQL 모드(SQL mode)를 바꾸지 말라는 매뉴얼 경고도 있다. SQL 모드는 서버가 SQL을 얼마나 엄격하게 해석할지 정하는 설정인데, 이것이 바뀌면 파티션 식의 계산 결과가 달라져 테이블을 못 쓰게 될 수 있다.
파티션 종류
| 종류 | 행을 나누는 기준 | 어울리는 데이터 |
|---|---|---|
| RANGE | 값의 구간(LESS THAN) | 날짜별 이력·로그 |
| LIST | 값의 목록(IN (…)) | 지역·상태 코드 |
| RANGE·LIST COLUMNS | 칼럼 값 그대로(날짜·문자열, 여러 칼럼) | 날짜를 함수 없이 나눌 때 |
| HASH | 사용자 식 값을 파티션 수로 나눈 나머지 | 고르게 흩뿌리기 |
| KEY | MySQL 내부 해시 함수 | 정수가 아닌 칼럼 흩뿌리기 |
- RANGE가 가장 흔하다. 날짜로 나누고 오래된 구간을 버리는 패턴이 파티셔닝의 대표 용도다. RANGE는 정수 식만 받으므로 날짜는
YEAR(created_at)이나TO_DAYS(created_at)처럼 정수로 바꾼다. RANGE COLUMNS를 쓰면DATE·DATETIME칼럼을 함수 없이 바로 쓸 수 있다. - HASH는
MOD(식, 파티션 수)로 파티션을 정한다. 구간 개념이 없으므로 오래된 데이터를 파티션째 버리는 용도에는 맞지 않는다. - KEY는 HASH와 같은 방식이지만 해시 함수를 MySQL이 제공한다. 그래서 문자열처럼 정수가 아닌 칼럼도 쓸 수 있다(TEXT·BLOB 제외).
PARTITION BY KEY()처럼 칼럼을 비우면 기본 키를 쓴다. - LINEAR HASH·LINEAR KEY는 나머지 대신, 파티션 수 이상인 가장 작은 2의 거듭제곱을 기준으로 값의 비트를 잘라 파티션을 고른다. 이렇게 하면 파티션을 추가·삭제·병합·분할할 때 옮길 데이터가 적어 관리가 훨씬 빠르다. 대신 일반 HASH보다 데이터가 덜 고르게 퍼질 수 있다.
- 서브파티션은 파티션을 한 번 더 나누는 것이다. RANGE·LIST 파티션만 HASH·KEY로 다시 나눌 수 있다.
쿼리 성능 주의
파티셔닝이 오히려 쿼리를 느리게 하는 경우도 있다.
- 파티션 키 조건이 없는 쿼리는 모든 파티션을 훑는다. 예를 들어 날짜로 나눈 테이블에서
user_id로만 찾으면,user_id세컨더리 인덱스(secondary index)가 파티션마다 따로 있으므로 파티션 수만큼 인덱스를 따로 탐색한다. 파티션이 없는 테이블이라면 인덱스 한 번이면 끝날 일이다. - 작은 테이블이나 한 건 조회 위주의 테이블에는 이득이 거의 없다. 이런 쿼리는 인덱스가 이미 필요한 행만 짚어 주므로, 먼저 인덱스를 설계하는 편이 낫다(B-Tree 활용 참고).
- 파티션이 너무 많으면 관리 비용이 커진다. 파티션마다 파일과 메타데이터가 따로 있어 열어야 할 파일이 늘어난다. 매뉴얼도 파일을 여는 도중 자원 부족 에러가 나면, 서버가 동시에 열 수 있는 파일 수의 상한인
open_files_limit을 늘리라고 안내한다. - 파티셔닝은 샤딩(sharding)이 아니다. 샤딩은 데이터를 여러 서버에 나눠 담는 것이다. 파티션은 모두 같은 서버의 같은 자원을 쓰므로 서버 한 대의 CPU·메모리 한계를 넘게 해 주지는 않는다.
면접 포인트
- "파티션 테이블에서 유니크 키 제약은?" → 파티션 키 칼럼이 모든 유니크 키(기본 키 포함)에 들어가야 한다. 인덱스가 파티션별이라 파티션 간 중복을 검사할 수 없기 때문.
- "파티션을 쓰면 무조건 빨라지나요?" → 아니다. 파티션 키 조건이 없으면 파티션 수만큼 탐색. 이득은 조각 단위 삭제와 파티션 프루닝(partition pruning).
- "외래 키와 같이 쓸 수 있나요?" → InnoDB 파티션 테이블은 외래 키 미지원.
- RANGE는 날짜 이력, HASH·KEY는 고른 분산. LINEAR는 관리가 빠르고 분포가 덜 고르다.
