
DB 샤딩(Sharding): 10억 건 데이터를 처리하는 유일한 방법
테이블 하나에 10억 개의 행이 쌓이면 인덱스도 소용없습니다. 수직 파티셔닝(Vertical)과 수평 샤딩(Horizontal)의 차이, 일관된 해싱(Consistent Hashing), 그리고 샤딩의 치명적 단점인 JOIN 문제를 분석합니다.

테이블 하나에 10억 개의 행이 쌓이면 인덱스도 소용없습니다. 수직 파티셔닝(Vertical)과 수평 샤딩(Horizontal)의 차이, 일관된 해싱(Consistent Hashing), 그리고 샤딩의 치명적 단점인 JOIN 문제를 분석합니다.
로버트 C. 마틴(Uncle Bob)이 제안한 클린 아키텍처의 핵심은 무엇일까요? 양파 껍질 같은 계층 구조와 의존성 규칙(Dependency Rule)을 통해 프레임워크와 UI로부터 독립적인 소프트웨어를 만드는 방법을 정리합니다.

DB 설계의 기초. 데이터를 쪼개고 쪼개서 이상 현상(Anomaly)을 방지하는 과정. 제1, 2, 3 정규형을 쉽게 설명합니다.

왜 CPU는 빠른데 컴퓨터는 느릴까? 80년 전 고안된 폰 노이만 구조의 혁명적인 아이디어와, 그것이 남긴 치명적인 병목현상에 대해 정리했습니다.

프링글스 통(Stack)과 맛집 대기 줄(Queue). 가장 기초적인 자료구조지만, 이걸 모르면 재귀 함수도 메시지 큐도 이해할 수 없습니다.

동네 도서관에 책이 너무 많아서 건물이 무너지기 직전입니다. 해결책은 두 가지입니다.
샤딩(Sharding)은 거대한 데이터베이스를 조각(Shard)내어 여러 대의 서버에 나누어 저장하는 기술입니다. 데이터가 PB(페타바이트) 단위로 넘어가면 선택이 아닌 필수가 됩니다.
처음에는 "DB가 느리면 인덱스 걸면 되는 거 아닌가?"라고 생각했습니다. 실제로 인덱스를 추가하니 쿼리가 빨라졌고, 거기서 만족했습니다.
그런데 테이블에 행이 1억 개를 넘어가기 시작하자 상황이 달라졌습니다.
인덱스를 걸어도 느리고, EXPLAIN을 찍어보면 풀 테이블 스캔이 돌고 있었습니다.
Redis 캐시를 앞에 둬도 캐시 미스가 나면 결국 DB로 떨어지고, 그때마다 응답이 3초씩 걸렸습니다.
그때 관련 문서를 읽다가 "샤딩을 검토해볼 시간이다"라는 조언을 접했습니다. 처음엔 "데이터를 쪼갠다고? DB를 여러 대 쓴다고?"가 와닿지 않았지만, 공부하다 보니 왜 마지막 수단인지 이해할 수 있었습니다.
User)이 너무 비대할 때.
ID, Name, Email → 고성능 SSD 서버에.Biography, ProfilePicBlob → 저렴한 HDD 서버에."어떤 기준으로 데이터를 나눌 것인가?" — 이것이 Shard Key 선택의 문제이고, 샤딩에서 가장 중요한 결정입니다.
UserID 1WHERE date BETWEEN ...)에 유리합니다.UserID % 3 연산 결과가 0이면 A, 1이면 B, 2면 C.% 3이 % 4로 바뀌니까, 기존에 잘 있던 데이터들이 전부 다른 서버로 가야 합니다.UserID 1 → Shard A, UserID 55 → Shard B).해시 기반 샤딩의 재할당 문제를 해결하기 위해 고안된 알고리즘입니다. DynamoDB, Cassandra, Discord 등에서 사용합니다.
서버 S4가 새로 추가되어도, S4 근처에 있는 일부 데이터만 S4로 이동하면 됩니다. 다른 서버(S1, S2, S3)에 있던 데이터는 건드릴 필요가 없습니다. 데이터 이동량을 획기적으로 줄여, 무중단 확장이 가능해집니다.
일반 해시 샤딩에서는 서버 추가 시 데이터의 거의 100%를 재배치해야 하지만,
Consistent Hashing에서는 평균적으로 1/N(N은 서버 수)만 이동하면 됩니다.
실제로는 물리 서버 하나당 여러 개의 "가상 노드"를 링 위에 배치합니다. 서버가 3대인데 링 위에 점이 3개뿐이면 데이터가 고르게 분산되지 않을 수 있습니다. 가상 노드를 100~200개 찍어두면 훨씬 균등한 분산이 가능합니다.
샤딩은 만병통치약이 아닙니다. "샤딩 도입 = 개발 난이도 10배 상승"입니다.
User와 Shard B에 있는 Order를 JOIN 하려면?ACID) 보장이 매우 어렵습니다.ID를 1씩 증가시키면, Shard A에도 ID=100, Shard B에도 ID=100이 생깁니다. 중복 키 발생!간단한 해시 샤딩 라우터를 만들어보겠습니다.
class ShardingRouter {
constructor(shardMap) {
this.shards = shardMap; // { 0: 'DB_A', 1: 'DB_B', 2: 'DB_C' }
this.totalShards = Object.keys(shardMap).length;
}
getShard(key) {
const hash = this.simpleHash(key);
const shardIndex = hash % this.totalShards;
return this.shards[shardIndex];
}
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash += str.charCodeAt(i);
}
return hash;
}
}
const router = new ShardingRouter({ 0: 'Server 1', 1: 'Server 2', 2: 'Server 3' });
console.log(router.getShard("user_100")); // Server 2
console.log(router.getShard("user_101")); // Server 1
실제 프로덕션에서는 simpleHash 대신 CRC32나 MurmurHash 같은 검증된 해시 함수를 사용합니다.
단순 아스키코드 합은 충돌이 많아 데이터가 고르게 분산되지 않기 때문입니다.
애플리케이션은 샤딩된 걸 몰라야 좋습니다. 중간에 Router(Proxy)를 둡니다.
SELECT * FROM User WHERE id=123 (그냥 보냄).이 패턴의 장점은 샤딩 로직을 애플리케이션 코드에서 분리할 수 있다는 것입니다. 나중에 샤드 수를 늘리거나 재배치해도 애플리케이션 코드를 수정할 필요가 없습니다.
특정 샤드 키에 트래픽이 몰리는 문제입니다.
UserID 기준으로 샤딩했습니다.Justin Bieber나 BTS의 데이터가 있는 샤드 서버는 수백만 명의 조회 요청을 받습니다. (Hotspot). 반면, 제 계정이 있는 샤드는 파리만 날립니다.샤딩은 최후의 수단입니다. 도입 전에 시도해볼 것들:
EXPLAIN 찍어보고, 빠진 인덱스 추가.이 모든 것을 시도하고도 감당이 안 될 때 (보통 데이터 수 TB 이상), 그때 샤딩을 도입합니다. 너무 일찍 도입하면 복잡도 지옥에 빠집니다.