분산 데이터베이스 기초
데이터를 여러 서버에 분산하는 두 가지 핵심 기법이 레플리케이션(Replication)과 샤딩(Sharding)입니다. 단일 서버의 한계(처리량, 가용성, 저장 용량)를 극복하기 위해 분산 아키텍처를 도입하며, 이때 발생하는 일관성·가용성·파티션 내성 사이의 트레이드오프를 이해해야 합니다.
분산 데이터베이스가 필요한 이유
┌──────────────────────────────────────────────────────┐
│ 단일 서버 아키텍처 │
│ │
│ ┌─────────────┐ │
│ │ Application │ ────→ [ DB Server ] │
│ └─────────────┘ (1대) │
│ │
│ 한계: │
│ * 처리량: CPU/메모리 한계 → 스케일업 비용 급증 │
│ * 가용성: 서버 1대 장애 = 서비스 중단 │
│ * 저장량: 디스크 용량 한계 │
│ * 지연: 먼 지역 사용자 → 네트워크 지연 │
│ │
│ 해결: 분산 아키텍처 (레플리케이션 + 샤딩) │
└──────────────────────────────────────────────────────┘| 분산 목적 | 기법 | 설명 |
|---|---|---|
| 읽기 확장 | 레플리케이션 | 읽기 요청을 여러 복제본에 분산 |
| 쓰기 확장 | 샤딩 | 데이터를 분할하여 쓰기 분산 |
| 고가용성 | 레플리케이션 | 한 서버 장애 시 다른 서버가 대체 |
| 지역 분산 | 멀티 리전 복제 | 사용자와 가까운 서버에서 응답 |
레플리케이션
레플리케이션은 같은 데이터를 여러 서버에 복사하여 가용성과 읽기 성능을 높이는 기법입니다.
마스터-슬레이브 (Primary-Replica)
가장 일반적인 레플리케이션 구조입니다. 쓰기는 마스터만, 읽기는 슬레이브에서 처리합니다.
마스터-슬레이브 구조
┌────────────┐
│ Master │ ← 쓰기 (INSERT, UPDATE, DELETE)
│ (Primary) │
└──────┬─────┘
│ 복제 (Binlog / WAL / Redo)
├──────────────→ Slave1 (Read)
└──────────────→ Slave2 (Read)
애플리케이션 관점
App ──W──→ Master
App ──R──→ Slave1
App ──R──→ Slave2
↑
Load Balancer로 읽기 분산| 장점 | 단점 |
|---|---|
| 읽기 부하 분산 | 쓰기는 Master만 가능 |
| Master 장애 시 Slave가 승격 | 복제 지연(Replication Lag) |
| 구현이 비교적 단순 | Master가 단일 장애점(SPOF) |
| 백업을 Slave에서 수행 가능 | 쓰기 확장 불가 |
복제 방식: 동기 vs 비동기
복제의 동기화 수준에 따라 데이터 일관성과 성능 사이의 트레이드오프가 달라집니다.
──── 동기 복제 (Synchronous) ────
Master: INSERT → Slave에 전송 → Slave 응답 대기 → COMMIT
장점: 데이터 손실 0 (RPO=0)
단점: 쓰기 지연 증가, Slave 장애 시 Master도 멈춤
──── 비동기 복제 (Asynchronous) ────
Master: INSERT → COMMIT → 나중에 Slave 전송
장점: 쓰기 성능 최상
단점: Master 장애 시 미복제 데이터 유실 가능
──── 반동기 복제 (Semi-Synchronous) ────
Master: INSERT → 최소 1개 Slave 수신 확인 → COMMIT
장점: 적어도 1개 Slave에는 데이터 존재
단점: 동기보다 빠르지만 비동기보다 느림
──── 각 DBMS 설정 ────
MySQL: INSTALL PLUGIN rpl_semi_sync_master;
SET GLOBAL rpl_semi_sync_master_enabled = 1;
PostgreSQL: synchronous_standby_names = 'standby1'
synchronous_commit = on -- 동기
synchronous_commit = remote_apply -- 강한 동기
Oracle Data Guard:
Maximum Performance → 비동기
Maximum Availability → 반동기
Maximum Protection → 동기 (데이터 손실 0)복제 지연(Replication Lag) 문제
비동기 복제에서 마스터와 슬레이브의 데이터가 일시적으로 다른 현상입니다.
시나리오: 사용자가 프로필 사진을 변경한 직후 새로고침
1. APP → Master: UPDATE users SET avatar='new.jpg' (쓰기)
2. Master: 커밋 완료 ✓
3. APP → Slave: SELECT avatar FROM users (읽기)
4. Slave: 아직 복제 안 됨 → avatar='old.jpg' 반환!
→ 사용자: "변경이 안 됐나?" 혼란
해결 방법
┌─────────────────────────────────────────────────────┐
│ 1. Read-Your-Writes 보장 │
│ → 방금 쓴 사용자의 읽기는 Master에서 조회 │
│ → 다른 사용자의 읽기는 Slave에서 조회 │
│ │
│ 2. 세션 기반 라우팅 │
│ → 쓰기 후 일정 시간(예: 3초) Master에서 읽기 │
│ → 이후 Slave로 전환 │
│ │
│ 3. 반동기/동기 복제 사용 │
│ → 최소 1개 Slave 복제 확인 후 응답 │
│ │
│ 4. GTID 기반 읽기 대기 │
│ → 쓰기 시 GTID 기록 │
│ → 읽기 시 해당 GTID가 Slave에 반영될 때까지 대기 │
└─────────────────────────────────────────────────────┘Failover (장애 조치)
마스터 장애 시 슬레이브를 새로운 마스터로 승격하는 과정입니다.
정상 상태:
[Master] ─복제→ [Slave 1]
─복제→ [Slave 2]
Master 장애 감지:
1. 주기적 헬스체크 (Heartbeat)
2. 연속 N회 실패 → 장애 판정
Failover 수행:
1. 가장 최신 데이터를 가진 Slave 선택
2. 해당 Slave를 새 Master로 승격 (Promote)
3. 나머지 Slave를 새 Master에 연결
4. 애플리케이션의 쓰기 대상을 새 Master로 전환
5. VIP(Virtual IP) 또는 DNS 전환
[Slave 1 → New Master] ← 쓰기 전환
─복제→ [Slave 2]
Failover 도구:
MySQL: MHA, Orchestrator, ProxySQL
PostgreSQL: Patroni, repmgr, pg_auto_failover
Oracle: Data Guard Broker (DGMGRL)마스터-마스터 (Multi-Master)
Master A ↔ Master B
┌────────┐
│Master A│
│ 쓰기 │
└────────┘
↕ 양방향 복제
┌────────┐
│Master B│
│ 쓰기 │
└────────┘
결과: 둘 다 쓰기 가능양쪽에서 동시에 같은 데이터를 수정하면 충돌이 발생합니다. 충돌 해결 정책이 필요합니다.
충돌 시나리오
Master A: UPDATE users SET name='Kim' WHERE id=1;
Master B: UPDATE users SET name='Lee' WHERE id=1;
→ 동시에 실행 → 어느 값이 최종?
해결 전략
1. 타임스탬프 우선 (Last Write Wins)
→ 더 늦은 타임스탬프 값이 우선
→ 단순하지만 데이터 유실 가능
2. 노드 우선순위
→ Master A > Master B로 우선순위 설정
→ 충돌 시 높은 우선순위 노드의 값 채택
3. 애플리케이션 레벨 해결
→ 충돌을 기록하고 사용자에게 선택 제시
→ Git의 merge conflict와 유사
4. 충돌 회피 설계
→ 각 Master가 다른 데이터를 담당
→ 지역 A 사용자 → Master A, 지역 B → Master B| 비교 | Master-Slave | Multi-Master |
|---|---|---|
| 쓰기 확장 | 불가 | 가능 |
| 복잡도 | 낮음 | 높음 |
| 충돌 | 없음 | 발생 가능 |
| 가용성 | Failover 필요 | 즉시 전환 가능 |
| 주요 사용 | 대부분의 시스템 | 멀티 리전, 특수 요구 |
샤딩
샤딩(Sharding)은 데이터를 여러 서버에 나누어 저장하는 기법입니다. 레플리케이션이 복사라면, 샤딩은 분할입니다. 단일 서버로 감당할 수 없는 쓰기 부하나 저장량을 해결합니다.
전체 데이터: [A B C D E F G H I J K L]
샤드 1: [A B C D] → 서버 1 (4건)
샤드 2: [E F G H] → 서버 2 (4건)
샤드 3: [I J K L] → 서버 3 (4건)
각 서버가 1/3의 부하만 처리
→ 수평 확장(Scale-Out) 가능샤드 키 선택
샤딩의 성패는 샤드 키(Shard Key) 선택에 달려 있습니다.
1. 높은 카디널리티 (값의 종류가 많음)
좋음: user_id (수백만 개)
나쁨: gender (2개) → 2개 샤드로만 분할
2. 균등한 분포 (데이터 편중 없음)
좋음: UUID, user_id
나쁨: 국가 코드 → 특정 국가에 편중
3. 자주 사용되는 쿼리에 포함
좋음: 대부분 쿼리가 WHERE user_id = ? 형태
나쁨: 쿼리와 무관한 키 → Cross-Shard 쿼리 증가
4. 변경되지 않는 값
좋음: user_id (불변)
나쁨: email (변경 가능) → 샤드 이동 필요해시 기반 샤딩
샤드 키: user_id
샤드 수: 3
hash(user_id) % 3 = 0 → 샤드 0
hash(user_id) % 3 = 1 → 샤드 1
hash(user_id) % 3 = 2 → 샤드 2
장점
* 데이터 균등 분배
* 핫스팟 방지
* 특정 키의 샤드를 즉시 계산 가능
단점
* 범위 쿼리 비효율 (전체 샤드 스캔)
* 샤드 추가/제거 시 대량 데이터 재배치
* 재배치 비용: 샤드 3개→4개 시 75% 데이터 이동범위 기반 샤딩
샤드 키: 날짜
2023년 데이터 → 샤드 1
2024년 데이터 → 샤드 2
2025년 데이터 → 샤드 3
장점:
* 범위 쿼리 효율적 (해당 샤드만 스캔)
* 구현이 직관적
* 오래된 데이터 아카이빙 용이
단점:
* 핫스팟 발생 (최신 데이터에 쓰기 집중)
* 데이터 불균형 가능샤드 조회 테이블 (Lookup Table)
┌───────────────┬───────────┐
│ 샤드 키 범위 │ 샤드 위치 │
├───────────────┼───────────┤
│user 1~1000 │ 샤드 A │
│user 1001~3000 │ 샤드 B │
│user 3001~10000│ 샤드 C │
└───────────────┴───────────┘
장점
* 유연한 샤드 배정 (불균형 보정 가능)
* 특정 키를 다른 샤드로 이동 가능
단점
* Lookup Table이 SPOF
* 조회 오버헤드 (매 요청마다 참조)일관성 해싱
해시 기반 샤딩에서 샤드를 추가하거나 제거할 때 일관성 해싱(Consistent Hashing)은 데이터 재배치를 최소화합니다.
──── 일반 해시 (Modular) ────
3개 노드: hash(key) % 3
4개 노드로 변경: hash(key) % 4
→ 거의 모든 키의 위치가 변경! (재배치 75%)
──── 일관성 해싱 ────
해시 링(Hash Ring) 위에 노드 배치
0°
N1
/ |
/ |
N4 | 90°
| N2
| /
\ /
N3
180°
규칙: 키는 시계 방향으로 가장 가까운 노드에 저장
노드 추가 시
N1
/ | \
N5 | \ ← N1과 N2 사이에 N5 추가
| | N2
| | /
\ |/
N3
→ N5 추가 시 N1~N5 구간의 키만 N5로 이동
→ 나머지 키는 이동 없음!
→ 재배치 비율: K/N (전체 키 수 / 노드 수)물리 노드가 적으면 데이터 편중이 심할 수 있음
→ 가상 노드로 해결
물리 노드 3개, 가상 노드 각 4개
N1 → N1-1, N1-2, N1-3, N1-4 (링 위 4곳)
N2 → N2-1, N2-2, N2-3, N2-4 (링 위 4곳)
N3 → N3-1, N3-2, N3-3, N3-4 (링 위 4곳)
→ 12개 지점이 링 위에 균등 배치
→ 데이터가 더 고르게 분산
사용 제품
* Amazon DynamoDB
* Apache Cassandra
* Redis Cluster (해시 슬롯 16384개)샤딩의 과제
샤딩은 강력하지만 여러 운영 과제를 수반합니다.
1. Cross-Shard 쿼리
→ 여러 샤드를 걸치는 쿼리 → 성능 저하
예: SELECT * FROM orders WHERE date BETWEEN ...
AND user_id IN (...)
→ 모든 샤드에 쿼리 후 애플리케이션에서 병합
2. Cross-Shard JOIN
→ 서로 다른 샤드의 테이블 JOIN 불가
→ 해결: 애플리케이션 레벨 JOIN, 또는 데이터 비정규화
3. Cross-Shard 트랜잭션
→ 여러 샤드에 걸친 ACID 트랜잭션 어려움
→ 해결: 2PC (느림), Saga 패턴 (Eventually Consistent)
4. 리밸런싱
→ 데이터 편중 시 샤드 간 데이터 이동 필요
→ 서비스 운영 중 리밸런싱 복잡
5. 샤드 키 변경 불가
→ 초기 설계가 중요
→ 잘못된 샤드 키 → 전체 재설계 필요
6. Auto-Increment ID
→ 샤드별 ID 중복 방지 필요
→ UUID, Snowflake ID, 또는 ID 생성 서비스 사용┌──────────────────┬──────────────────────┬──────────────────┐
│ 전략 │ 예시 │ 특징 │
├──────────────────┼──────────────────────┼──────────────────┤
│ UUID v4 │ 550e8400-e29b-41d4 │ 충돌 거의 없음 │
│ │ -a716-446655440000 │ 정렬 불가, 인덱스│
│ │ │ 효율 낮음 │
│ Snowflake ID │ 1771836590123456789 │ 시간순 정렬 가능 │
│ (Twitter) │ [시간|워커|시퀀스] │ 64비트 정수 │
│ ULID │ 01ARZ3NDEKTSV4R... │ UUID + 시간 정렬 │
│ 샤드 ID + Seq │ [샤드번호][시퀀스] │ 단순, 샤드 식별 │
│ 중앙 발급 │ ID 서버에서 배치 발급│ SPOF 위험 │
└──────────────────┴──────────────────────┴──────────────────┘레플리케이션 + 샤딩 조합
실무에서는 레플리케이션과 샤딩을 함께 사용합니다.
┌───────────────────────────────────────────────────┐
│ 전체 아키텍처 │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 라우터 (ProxySQL / Vitess / ShardingSphere) │ │
│ └──────────┬──────────┬──────────┬─────────────┘ │
│ │ │ │ │
│ 샤드 1 샤드 2 샤드 3 │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Master│ │Master│ │Master│ │
│ └───┬──┘ └───┬──┘ └───┬──┘ │
│ ┌───┴──┐ ┌───┴──┐ ┌───┴──┐ │
│ │Slave │ │Slave │ │Slave │ │
│ │Slave │ │Slave │ │Slave │ │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ 각 샤드 = Master-Slave 레플리케이션 구성 │
│ * 샤딩으로 쓰기 확장 │
│ * 레플리케이션으로 읽기 확장 + 가용성 확보 │
└───────────────────────────────────────────────────┘합의 알고리즘
분산 시스템에서 여러 노드가 하나의 값에 합의하는 방법입니다. 노드 장애가 발생해도 시스템이 올바르게 동작하기 위해 필수적입니다.
| 알고리즘 | 특징 | 사용 제품 |
|---|---|---|
| Raft | 이해하기 쉬움, 리더 기반 | etcd, CockroachDB, TiDB |
| Paxos | 이론적 근간, 구현 복잡 | Google Spanner, Chubby |
| ZAB | Zookeeper 전용, Paxos 변형 | Apache Zookeeper |
| PBFT | 비잔틴 장애 허용 | 블록체인 일부 |
──── 노드 상태 ────
1. Leader: 클라이언트 요청 처리, 로그 복제
2. Follower: Leader의 로그 복제 수신
3. Candidate: Leader 선출 투표 참여
──── 정상 작동 ────
Client → Leader: 쓰기 요청
Leader: 로그에 기록
Leader → Followers: 로그 복제 전송
Followers: 로그 응답 (ACK)
Leader: 과반수 ACK → 커밋 → Client 응답
──── Leader 장애 시 ────
Follower: 헬스체크 타임아웃 (150~300ms)
→ Candidate 상태로 전환
→ 임기(Term) 증가
→ 자신에게 투표 + 다른 노드에게 투표 요청
투표 규칙:
* 한 임기에 한 표만 가능
* 로그가 더 최신인 Candidate에게만 투표
* 과반수(N/2 + 1) 득표 → 새 Leader 당선
Split Brain 방지:
* 과반수 합의 → 두 Leader 동시 존재 불가
* 3노드 → 1대 장애 허용
* 5노드 → 2대 장애 허용
* 공식: (N-1)/2 대 장애 허용CAP 정리와 분산 DB
분산 시스템에서는 CAP 3가지 속성을 동시에 모두 만족할 수 없습니다.
C (Consistency)
/ \
/ \
/ \
/ CAP \
/ 정리 \
/ \
A ─────────── P
(Availability) (Partition Tolerance)
C: 일관성 → 모든 노드가 같은 데이터
A: 가용성 → 모든 요청에 응답
P: 파티션 내성 → 네트워크 분할에도 동작
네트워크 파티션(P)은 피할 수 없으므로,
실질적으로 C와 A 중 선택:
CP 시스템: 일관성 우선 (파티션 시 일부 요청 거부)
→ HBase, MongoDB (강한 일관성 설정)
→ 금융, 재고 관리
AP 시스템: 가용성 우선 (파티션 시 다른 데이터 반환 가능)
→ Cassandra, DynamoDB
→ SNS, 센서 데이터, 로그네트워크 파티션이 발생하면 (P):
C(일관성) vs A(가용성) 선택
그렇지 않으면 (E = Else):
L(지연시간) vs C(일관성) 선택
┌──────────────┬────────────┬──────────────┐
│ 시스템 │ P 시 선택 │ E 시 선택 │
├──────────────┼────────────┼──────────────┤
│ MySQL(InnoDB)│ C (일관성) │ C (일관성) │
│ Cassandra │ A (가용성) │ L (낮은 지연)│
│ MongoDB │ C (일관성) │ C (일관성) │
│ DynamoDB │ A (가용성) │ L (낮은 지연)│
│ CockroachDB │ C (일관성) │ C (일관성) │
│ Spanner │ C (일관성) │ L (TrueTime) │
└──────────────┴────────────┴──────────────┘분산 DB 제품 비교
┌─────────────────────────────────────────────────────────┐
│ 분산 DB 제품 비교 │
├──────────────┬──────────┬──────────┬────────────────────┤
│ 제품 │ 유형 │ CAP │ 특징 │
├──────────────┼──────────┼──────────┼────────────────────┤
│ CockroachDB │ NewSQL │ CP │ Raft, 분산 트랜잭션│
│ TiDB │ NewSQL │ CP │ MySQL 호환, Raft │
│Google Spanner│ NewSQL │ CP(+A) │ TrueTime, 전역 분산│
│ Vitess │ 미들웨어 │ (MySQL) │ MySQL 샤딩 계층 │
│ Citus │ 확장 │ (PG) │PostgreSQL 분산 확장│
│ Amazon Aurora│ 클라우드 │ CP │ MySQL/PG 호환 │
│ PlanetScale │ 클라우드 │ CP │ Vitess 기반 MySQL │
├──────────────┼──────────┼──────────┼────────────────────┤
│ Cassandra │ Wide-Col │ AP │ 링 구조, 높은 쓰기 │
│ DynamoDB │ Key-Value│ AP │ AWS 관리형 │
│ MongoDB │ Document │ CP │ 자동 샤딩, 유연한 │
│ │ │ │ 스키마 │
└──────────────┴──────────┴──────────┴────────────────────┘분산 데이터베이스 종합 정리
┌─────────────────────────────────────────────────────────┐
│ 분산 DB 도입 의사결정 │
├─────────────────────────────────────────────────────────┤
│ │
│ Q1. 단일 서버로 감당 가능한가? │
│ YES → 분산 불필요 (단순함이 최고) │
│ NO → Q2로 │
│ │
│ Q2. 읽기 확장만 필요한가? │
│ YES → 레플리케이션 (Master-Slave) │
│ NO → Q3로 │
│ │
│ Q3. 쓰기도 확장이 필요한가? │
│ YES → 샤딩 (+ 레플리케이션 병행) │
│ NO → 레플리케이션으로 충분 │
│ │
│ Q4. 강한 일관성이 필요한가? │
│ YES → NewSQL (CockroachDB, Spanner, TiDB) │
│ NO → NoSQL (Cassandra, DynamoDB) │
│ │
│ Q5. 멀티 리전 분산이 필요한가? │
│ YES → Spanner, CockroachDB, Cassandra │
│ NO → 단일 리전 샤딩 │
│ │
└─────────────────────────────────────────────────────────┘| 핵심 개념 | 설명 |
|---|---|
| 레플리케이션 | 같은 데이터 복사 → 읽기 확장, 가용성 |
| 샤딩 | 데이터 분할 → 쓰기 확장, 저장량 확장 |
| 일관성 해싱 | 노드 변경 시 최소 재배치 |
| 합의 알고리즘 | 노드 장애에도 올바른 결정 |
| CAP 정리 | C·A·P 중 2개만 선택 가능 |
| Failover | 자동 장애 감지 후 대체 노드 승격 |
| 복제 지연 | 비동기 복제에서 일시적 불일치 |
| 샤드 키 | 데이터 분배 기준 → 설계 핵심 |
다음 절에서는 RDB와 NoSQL의 선택 기준을 다루겠습니다.