서비스를 운영하다 보면 데이터베이스의 크기는 자연스럽게 커진다. 사용자 수가 늘고, 로그와 이력 데이터가 쌓이며, 하나의 테이블에 수백만 건 이상의 데이터가 들어가게 된다. 이 시점부터는 단순히 “인덱스를 잘 잡으면 된다”는 접근만으로는 성능 문제를 해결하기 어려워진다.
테이블이 커질수록 읽기와 쓰기 비용은 증가하고, 인덱스 역시 함께 커지면서 쿼리 처리 시간이 점점 늘어난다. 이런 상황에서 데이터베이스 구조 자체를 나누는 전략이 필요해지는데, 그 대표적인 방법이 Partitioning, Sharding, 그리고 Replication이다.
Vertical Partitioning
Vertical Partitioning은 컬럼(column) 기준으로 테이블을 분리하는 방식이다. 하나의 테이블에 너무 많은 컬럼이 있거나, 일부 컬럼의 데이터 용량이 지나치게 큰 경우에 주로 사용된다.
예를 들어 블로그 서비스를 운영한다고 가정해보자. 하나의 게시글을 표현하기 위해 보통 제목, 작성자, 작성 시각뿐 아니라 실제 본문 내용까지 함께 저장한다. 이 모든 정보를 하나의 post 테이블에 넣어두면 구조는 단순하지만, 서비스 규모가 커질수록 성능 문제를 유발할 수 있다.

블로그의 게시글 목록 화면을 생각해보면, 사용자가 보는 것은 대개 제목, 작성자, 작성일, 간단한 요약 정보 정도다. 이때 게시글의 전체 본문(content)은 필요하지 않다. 컬럼을 명시해서 조회하더라도, InnoDB와 같은 row 기반 스토리지에서는 해당 row 전체를 읽은 뒤 필요한 컬럼만 반환하는 경우가 많다. 따라서 대용량 컬럼이 같은 테이블에 존재한다면, 단순히 SELECT *를 피하는 것만으로는 디스크 I/O 비용을 근본적으로 줄이기 어렵다.
설령 WHERE 절에 인덱스가 잘 걸려 있더라도, row 자체의 크기가 커지면 디스크 I/O 비용은 줄어들지 않는다. 목록 조회처럼 빈번하게 호출되는 쿼리에서 이런 불필요한 데이터 로딩이 반복되면, 전체 서비스 성능에 점진적인 부담이 쌓이게 된다.
이 문제를 해결하기 위해 게시글 테이블을 역할에 따라 분리할 수 있다. 예를 들어 제목과 메타데이터만 담은 post_summary 테이블과, 실제 본문 내용을 담은 post_content 테이블로 나누는 방식이다. 목록 화면에서는 가벼운 post_summary만 조회하고, 사용자가 게시글 상세 페이지에 들어왔을 때만 post_content를 추가로 조회하거나 JOIN해서 가져온다.

이렇게 하면 게시글 목록 조회는 훨씬 가벼워지고, 디스크 I/O와 메모리 사용량도 자연스럽게 줄어든다. 결국 Vertical Partitioning은 데이터 모델을 논리적으로 쪼개기 위한 기법이라기보다는, 실제 사용 패턴에 맞춰 성능을 최적화하기 위한 구조적 선택이라고 볼 수 있다.
Horizontal Partitioning
Horizontal Partitioning은 row(행) 기준으로 데이터를 나누는 방식이다. 테이블의 스키마는 동일하지만, 데이터만 여러 테이블로 분산된다.
테이블의 데이터가 수천만 건 이상으로 커지면, 인덱스 역시 함께 커진다. 그 결과 데이터가 추가되거나 조회될 때마다 인덱스 탐색 비용이 점점 늘어나고, 읽기·쓰기 성능 모두에 영향을 미친다. 이럴 때 테이블을 여러 개로 나눠 각 테이블의 크기를 줄이는 방식이 Horizontal Partitioning이다.
예를 들어 유튜브와 같은 서비스를 생각해보자.
유저와 채널이 있고, “유저가 어떤 채널을 구독하고 있는지”를 저장하는 테이블이 있다고 가정하면, 이 테이블의 최대 크기는 유저 수 × 채널 수에 가까워질 수 있다. 서비스 규모가 커질수록 이 테이블은 빠르게 비대해진다.

이 문제를 해결하기 위해 가장 흔히 사용하는 방식이 Hash 기반 Horizontal Partitioning이다. 예를 들어 user_id를 기준으로 해시 함수를 적용해, 그 결과에 따라 테이블을 subscription_0, subscription_1 같은 여러 테이블로 나누는 방식이다. 같은 user_id는 항상 같은 테이블에 저장되기 때문에, 특정 유저의 구독 정보를 조회할 때 어느 테이블을 조회해야 하는지도 명확해진다.
Partition Key 선택이 중요한 이유
이때 가장 중요한 점은 partition key를 실제 사용 패턴에 맞게 선택해야 한다는 것이다.
대부분의 쿼리가 user_id를 기준으로 이루어진다면 user_id를 partition key로 사용하는 것은 합리적인 선택이다. 하지만 만약 “이 채널을 구독한 유저 목록”처럼 channel_id 기준 조회가 자주 발생한다면 이야기가 달라진다. user_id를 기준으로 파티셔닝된 상태에서는, 특정 channel_id에 대한 정보를 얻기 위해 모든 파티션을 조회해야 하는 상황이 발생할 수 있다. 이는 파티셔닝을 하지 않았을 때보다 오히려 성능이 더 나빠질 수도 있다.
결국 Horizontal Partitioning은 가장 빈번하게 사용되는 쿼리 패턴이 무엇인지를 먼저 파악한 뒤, 그에 맞는 partition key를 선택해야 효과를 볼 수 있다.
한 번 나누면 되돌리기 어려운 구조
또 한 가지 주의할 점은, 한 번 파티션을 나누고 나면 이후에 구조를 변경하기가 쉽지 않다는 것이다.
새로운 파티션 테이블을 추가하거나 해시 함수 로직을 수정하는 것 자체는 비교적 간단할 수 있다. 하지만 문제는 이미 저장되어 있는 기존 데이터를 다시 분리하고 재배치해야 하는 과정이다. 데이터가 많을수록 이 작업은 매우 부담스러워진다.
그래서 Horizontal Partitioning은 “일단 해보자” 식으로 도입하기보다는,
서비스의 성장 방향과 쿼리 패턴을 충분히 고려한 뒤 적절한 시점과 기준을 신중하게 결정해야 하는 구조적 선택이라고 볼 수 있다.
Sharding
Sharding은 개념적으로는 Horizontal Partitioning과 매우 유사하다. 차이점은 각 파티션이 서로 다른 DB 서버에 저장된다는 점이다.
기존 Horizontal Partitioning은 하나의 DB 서버 안에서 테이블만 나눴다면, Sharding은 아예 DB 서버 자체를 여러 개로 분리한다. 이때 partition key는 shard key라고 부르고, 각 partition은 shard라고 부른다.

이 방식의 가장 큰 장점은 트래픽 분산이다. 특정 테이블에 대한 요청이 몰려도, shard key에 따라 서로 다른 DB 서버로 요청이 분산되기 때문에 단일 DB 서버가 병목이 되는 상황을 줄일 수 있다. 데이터 용량과 트래픽이 동시에 증가하는 서비스에서는 Sharding이 사실상 필수적인 선택이 되기도 한다.
다만 Sharding은 구조가 복잡해지고, cross-shard JOIN이나 트랜잭션 처리에 제약이 생긴다는 단점도 함께 따른다.
Replication
Replication은 데이터를 나누는 방식이 아니라, 같은 데이터를 여러 DB 서버에 복제하는 방식이다. 일반적으로 하나의 Primary DB 서버와 여러 개의 Secondary(Replica) 서버로 구성된다.
쓰기 작업은 Primary 서버에서만 처리되고, 읽기 작업은 Secondary 서버로 분산된다. 대부분의 서비스에서 Read 요청이 Write 요청보다 훨씬 많기 때문에, 이 구조만으로도 전체 DB 부하를 크게 줄일 수 있다.

또한 Primary 서버에 장애가 발생했을 때 Secondary 서버로 전환하는 Failover를 통해 서비스 중단을 최소화할 수 있다. 이런 이유로 Replication은 성능 개선뿐 아니라 High Availability(HA) 확보를 위한 핵심 구성 요소로 사용된다.
똑같은 테이블을 어떻게 유지하지?
보통 write 요청은 Primary DB 서버로 보내고, read 요청은 Replica DB 서버로 보내는 구조를 사용한다. 그렇다면 자연스럽게 이런 의문이 든다.
“Replica들도 write로 변경된 내용을 알고 있어야 하는데, 이걸 백엔드 애플리케이션에서 맞춰주는 걸까?”
처음에는 backend application이 write 요청과 read 요청에 따라 각각 다른 DB 서버에 요청을 보내니, 두 서버의 데이터를 맞추는 역할도 백엔드가 담당하는 것이 아닐까?라고 생각했다. 하지만 조금만 생각해보면 이 방식은 굉장히 비효율적이다. 애초에 트래픽을 분산하기 위해 read와 write를 분리했는데, 동일한 데이터를 유지하기 위해 백엔드가 여러 DB 서버에 요청을 보내거나 동기화를 관리해야 한다면, 구조가 지나치게 복잡해진다.
그래서 실제로는 이 역할을 백엔드 애플리케이션이 아니라 DB 서버 자체가 담당한다. Replication 환경에서는 Primary DB 서버에서 발생한 모든 데이터 변경 사항이 로그 형태로 기록되고, Replica DB 서버들은 이 로그를 받아 동일한 변경을 순서대로 적용한다. 이 과정은 DB 엔진 내부에서 자동으로 처리되며, 애플리케이션은 전혀 관여하지 않는다.
즉, backend application의 역할은 단순하다.
write 요청은 Primary로 보내고, read 요청은 Replica로 보내는 것뿐이다. 데이터 변경 내용을 Replica에게 전달하고, 테이블 상태를 동일하게 유지하는 책임은 전적으로 DB 서버 간 통신에 있다.
이 구조 덕분에 애플리케이션은 데이터 동기화라는 복잡한 문제에서 자유로워지고, DB 서버는 자신이 가장 잘하는 방식으로 데이터 정합성을 유지할 수 있다. 결과적으로 Replication은 트래픽 분산뿐만 아니라, 시스템 전체 구조를 단순하게 만드는 역할도 함께 수행한다.
DB 서버 간에 실제로 어떤 방식으로 통신이 이루어지는지는, 다음 글에서 조금 더 자세히 정리해보려고 한다.
정리해보면, Partitioning, Sharding, Replication은 DB 서버의 트래픽을 처리하기 위한 서로 다른 방법이지만, 서로를 대체하는 관계는 아니다. 오히려 데이터의 크기와 트래픽이 증가하는 과정에서 단계적으로 선택하게 되는 전략들에 가깝다.
중요한 것은 “어떤 기술이 더 좋은가”를 따지는 것이 아니라, 현재 서비스에서 어디가 병목인지를 정확히 파악하고,
그 병목을 해결하기에 가장 적절한 구조를 선택하는 것이다.
쉬운 코드 : BJ.51 DB 파티셔닝? 샤딩? 레플리케이션? (partitioning? sharding? replication?)
'CS > DB' 카테고리의 다른 글
| NoSQL은 뭐지? SQL을 안 쓰는 걸까 (0) | 2025.12.29 |
|---|---|
| DBCP가 뭐지..? (0) | 2025.12.28 |
| 데이터베이스 성능을 저하시키는 N+1 쿼리 문제란? (0) | 2025.10.03 |
| Transaction 알아보기 (0) | 2025.09.15 |
| Enum VS VARCHAR (0) | 2025.09.15 |
