Theme:

데이터가 단일 서버에 다 담기지 않을 만큼 커지면, 복제로는 해결할 수 없습니다. 데이터 자체를 여러 서버에 나눠야 한다면 어떻게 해야 할까요?

샤딩이란

샤딩(Sharding)은 하나의 데이터셋을 여러 데이터베이스 서버에 분산 저장하는 수평 분할 기법입니다.

PLAINTEXT
단일 서버:
┌──────────────────────┐
│ 모든 사용자 데이터     │  ← 한계에 도달
│ (10억 행)             │
└──────────────────────┘

샤딩 후:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Shard 0   │ │ Shard 1   │ │ Shard 2   │ │ Shard 3   │
│ user 0-   │ │ user 250M-│ │ user 500M-│ │ user 750M-│
│ 250M      │ │ 500M      │ │ 750M      │ │ 1B        │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
   서버 A       서버 B       서버 C       서버 D

복제(Replication)는 같은 데이터를 복사하는 것이고, 샤딩은 다른 데이터를 나누는 것입니다.

구분복제샤딩
목적읽기 분산, 고가용성쓰기 분산, 저장 용량 확장
데이터모든 서버가 동일 데이터각 서버가 일부 데이터
확장 대상읽기 처리량읽기 + 쓰기 처리량 + 저장 용량

샤딩 전략

1. 해시 기반 샤딩 (Hash Sharding)

샤드 키의 해시값으로 데이터가 어느 샤드에 갈지 결정합니다.

PLAINTEXT
shard_id = hash(user_id) % num_shards

user_id = 12345 → hash(12345) % 4 = 1 → Shard 1
user_id = 67890 → hash(67890) % 4 = 3 → Shard 3

장점:

  • 데이터가 균등하게 분배됩니다
  • 구현이 단순합니다

단점:

  • 범위 쿼리(WHERE user_id BETWEEN 1000 AND 2000)를 효율적으로 처리할 수 없습니다
  • 샤드 수를 변경하면 거의 모든 데이터의 재분배가 필요합니다

Consistent Hashing

일반 해시의 리샤딩 문제를 완화하는 방법입니다.

PLAINTEXT
일반 해시: 샤드 4개 → 5개로 변경 시 ~80% 데이터 이동
Consistent Hashing: 샤드 추가 시 ~1/N의 데이터만 이동

해시 링(Hash Ring) 위에 샤드와 데이터를 배치하여, 샤드 추가/제거 시 최소한의 데이터만 이동합니다.

2. 범위 기반 샤딩 (Range Sharding)

연속된 값의 범위로 샤드를 나눕니다.

PLAINTEXT
Shard 0: user_id 1 ~ 1,000,000
Shard 1: user_id 1,000,001 ~ 2,000,000
Shard 2: user_id 2,000,001 ~ 3,000,000

장점:

  • 범위 쿼리가 효율적입니다 (해당 샤드만 조회)
  • 데이터의 위치를 쉽게 예측할 수 있습니다

단점:

  • 핫스팟 문제: 새 사용자가 항상 마지막 샤드에 집중됩니다
  • 데이터 분포가 불균형해질 수 있습니다

3. 디렉토리 기반 샤딩 (Directory Sharding)

별도의 매핑 테이블(디렉토리)이 어떤 데이터가 어느 샤드에 있는지 관리합니다.

PLAINTEXT
디렉토리 서비스 (Redis/별도 DB):
user_id 12345 → Shard 2
user_id 67890 → Shard 0
user_id 11111 → Shard 3

장점:

  • 유연한 데이터 배치 (특정 사용자를 특정 샤드로 이동 가능)
  • 리샤딩 시 디렉토리만 업데이트

단점:

  • 디렉토리 자체가 단일 장애점(SPOF)이 될 수 있습니다
  • 모든 쿼리가 디렉토리를 먼저 조회해야 합니다

샤드 키 선택

샤딩에서 가장 중요한 결정입니다.

좋은 샤드 키의 조건

  1. 높은 카디널리티: 값이 다양해야 균등 분배가 가능합니다
  2. 쿼리 패턴과 일치: 대부분의 쿼리에 샤드 키가 포함되어야 합니다
  3. 균등 분배: 특정 샤드에 데이터가 몰리지 않아야 합니다
PLAINTEXT
좋은 샤드 키: user_id
→ 사용자별로 데이터가 분산
→ "내 주문 목록" 같은 쿼리가 단일 샤드에서 처리됨

나쁜 샤드 키: country
→ 한국, 미국 등 특정 국가에 데이터 집중
→ 핫스팟 발생

크로스 샤드 쿼리의 어려움

크로스 샤드 조인

SQL
-- 단일 DB에서는 간단
SELECT u.name, o.amount
FROM users u JOIN orders o ON u.id = o.user_id
WHERE o.amount > 10000;

-- 샤딩 후: users와 orders가 다른 샤드에 있을 수 있음
-- → 애플리케이션에서 두 샤드에 각각 쿼리 후 결과를 합침

해결 방법:

  • 같은 샤드 키로 코로케이션: 같은 user_id의 users와 orders를 같은 샤드에 배치
  • 글로벌 테이블: 변경이 드문 작은 테이블(국가 코드 등)은 모든 샤드에 복제

크로스 샤드 집계

SQL
-- 전체 매출 합계
SELECT SUM(amount) FROM orders;
-- → 모든 샤드에 쿼리 → 결과를 합산

분산 트랜잭션

여러 샤드에 걸친 트랜잭션은 2PC(Two-Phase Commit)가 필요합니다.

PLAINTEXT
1단계 (Prepare): 모든 샤드에 "커밋할 준비 됐나?" 확인
2단계 (Commit): 모든 샤드가 OK 응답 시 커밋 실행

하나라도 실패하면 전체 롤백

2PC는 복잡하고 느리므로, 가능하면 트랜잭션이 단일 샤드 내에서 완결되도록 설계합니다.

글로벌 유니크 ID

Auto Increment PK는 각 샤드에서 독립적으로 증가하므로 전체적으로 유니크하지 않습니다.

PLAINTEXT
Shard 0: id = 1, 2, 3, ...
Shard 1: id = 1, 2, 3, ...  ← 충돌!

해결 방법:

  • UUID: 전역 유니크, 하지만 인덱스 성능 저하 (랜덤 분포)
  • Snowflake ID: Twitter가 개발한 방식. 타임스탬프 + 워커ID + 시퀀스
  • 범위 할당: Shard 0은 11000000, Shard 1은 10000012000000
PLAINTEXT
Snowflake ID 구조 (64비트):
[1 bit 사인] [41 bit 타임스탬프] [10 bit 머신ID] [12 bit 시퀀스]

샤딩 미들웨어

Vitess

YouTube(Google)에서 개발한 MySQL 샤딩 플랫폼입니다.

PLAINTEXT
애플리케이션 → VTGate (프록시) → VTTablet → MySQL
                                VTTablet → MySQL
                                VTTablet → MySQL

주요 기능:

  • 자동 라우팅: 샤드 키를 분석하여 올바른 샤드로 전달
  • 온라인 리샤딩: 서비스 중단 없이 샤드 분할/병합
  • 스키마 관리: 모든 샤드에 DDL을 일관되게 적용
  • 연결 풀링: MySQL 연결을 효율적으로 관리

ProxySQL

MySQL 프록시 레이어로, 읽기/쓰기 분리와 기본적인 샤딩 라우팅을 지원합니다.

PLAINTEXT
애플리케이션 → ProxySQL → 쓰기: Source
                       → 읽기: Replica (라운드 로빈)

쿼리 규칙을 설정하여 특정 패턴의 쿼리를 특정 서버로 라우팅할 수 있습니다.

리샤딩 전략

샤드 수를 변경하거나 데이터를 재분배하는 과정입니다. 가장 어려운 운영 작업 중 하나입니다.

1. 2배 확장 (Split)

기존 샤드를 2개로 분할합니다. 해시 기반에서 가장 단순합니다.

PLAINTEXT
Before: Shard 0 (hash % 2 == 0), Shard 1 (hash % 2 == 1)
After:  Shard 0 (hash % 4 == 0), Shard 2 (hash % 4 == 2)  ← 기존 Shard 0에서 분리
        Shard 1 (hash % 4 == 1), Shard 3 (hash % 4 == 3)  ← 기존 Shard 1에서 분리

2. 온라인 리샤딩 과정

PLAINTEXT
1. 새 샤드 서버 준비
2. 기존 샤드에서 새 샤드로 데이터 복제 시작 (Binlog 기반)
3. 복제가 따라잡으면 쓰기 일시 중단 (짧은 시간)
4. 라우팅 규칙을 새 구성으로 변경
5. 쓰기 재개
6. 기존 샤드에서 이동한 데이터 정리

Vitess는 이 과정을 자동화합니다.

3. 그림자 쓰기 (Shadow Writing)

PLAINTEXT
1. 기존 샤드에 쓰기 계속
2. 동시에 새 샤드 구성으로도 데이터 이중 쓰기
3. 데이터 일관성 검증
4. 라우팅을 새 구성으로 전환
5. 기존 구성 제거

샤딩 도입 전 체크리스트

샤딩은 복잡도가 매우 높으므로, 먼저 다른 방법을 시도해야 합니다.

  1. 쿼리 최적화: 인덱스, 쿼리 튜닝으로 해결 가능한가?
  2. 수직 분할: 특정 컬럼을 별도 테이블로 분리하면 해결되는가?
  3. 읽기 복제: 읽기 부하가 문제라면 복제본으로 분산 가능한가?
  4. 캐싱: Redis 등으로 데이터베이스 부하를 줄일 수 있는가?
  5. 아카이빙: 오래된 데이터를 별도 저장소로 이동하면 되는가?

이 모든 방법으로도 해결이 안 될 때 샤딩을 도입합니다.

정리

  • 샤딩은 데이터를 여러 서버에 분산하여 단일 서버의 한계를 넘는 기법입니다
  • 해시/범위/디렉토리 기반 전략이 있으며, 샤드 키 선택이 성능의 핵심입니다
  • 크로스 샤드 조인, 분산 트랜잭션, 글로벌 유니크 ID 등의 과제가 있습니다
  • Vitess 같은 미들웨어가 샤딩의 복잡성을 관리하는 데 도움을 줍니다
  • 샤딩은 최후의 수단이며, 쿼리 최적화와 복제를 먼저 시도해야 합니다
댓글 로딩 중...