파티셔닝 (수평, 수직, 샤딩)
데이터베이스 한 대로 처리할 수 있는 행 수와 디스크 용량에는 분명한 상한이 있다. 한 테이블에 1억 행이 쌓이면 인덱스 트리 깊이가 깊어져 같은 쿼리가 10배 느려지고, 디스크가 가득 차면 새 행을 받지 못한다. 본 글은 이 상한을 푸는 두 단계 기법인 파티셔닝(Partitioning)과 샤딩(Sharding)을 다룬다. 수평 파티셔닝·수직 파티셔닝의 차이, 같은 DB 안에서의 파티셔닝과 여러 DB로 쪼개는 샤딩의 차이, 그리고 두 기법이 만드는 새로운 운영 부담까지 4학년 데이터베이스 시스템 수업 맥락에 비추어 정리한다(출처: 위키백과 — Partition (database)). 제가 학교 캡스톤에서 5만 행짜리 로그 테이블이 6개월 만에 1200만 행이 되면서 조회가 2~3초씩 걸리는 모습을 본 후로는 "한 테이블에 모든 데이터를 담는 흐름은 처음부터 의문을 가져야 한다"는 사실을 손끝으로 받아들였다.

수평 파티셔닝과 수직 파티셔닝의 차이
파티셔닝이란 한 테이블의 데이터를 여러 물리적 단위로 쪼개어 저장하는 기법을 가리키며, 쪼개는 방향에 따라 수평과 수직 두 가지로 나뉜다.
수평 파티셔닝(Horizontal Partitioning)은 행 단위로 자른다. 예를 들어 주문 테이블을 2024년·2025년·2026년 세 파티션으로 나누면, 한 해의 데이터가 한 파티션에 모이고 쿼리가 특정 연도만 조회할 때 다른 파티션은 아예 스캔하지 않는다. 이런 동작을 파티션 프루닝(Partition Pruning)이라 부르며, 여기서 프루닝이란 옵티마이저가 쿼리 조건을 보고 접근할 필요가 없는 파티션을 미리 잘라내는 최적화 기법을 의미한다.
-- MySQL 수평 파티셔닝 예시 (날짜 기준 RANGE)
CREATE TABLE orders (
id BIGINT, user_id BIGINT, amount INT, created_at DATETIME,
PRIMARY KEY (id, created_at)
)
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026),
PARTITION p2026 VALUES LESS THAN (2027)
);
수직 파티셔닝(Vertical Partitioning)은 컬럼 단위로 자른다. 한 행의 컬럼이 너무 많거나 일부 컬럼이 거의 안 읽히는 경우, 자주 읽는 컬럼만 본체 테이블에 두고 거의 안 읽는 큰 컬럼(예: 첨부 본문·이미지 BLOB)은 별도 테이블에 분리한다. 본체 테이블의 한 행 크기가 작아지면 한 페이지(보통 16KB)에 더 많은 행이 들어가고, 같은 메모리로 더 많은 데이터를 캐싱할 수 있다.
다만 두 기법 모두 데이터를 어떻게 나눌지에 대한 키 선택이 핵심이며, 잘못 잡으면 오히려 성능이 더 나빠진다. 솔직히 제 경험상 입문 단계에서는 "파티셔닝 = 무조건 빨라진다"고 오해하기 쉬운데, 파티션 키와 쿼리 조건이 어긋나는 순간 모든 파티션을 다 스캔하게 되어 단일 테이블보다 더 느린 상황도 흔하다.
같은 DB 파티셔닝과 여러 DB 샤딩의 차이
파티셔닝은 같은 데이터베이스 인스턴스 안에서 일어나는 분할이다. 한 서버, 한 디스크, 하나의 트랜잭션 관리자가 그대로 유지된다. 반면 샤딩(Sharding)은 데이터를 아예 다른 데이터베이스 인스턴스로 쪼개는 분할이다. 즉 샤딩은 수평 파티셔닝의 한 형태이면서, 그 파티션들이 물리적으로 다른 서버에 흩어진 경우를 가리킨다(출처: 위키백과 — Shard (database architecture)).
샤딩의 직관적 예가 사용자 ID 기준 분할이다. user_id를 4로 나눈 나머지에 따라 4개 샤드로 보내면, 사용자별 데이터는 항상 같은 샤드에 모이고 한 사용자에 대한 쿼리는 한 샤드만 건드린다. 흔히 쓰는 두 방식이 해시 샤딩과 범위 샤딩이며, 해시 샤딩은 키를 해시 함수로 분산해 균등하게 나누는 방식, 범위 샤딩은 키 범위(예: user_id 1~999만 명 → 샤드 A, 1000만 명 이상 → 샤드 B)로 나누는 방식이다. 해시는 분포가 균등하지만 범위 쿼리에 약하고, 범위는 범위 쿼리는 빠르지만 핫스팟(특정 샤드에 트래픽이 몰리는 현상)이 생기기 쉽다.
샤딩은 단일 서버의 한계를 거의 무한히 확장할 수 있다는 점에서 강력하지만, 그 대가가 같이 따라온다. 가장 큰 대가가 샤드 간 조인(Cross-shard Join)과 트랜잭션의 비용이다. 두 사용자의 데이터를 한 쿼리로 묶거나, 두 샤드에 걸친 송금을 하나의 트랜잭션으로 처리하려면 분산 트랜잭션(2PC, Two-Phase Commit)이 필요한데, 이 메커니즘은 단일 DB의 트랜잭션보다 10배 가까이 느리고 운영 복잡도도 비약적으로 올라간다. 즉 샤딩은 "분산 시스템의 모든 복잡성"을 동시에 받아들이는 결정이다.
흔한 오해 하나를 짚으면, "테이블이 크면 무조건 샤딩"이라는 통념이다. 그러나 1억 행 수준은 적절한 인덱스·파티셔닝·읽기 복제만으로도 단일 DB가 충분히 처리한다. 본인이 학교 캡스톤에서 1200만 행 로그를 다뤘을 때도 파티셔닝과 BRIN 인덱스(Block Range Index)만으로 응답 시간이 다시 100ms 안쪽으로 들어왔고, 샤딩까지 가지 않아도 됐다는 점이 결정적 경험이었다. 일반적인 기준선은 단일 DB 한계가 명확히 보일 때(보통 수억~수십억 행 또는 단일 인스턴스 디스크가 70% 이상 차는 시점)이며, 그 전엔 파티셔닝·읽기 복제·아카이빙으로 충분하다.
파티셔닝·샤딩이 만드는 새 운영 부담 (인덱스, 키 충돌, 마이그레이션)
파티셔닝과 샤딩은 도입 시점부터 새로운 운영 책임을 떠넘긴다. 본인이 정리한 실전 빈도 순서로 세 가지를 짚는다.
첫째, 인덱스의 글로벌성 상실이다. 단일 테이블의 PK 인덱스는 그 자체로 전역 유일성을 보장하지만, 파티셔닝된 테이블의 인덱스는 기본적으로 각 파티션 안에서만 동작한다(로컬 인덱스). 전역 유니크 제약을 걸려면 추가 비용이 들고, 일부 DBMS는 글로벌 인덱스를 아예 지원하지 않는다.
둘째, 자동 증가 키의 충돌이다. 샤딩 환경에서 각 샤드가 독립적으로 1, 2, 3으로 자동 증가시키면 ID가 충돌한다. 표준 해결책은 트위터의 Snowflake처럼 시간·머신·시퀀스를 조합한 분산 ID 생성기이며, 또는 UUID·ULID 같은 충돌 확률이 무한히 낮은 식별자 체계로 전환한다.
셋째, 리샤딩(Resharding)의 비용이다. 4개 샤드로 시작했다가 8개로 늘리려면 절반의 데이터를 새 샤드로 이동시켜야 하는데, 이 과정은 운영 중인 서비스에는 거의 무중단으로 일어나야 한다. 일관 해시(Consistent Hashing)나 슬롯 기반 분할(Redis Cluster 16384 슬롯 등)을 도입하면 이 비용이 절반 이하로 떨어지지만, 그 자체로 또 한 층의 학습 곡선을 만든다.
파티셔닝은 데이터베이스의 성능 천장을 부드럽게 끌어올리는 도구이고, 샤딩은 그 천장을 아예 다른 차원으로 넘기는 도구다. 둘은 같은 선상의 도구처럼 보이지만 운영 비용은 한 자릿수 차이가 난다. "큰 테이블이 보이면 일단 파티셔닝, 그래도 안 풀리면 그제야 샤딩"이라는 순서를 손에 잡고 가는 게, 적어도 본인 캡스톤에서 검증된 가장 안전한 진로였다.