데이터베이스 복제 (Database Replication)
데이터베이스 복제 (Database Replication)¶
난이도: ⭐⭐⭐ (중급)¶
개요¶
데이터베이스 복제(Replication)는 동일한 데이터를 여러 노드에 복사하여 가용성, 내결함성, 읽기 성능을 향상시키는 기술입니다. 이 문서에서는 다양한 복제 전략과 일관성 보장 메커니즘, 장애 복구 방법을 학습합니다.
목차¶
- 복제의 개념과 목적
- 단일 리더 복제
- 다중 리더 복제
- 리더 없는 복제
- 동기/비동기 복제
- 복제 지연과 일관성 문제
- 장애 복구와 리더 선출
- Quorum과 일관성 수준
- 연습 문제
- 다음 단계
- 참고 자료
1. 복제의 개념과 목적¶
복제가 필요한 이유¶
┌─────────────────────────────────────────────────────────────────┐
│ 복제의 주요 목적 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 고가용성 (High Availability) │
│ ┌─────────┐ ┌─────────┐ │
│ │ Primary │ ──X──> │ Replica │ ← Primary 장애 시 │
│ │ (Down) │ │ (Active)│ Replica가 서비스 유지 │
│ └─────────┘ └─────────┘ │
│ │
│ 2. 읽기 확장성 (Read Scalability) │
│ ┌─────────┐ │
│ │ Primary │────┬────> Replica 1 ←─── Read │
│ │ (Write) │ ├────> Replica 2 ←─── Read │
│ └─────────┘ └────> Replica 3 ←─── Read │
│ │
│ 3. 지리적 분산 (Geographical Distribution) │
│ │
│ Seoul ──────────────> Tokyo ──────────────> US-West │
│ [Primary] [Replica] [Replica] │
│ 지연: 0ms 지연: ~30ms 지연: ~100ms │
│ │
│ 4. 데이터 보호 (Data Protection) │
│ - 하드웨어 장애 대비 │
│ - 데이터 센터 장애 대비 │
│ - 재해 복구 (Disaster Recovery) │
│ │
└─────────────────────────────────────────────────────────────────┘
복제 아키텍처 유형¶
┌─────────────────────────────────────────────────────────────────┐
│ 복제 아키텍처 비교 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 단일 리더 (Single-Leader) │
│ ┌────────┐ │
│ │ Leader │ ──Write──> │
│ └───┬────┘ │
│ │ Replicate │
│ ┌───┴───────────┐ │
│ ▼ ▼ ▼ │
│ Follower Follower Follower │
│ │
│ 2. 다중 리더 (Multi-Leader) │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │Leader 1│<───>│Leader 2│<───>│Leader 3│ │
│ └───┬────┘ └───┬────┘ └───┬────┘ │
│ │ │ │ │
│ Follower Follower Follower │
│ │
│ 3. 리더 없음 (Leaderless) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 1│ │Node 2│ │Node 3│ │
│ └──────┘ └──────┘ └──────┘ │
│ ▲ ▲ ▲ │
│ └──────────┴──────────┘ │
│ 모든 노드가 동등 │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 단일 리더 복제¶
동작 원리¶
┌─────────────────────────────────────────────────────────────────┐
│ 단일 리더 복제 (Single-Leader) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client │
│ │ │
│ │ Write Request │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Leader (Primary) │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ Transaction Log │ │ │
│ │ │ [1] INSERT INTO users... │ │ │
│ │ │ [2] UPDATE orders... │ │ │
│ │ │ [3] DELETE from cart... │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └─────────────────┬───────────────────────┘ │
│ │ │
│ ┌─────────┼─────────┐ │
│ │ Replication Log │ │
│ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ Follower1 │ │ Follower2 │ │ Follower3 │ │
│ │ (Replica) │ │ (Replica) │ │ (Replica) │ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │
│ └─────────────┴─────────────┘ │
│ │ │
│ Read Requests │
│ ▲ │
│ Clients │
│ │
└─────────────────────────────────────────────────────────────────┘
복제 방식¶
┌─────────────────────────────────────────────────────────────────┐
│ 복제 방식 비교 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Statement-Based Replication │
│ ┌─────────────────────────────────────────┐ │
│ │ Leader: INSERT INTO users VALUES (...) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Follower: INSERT INTO users VALUES (...) │ ← SQL 재실행 │
│ └─────────────────────────────────────────┘ │
│ 문제점: NOW(), RAND() 등 비결정적 함수 │
│ │
│ 2. Write-Ahead Log (WAL) Shipping │
│ ┌─────────────────────────────────────────┐ │
│ │ Leader WAL: │ │
│ │ [Page 5, Offset 120, Data: 0x45AB...] │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Follower: 바이트 단위 동일하게 적용 │ ← 저수준 복제 │
│ └─────────────────────────────────────────┘ │
│ 문제점: 버전 호환성 (업그레이드 시 다운타임) │
│ │
│ 3. Row-Based (Logical) Replication │
│ ┌─────────────────────────────────────────┐ │
│ │ Change Log: │ │
│ │ {table: "users", │ │
│ │ op: "INSERT", │ │
│ │ new_row: {id:1, name:"Kim"}} │ ← 논리적 변경 │
│ │ │ │ │
│ │ ▼ │ │
│ │ Follower: 행 데이터 적용 │ │
│ └─────────────────────────────────────────┘ │
│ 장점: 버전 호환, 유연한 복제 가능 │
│ │
└─────────────────────────────────────────────────────────────────┘
PostgreSQL 스트리밍 복제 예시¶
-- Primary 설정 (postgresql.conf)
wal_level = replica
max_wal_senders = 10
wal_keep_size = 1GB
-- Primary: 복제 슬롯 생성
SELECT * FROM pg_create_physical_replication_slot('replica1_slot');
-- Standby 설정 (postgresql.conf)
primary_conninfo = 'host=primary-host port=5432 user=replicator'
primary_slot_name = 'replica1_slot'
-- 복제 상태 확인
SELECT
client_addr,
state,
sent_lsn,
write_lsn,
flush_lsn,
replay_lsn,
pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replication_lag
FROM pg_stat_replication;
MySQL 복제 설정 예시¶
-- Master 설정 (my.cnf)
-- server-id=1
-- log_bin=mysql-bin
-- binlog_format=ROW
-- Master: 복제 사용자 생성
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
SHOW MASTER STATUS;
-- Slave 설정
CHANGE MASTER TO
MASTER_HOST='master-host',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=154;
START SLAVE;
SHOW SLAVE STATUS\G
3. 다중 리더 복제¶
아키텍처¶
┌─────────────────────────────────────────────────────────────────┐
│ 다중 리더 복제 (Multi-Leader) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Data Center A Data Center B │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Leader A │ <────> │ Leader B │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ users: 1 │ │ 동기화 │ │ users: 1 │ │ │
│ │ │ orders: 5 │ │ <────> │ │ orders: 5 │ │ │
│ │ └───────────┘ │ │ └───────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌───┴───┐ │ │ ┌───┴───┐ │ │
│ │ ▼ ▼ │ │ ▼ ▼ │ │
│ │ Follower Follower│ │ Follower Follower│ │
│ └─────────────────┘ └─────────────────┘ │
│ ▲ ▲ │
│ │ │ │
│ [Clients A] [Clients B] │
│ 지연: ~1ms 지연: ~1ms │
│ │
│ 장점: │
│ - 각 데이터센터에서 낮은 쓰기 지연 │
│ - 데이터센터 장애 시 다른 곳에서 계속 쓰기 가능 │
│ │
│ 단점: │
│ - 충돌 해결 필요 │
│ - 복잡성 증가 │
│ │
└─────────────────────────────────────────────────────────────────┘
쓰기 충돌 문제¶
┌─────────────────────────────────────────────────────────────────┐
│ 쓰기 충돌 시나리오 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 시간 ────────────────────────────────────────────────> │
│ │
│ Leader A: │
│ T1: UPDATE users SET name='Kim' WHERE id=1 │
│ │ │
│ ▼ (복제) │
│ │
│ Leader B: │
│ T1: UPDATE users SET name='Lee' WHERE id=1 │
│ │ │
│ ▼ (복제) │
│ │
│ 결과: │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Leader A │ │ Leader B │ │
│ │ name='Lee'? │ ≠ │ name='Kim'? │ 충돌! │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
충돌 해결 전략¶
┌─────────────────────────────────────────────────────────────────┐
│ 충돌 해결 전략 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Last Write Wins (LWW) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Write A: {value: "Kim", timestamp: 100} │ │
│ │ Write B: {value: "Lee", timestamp: 105} │ │
│ │ │ │
│ │ Result: "Lee" (timestamp 105 > 100) │ │
│ └─────────────────────────────────────────────┘ │
│ 문제점: 데이터 손실 가능 │
│ │
│ 2. Version Vector (벡터 클록) │
│ ┌─────────────────────────────────────────────┐ │
│ │ Node A: {value: "Kim", version: [A:1, B:0]} │ │
│ │ Node B: {value: "Lee", version: [A:0, B:1]} │ │
│ │ │ │
│ │ 동시 쓰기 감지 → 병합 필요 │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 3. CRDT (Conflict-free Replicated Data Type) │
│ ┌─────────────────────────────────────────────┐ │
│ │ G-Counter: 각 노드별 카운터 유지 │ │
│ │ Node A: +3 │ │
│ │ Node B: +2 │ │
│ │ Total: 3 + 2 = 5 (자동 병합) │ │
│ └─────────────────────────────────────────────┘ │
│ │
│ 4. Custom Resolution Logic │
│ ┌─────────────────────────────────────────────┐ │
│ │ 예: 장바구니 충돌 │ │
│ │ Cart A: [Item1, Item2] │ │
│ │ Cart B: [Item1, Item3] │ │
│ │ Merge: [Item1, Item2, Item3] (Union) │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
복제 토폴로지¶
┌─────────────────────────────────────────────────────────────────┐
│ 복제 토폴로지 유형 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 원형 (Circular) │
│ ┌───┐ │
│ │ A │ ──────────┐ │
│ └─┬─┘ │ │
│ │ ┌─▼─┐ │
│ │ │ B │ │
│ │ └─┬─┘ │
│ ┌─▼─┐ │ │
│ │ D │ <─────────┘ │
│ └─┬─┘ │ │
│ └──> ┌───┐ <──┘ │
│ │ C │ │
│ └───┘ │
│ 장애 시 전체 복제 중단 위험 │
│ │
│ 2. 별형 (Star) │
│ ┌───┐ │
│ │ B │ │
│ └─┬─┘ │
│ ┌───┐ │ ┌───┐ │
│ │ A │─┼─│ C │ │
│ └───┘ │ └───┘ │
│ ┌─▼─┐ │
│ │Hub│ ← 중앙 노드 장애 시 취약 │
│ └───┘ │
│ │
│ 3. 전체 연결 (All-to-All) │
│ ┌───┐ ┌───┐ │
│ │ A │ ←──→│ B │ │
│ └─┬─┘ └─┬─┘ │
│ │ ╲ ╱ │ │
│ │ ╳ │ │
│ │ ╱ ╲ │ │
│ ┌─▼─┐ ┌─▼─┐ │
│ │ D │ ←──→│ C │ │
│ └───┘ └───┘ │
│ 높은 내결함성, 복잡한 관리 │
│ │
└─────────────────────────────────────────────────────────────────┘
4. 리더 없는 복제¶
Dynamo 스타일 복제¶
┌─────────────────────────────────────────────────────────────────┐
│ 리더 없는 복제 (Leaderless Replication) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 특징: │
│ - 모든 노드가 읽기/쓰기 가능 │
│ - 클라이언트가 여러 노드에 동시 요청 │
│ - Amazon Dynamo, Cassandra, Riak 등에서 사용 │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Client │ │
│ │ │ │ │
│ │ Write: key=X, value=V │ │
│ │ ┌──────────────┼──────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │
│ │ │Node 1│ │Node 2│ │Node 3│ │ │
│ │ │ X=V │ │ X=V │ │(Down)│ │ │
│ │ │ ✓ │ │ ✓ │ │ ✗ │ │ │
│ │ └──────┘ └──────┘ └──────┘ │ │
│ │ │ │
│ │ N=3 (총 복제본), W=2 (쓰기 성공 필요 수) │ │
│ │ 2개 노드 성공 → 쓰기 성공! │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
읽기 복구와 Anti-Entropy¶
┌─────────────────────────────────────────────────────────────────┐
│ 데이터 복구 메커니즘 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Read Repair (읽기 시 복구) │
│ │
│ Client │
│ │ Read key=X │
│ ├────────────────────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 1│ │Node 2│ │Node 3│ │
│ │X=V2 │ │X=V2 │ │X=V1 │ ← 오래된 버전 │
│ │ver:2 │ │ver:2 │ │ver:1 │ │
│ └──────┘ └──────┘ └──────┘ │
│ │ │ │ │
│ └──────────────┴─────────────────────┘ │
│ │ │
│ ▼ │
│ Client: 버전 비교 │
│ 최신 버전 V2 반환 │
│ Node 3에 V2 업데이트 요청 ←── Read Repair │
│ │
│ 2. Anti-Entropy Process (백그라운드 동기화) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Background Anti-Entropy Process │ │
│ │ │ │
│ │ Node 1 Node 2 Node 3 │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ A │ │ A │ │ A │ │ │
│ │ │ B │ <────> │ B │ <────> │ B │ │ │
│ │ │ C │ Merkle │ C │ Merkle │ C* │ ← 불일치 │ │
│ │ │ D │ Tree │ D │ Tree │ D │ │ │
│ │ └────┘ Compare └────┘ Compare └────┘ │ │
│ │ │ │
│ │ Merkle Tree로 효율적인 차이 감지 및 동기화 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Sloppy Quorum과 Hinted Handoff¶
┌─────────────────────────────────────────────────────────────────┐
│ Sloppy Quorum & Hinted Handoff │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Strict Quorum: 지정된 노드에서만 읽기/쓰기 │
│ │
│ Sloppy Quorum: 일부 노드 장애 시 다른 노드가 대신 │
│ │
│ 정상 상황: │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 1│ │Node 2│ │Node 3│ ← key X의 home nodes │
│ └──────┘ └──────┘ └──────┘ │
│ │
│ Node 3 장애 시: │
│ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 1│ │Node 2│ │(Down)│ │Node 4│ │
│ │ X=V │ │ X=V │ │ │ │ X=V* │ ← Hint 저장 │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ │
│ *Hinted Handoff: │
│ ┌──────────────────────────────────────────┐ │
│ │ Node 4 저장 데이터: │ │
│ │ { │ │
│ │ key: "X", │ │
│ │ value: "V", │ │
│ │ hint: "Node 3" ← 원래 저장될 노드 │ │
│ │ } │ │
│ │ │ │
│ │ Node 3 복구 시 → 데이터 전송 후 삭제 │ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
5. 동기/비동기 복제¶
동기 복제 (Synchronous Replication)¶
┌─────────────────────────────────────────────────────────────────┐
│ 동기 복제 (Synchronous) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client Leader Follower 1 Follower 2 │
│ │ │ │ │ │
│ │ Write X=V │ │ │ │
│ │──────────────>│ │ │ │
│ │ │ Replicate │ │ │
│ │ │─────────────────>│ │ │
│ │ │─────────────────────────────────>│ │
│ │ │ │ │ │
│ │ │ ACK │ │ │
│ │ │<─────────────────│ │ │
│ │ │<─────────────────────────────────│ │
│ │ │ │ │ │
│ │ ACK │ (모든 복제 완료 후) │ │
│ │<──────────────│ │ │ │
│ │ │ │ │ │
│ │
│ 장점: │
│ - 강한 일관성 보장 │
│ - 데이터 손실 없음 │
│ │
│ 단점: │
│ - 느린 쓰기 지연 (모든 복제본 대기) │
│ - 복제본 장애 시 쓰기 불가 │
│ │
└─────────────────────────────────────────────────────────────────┘
비동기 복제 (Asynchronous Replication)¶
┌─────────────────────────────────────────────────────────────────┐
│ 비동기 복제 (Asynchronous) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client Leader Follower 1 Follower 2 │
│ │ │ │ │ │
│ │ Write X=V │ │ │ │
│ │──────────────>│ │ │ │
│ │ │ │ │ │
│ │ ACK │ (즉시 응답) │ │ │
│ │<──────────────│ │ │ │
│ │ │ │ │ │
│ │ │ Replicate (백그라운드) │ │
│ │ │─────────────────>│ │ │
│ │ │─────────────────────────────────>│ │
│ │ │ │ │ │
│ │
│ 장점: │
│ - 빠른 쓰기 응답 │
│ - 복제본 장애가 쓰기에 영향 없음 │
│ │
│ 단점: │
│ - 리더 장애 시 데이터 손실 가능 │
│ - 읽기 일관성 문제 (stale read) │
│ │
└─────────────────────────────────────────────────────────────────┘
반동기 복제 (Semi-Synchronous)¶
┌─────────────────────────────────────────────────────────────────┐
│ 반동기 복제 (Semi-Synchronous) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 최소 1개의 복제본에는 동기로, 나머지는 비동기로 복제 │
│ │
│ Client Leader Sync Follower Async Followers │
│ │ │ │ │ │
│ │ Write X=V │ │ │ │
│ │──────────────>│ │ │ │
│ │ │ Replicate │ │ │
│ │ │───────────────>│ │ │
│ │ │ ACK │ │ │
│ │ │<───────────────│ │ │
│ │ ACK │ │ │ │
│ │<──────────────│ │ │ │
│ │ │ Replicate (async) │ │
│ │ │───────────────────────────────>│ │
│ │ │ │ │ │
│ │
│ MySQL Semi-Sync 설정: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ -- Master │ │
│ │ SET GLOBAL rpl_semi_sync_master_enabled = 1; │ │
│ │ SET GLOBAL rpl_semi_sync_master_timeout = 10000; -- 10s │ │
│ │ │ │
│ │ -- Slave │ │
│ │ SET GLOBAL rpl_semi_sync_slave_enabled = 1; │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
6. 복제 지연과 일관성 문제¶
복제 지연 (Replication Lag)¶
┌─────────────────────────────────────────────────────────────────┐
│ 복제 지연 시나리오 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 시간 ────────────────────────────────────────────────> │
│ │
│ T0 T1 T2 T3 T4 T5 │
│ │ │ │ │ │ │ │
│ Leader: Write │ │ │ │ │
│ X=1 │ │ │ │ │
│ │ │ │ │ │ │
│ │ ▼ │ │ │ │
│ Follower: X=1 │ │ │ │
│ (지연) │ │ │ │
│ │ │ │ │
│ Client A: Read from Leader │ │ │
│ X=1 ✓ │ │ │ │
│ │ │ │ │
│ Client B: Read from Follower│ │
│ X=??? ← T2 시점에서 │
│ Follower가 아직 복제 안됨 │
│ → Stale Read! │
│ │
│ 복제 지연 = T1(Leader Write) ~ T2(Follower Apply) 시간 │
│ │
└─────────────────────────────────────────────────────────────────┘
일관성 보장 수준¶
┌─────────────────────────────────────────────────────────────────┐
│ 일관성 보장 수준 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Read-Your-Writes (자신의 쓰기 읽기) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ User A: Write profile photo │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ User A: Read profile → 새 사진 보여야 함 │ │
│ │ │ │
│ │ 해결책: │ │
│ │ - 자신의 변경은 항상 Leader에서 읽기 │ │
│ │ - 또는 타임스탬프 기반 읽기 위치 지정 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. Monotonic Reads (단조 읽기) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ T1: Read from Follower A → X=2 │ │
│ │ T2: Read from Follower B → X=1 ← 문제! │ │
│ │ │ │
│ │ 시간이 뒤로 가는 것처럼 보임 │ │
│ │ │ │
│ │ 해결책: │ │
│ │ - 사용자별로 같은 복제본에서 읽기 │ │
│ │ - 버전 기반 읽기 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. Consistent Prefix Reads (인과 관계 유지) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 채팅: │ │
│ │ A: "서울 날씨 어때?" (T1) │ │
│ │ B: "맑아!" (T2) │ │
│ │ │ │
│ │ 복제 지연 시: │ │
│ │ C가 보는 화면: │ │
│ │ B: "맑아!" ← T2 먼저 도착 │ │
│ │ A: "서울 날씨 어때?" ← T1 나중에 도착 │ │
│ │ │ │
│ │ 해결책: │ │
│ │ - 같은 파티션에 인과 관계 데이터 저장 │ │
│ │ - 인과 관계 추적 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
복제 지연 모니터링¶
-- PostgreSQL 복제 지연 확인
SELECT
client_addr,
state,
pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS lag_bytes,
EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp())) AS lag_seconds
FROM pg_stat_replication;
-- MySQL 복제 지연 확인
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master 값 확인
-- 지연 기반 라우팅 로직 (애플리케이션)
/*
def get_connection(query_type, max_lag_seconds=5):
if query_type == 'write':
return master_connection
for replica in replicas:
if replica.lag_seconds < max_lag_seconds:
return replica.connection
# 모든 복제본 지연이 큰 경우 마스터에서 읽기
return master_connection
*/
7. 장애 복구와 리더 선출¶
Follower 장애 복구¶
┌─────────────────────────────────────────────────────────────────┐
│ Follower 장애 복구 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Catch-up Recovery │
│ │
│ 정상 상태: │
│ Leader: [1] [2] [3] [4] [5] [6] │
│ Follower: [1] [2] [3] │
│ ▲ │
│ └── 마지막 적용 위치 기억 │
│ │
│ Follower 복구 후: │
│ Leader: [1] [2] [3] [4] [5] [6] [7] [8] │
│ Follower: [1] [2] [3] → [4] [5] [6] [7] [8] │
│ ↑ │
│ 따라잡기 (catch-up) │
│ │
│ 2. 복구 과정 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 1. Follower 재시작 │ │
│ │ 2. 마지막 적용 LSN/binlog 위치 확인 │ │
│ │ 3. Leader에게 해당 위치 이후 로그 요청 │ │
│ │ 4. 로그 적용하며 따라잡기 │ │
│ │ 5. 실시간 복제 재개 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Leader 장애와 Failover¶
┌─────────────────────────────────────────────────────────────────┐
│ Leader 장애 복구 (Failover) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Failover 과정: │
│ │
│ 단계 1: 장애 감지 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ ┌────────┐ Heartbeat 실패 │ │
│ │ │ Leader │ ────X──── (3회 연속) │ │
│ │ │ (Down) │ │ │
│ │ └────────┘ Timeout: 30초 │ │
│ │ │ │
│ │ Follower들: "Leader 응답 없음!" → Failover 시작 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 단계 2: 새 Leader 선출 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Follower A: LSN = 1000 │ │
│ │ Follower B: LSN = 1050 ← 가장 최신 → 새 Leader │ │
│ │ Follower C: LSN = 980 │ │
│ │ │ │
│ │ 선출 기준: │ │
│ │ 1. 가장 최신 데이터를 가진 노드 │ │
│ │ 2. 노드 우선순위 │ │
│ │ 3. 합의 알고리즘 (Raft, Paxos) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ 단계 3: 클라이언트 재연결 │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Clients ───── (이전) Leader: 10.0.0.1 │ │
│ │ ╲ │ │
│ │ ╲─── (새) Leader: 10.0.0.2 │ │
│ │ │ │
│ │ 방법: │ │
│ │ - DNS 업데이트 │ │
│ │ - Virtual IP (VIP) 이동 │ │
│ │ - Proxy/Load Balancer 설정 변경 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Failover의 문제점¶
┌─────────────────────────────────────────────────────────────────┐
│ Failover 시 문제점 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 데이터 손실 (비동기 복제 시) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Old Leader: [1][2][3][4][5] │ │
│ │ ↑ 복제 안됨 │ │
│ │ New Leader: [1][2][3][4] │ │
│ │ │ │
│ │ Write [5]가 손실됨! │ │
│ │ │ │
│ │ Old Leader 복구 시: [5]는 어떻게? │ │
│ │ → 일반적으로 버림 (충돌 방지) │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 2. Split Brain (두 Leader 문제) │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 네트워크 분리 │ │
│ │ │ │
│ │ ┌────────┐ ╳ ┌────────┐ │ │
│ │ │Old │ │New │ │ │
│ │ │Leader │ 네트워크 단절 │Leader │ │ │
│ │ └────────┘ └────────┘ │ │
│ │ ▲ ▲ │ │
│ │ Clients A Clients B │ │
│ │ (쓰기 계속) (쓰기 계속) │ │
│ │ │ │
│ │ → 데이터 불일치 발생! │ │
│ │ │ │
│ │ 해결책: │ │
│ │ - Fencing (STONITH: Shoot The Other Node In Head)│ │
│ │ - Old Leader 강제 종료 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 3. Timeout 설정 딜레마 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Timeout 너무 짧음: │ │
│ │ - 네트워크 지연/부하 시 불필요한 Failover │ │
│ │ - 시스템 불안정 │ │
│ │ │ │
│ │ Timeout 너무 김: │ │
│ │ - 실제 장애 시 복구 지연 │ │
│ │ - 서비스 중단 시간 증가 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
리더 선출 알고리즘¶
┌─────────────────────────────────────────────────────────────────┐
│ Raft 리더 선출 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 상태: Follower, Candidate, Leader │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────┐ Election ┌───────────┐ │ │
│ │ │ │ Timeout │ │ │ │
│ │ │ Follower │ ─────────────────>│ Candidate │ │ │
│ │ │ │ │ │ │ │
│ │ └──────────┘ └─────┬─────┘ │ │
│ │ ▲ │ │ │
│ │ │ Discovers │ Receives │ │
│ │ │ current │ majority │ │
│ │ │ leader │ votes │ │
│ │ │ ▼ │ │
│ │ │ ┌──────────┐ │ │
│ │ └─────────────────────────│ Leader │ │ │
│ │ │ │ │ │
│ │ └──────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 선출 과정: │
│ 1. Follower가 heartbeat 못받으면 Candidate로 전환 │
│ 2. Term(임기) 번호 증가 │
│ 3. 자신에게 투표 + 다른 노드에게 RequestVote 요청 │
│ 4. 과반수 득표 시 Leader 선출 │
│ 5. Leader는 주기적으로 heartbeat 전송 │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 예시: 5노드 클러스터 │ │
│ │ │ │
│ │ Node A (Candidate, Term 5) │ │
│ │ │ │ │
│ │ ├── RequestVote ──> Node B: Vote YES │ │
│ │ ├── RequestVote ──> Node C: Vote YES │ │
│ │ ├── RequestVote ──> Node D: Vote NO (이미 투표) │ │
│ │ └── RequestVote ──> Node E: Vote YES │ │
│ │ │ │
│ │ 3/5 = 과반수 → Node A가 Leader! │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
8. Quorum과 일관성 수준¶
Quorum 개념¶
┌─────────────────────────────────────────────────────────────────┐
│ Quorum (정족수) 개념 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ N = 복제본 수 (Total Replicas) │
│ W = 쓰기 성공에 필요한 응답 수 (Write Quorum) │
│ R = 읽기 성공에 필요한 응답 수 (Read Quorum) │
│ │
│ 강한 일관성 보장 조건: R + W > N │
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ N=3, W=2, R=2 인 경우: │ │
│ │ │ │
│ │ 쓰기 시: │ │
│ │ [Node1: X=V] [Node2: X=V] [Node3: ?] │ │
│ │ ✓ ✓ (아직) │ │
│ │ │ │
│ │ 읽기 시: │ │
│ │ [Node1: X=V] [Node2: ?] [Node3: X=V] │ │
│ │ ✓ (skip) ✓ │ │
│ │ │ │
│ │ R(2) + W(2) = 4 > N(3) │ │
│ │ → 최소 1개 노드는 최신 쓰기를 가짐! │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ Write Set │ │ Read Set │
│ (W=2) │ ┌─────────┐ │ (R=2) │
│ ┌─────────┐ │ │ Node 1 │ │ ┌─────────┐ │
│ │ Node 1 │────┼───│ (겹침) │───┼────│ Node 1 │ │
│ │ Node 2 │ │ └─────────┘ │ │ Node 3 │ │
│ └─────────┘ │ (최소 1개) │ └─────────┘ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Quorum 설정별 특성¶
┌─────────────────────────────────────────────────────────────────┐
│ Quorum 설정 전략 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ N = 3 복제본 기준 │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 설정 1: W=1, R=3 (쓰기 우선) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 쓰기: 1개 노드만 성공하면 됨 → 빠른 쓰기 │ │ │
│ │ │ 읽기: 3개 모두 읽어야 함 → 느린 읽기 │ │ │
│ │ │ │ │ │
│ │ │ 사용 예: 로그 수집, IoT 데이터 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 설정 2: W=3, R=1 (읽기 우선) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 쓰기: 3개 모두 성공해야 함 → 느린 쓰기 │ │ │
│ │ │ 읽기: 1개만 읽으면 됨 → 빠른 읽기 │ │ │
│ │ │ │ │ │
│ │ │ 사용 예: 카탈로그, 참조 데이터 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 설정 3: W=2, R=2 (균형) │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 쓰기: 2개 노드 성공 필요 │ │ │
│ │ │ 읽기: 2개 노드에서 읽기 │ │ │
│ │ │ │ │ │
│ │ │ 일반적인 균형 잡힌 설정 │ │ │
│ │ │ 1개 노드 장애 허용 │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 내결함성: min(N-W, N-R) 개 노드 장애 허용 │
│ │
└─────────────────────────────────────────────────────────────────┘
Cassandra 일관성 수준¶
┌─────────────────────────────────────────────────────────────────┐
│ Cassandra Consistency Levels │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 쓰기 일관성 수준: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ANY : 1개 노드 (힌트 포함) → 최저 일관성 │ │
│ │ ONE : 1개 복제본 │ │
│ │ TWO : 2개 복제본 │ │
│ │ THREE : 3개 복제본 │ │
│ │ QUORUM : (RF/2)+1 복제본 │ │
│ │ LOCAL_QUORUM: 로컬 DC의 (RF/2)+1 │ │
│ │ EACH_QUORUM : 각 DC의 (RF/2)+1 │ │
│ │ ALL : 모든 복제본 → 최고 일관성 │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 예시: RF=3 클러스터 │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ QUORUM 계산: (3/2)+1 = 2 │ │
│ │ │ │
│ │ 쓰기 CL=QUORUM: 2개 노드 ACK 필요 │ │
│ │ 읽기 CL=QUORUM: 2개 노드에서 읽기 │ │
│ │ │ │
│ │ R(2) + W(2) = 4 > N(3) → 강한 일관성! │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ 코드 예시: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ -- CQL │ │
│ │ CONSISTENCY QUORUM; │ │
│ │ INSERT INTO users (id, name) VALUES (1, 'Kim'); │ │
│ │ │ │
│ │ -- 또는 쿼리별 설정 │ │
│ │ SELECT * FROM users WHERE id = 1 │ │
│ │ USING CONSISTENCY LOCAL_QUORUM; │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
일관성 vs 가용성 트레이드오프¶
┌─────────────────────────────────────────────────────────────────┐
│ 일관성 vs 가용성 트레이드오프 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 일관성 (Consistency) │
│ ▲ │
│ │ │
│ ALL ●──────────┤ │
│ │ │
│ QUORUM ●─────────┤ 균형점 │
│ │ (권장) │
│ TWO ●────────┤ │
│ │ │
│ ONE ●───────┤ │
│ │ │
│ ANY ●──────┼─────────────────────────────────> │
│ 가용성 (Availability) │
│ │
│ 상황별 권장 설정: │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 금융 거래: W=ALL, R=ALL (일관성 최우선) │ │
│ │ 소셜 피드: W=ONE, R=ONE (가용성 최우선) │ │
│ │ 전자상거래: W=QUORUM, R=QUORUM (균형) │ │
│ │ 분석 데이터: W=ONE, R=ONE (최종 일관성 허용) │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
9. 연습 문제¶
연습 문제 1: 복제 전략 선택¶
다음 시나리오에서 적절한 복제 전략을 선택하고 이유를 설명하세요.
시나리오:
1. 전 세계 사용자가 접속하는 소셜 미디어 서비스
2. 금융 거래를 처리하는 은행 시스템
3. 실시간 채팅 애플리케이션
4. 로그 수집 및 분석 시스템
선택지:
A. 단일 리더 + 동기 복제
B. 단일 리더 + 비동기 복제
C. 다중 리더 복제
D. 리더 없는 복제
예시 답안:
1. 소셜 미디어: C (다중 리더)
- 지리적으로 분산된 사용자
- 각 지역에서 낮은 지연 필요
- 약간의 일관성 지연 허용 가능
2. 은행 시스템: A (단일 리더 + 동기)
- 강한 일관성 필수
- 데이터 손실 불가
- 쓰기 지연 감수 가능
3. 실시간 채팅: B (단일 리더 + 비동기)
- 빠른 응답 필요
- 메시지 순서 중요
- 약간의 데이터 손실 허용 가능
4. 로그 수집: D (리더 없는)
- 높은 쓰기 처리량 필요
- 최종 일관성으로 충분
- 고가용성 중요
연습 문제 2: Quorum 계산¶
문제:
N=5 복제본 클러스터에서:
1. 강한 일관성을 보장하면서 최대 가용성을 위한 W, R 값은?
2. W=3, R=2 설정에서 몇 개 노드 장애까지 허용?
3. 쓰기 가용성을 최대화하면서 강한 일관성을 유지하려면?
예시 답안:
1. W=3, R=3 (또는 W=2, R=4 등)
- R + W > N (3+3=6 > 5) ✓
- 쓰기: 2개 노드 장애 허용
- 읽기: 2개 노드 장애 허용
2. W=3, R=2 분석:
- R + W = 5 = N (강한 일관성 아님!)
- 정확히 말하면 R + W > N 이어야 강한 일관성
- 쓰기 가용성: 5-3 = 2개 노드 장애 허용
- 읽기 가용성: 5-2 = 3개 노드 장애 허용
3. 쓰기 가용성 최대화:
- W=1, R=5 (쓰기: 4개 장애 허용)
- 단, 읽기는 모든 노드 필요 (가용성 낮음)
연습 문제 3: Failover 시나리오¶
문제:
비동기 복제를 사용하는 MySQL 마스터-슬레이브 구성에서
마스터 장애 발생 시:
1. 슬레이브 A: binlog position 1000
2. 슬레이브 B: binlog position 950
3. 마스터 마지막 binlog position: 1020
다음 질문에 답하세요:
1. 어떤 슬레이브를 새 마스터로 승격해야 하나요?
2. 최대 몇 개의 트랜잭션이 손실될 수 있나요?
3. 이전 마스터가 복구되면 어떻게 처리해야 하나요?
예시 답안:
1. 슬레이브 A를 새 마스터로 승격
- Position 1000 > 950
- 가장 최신 데이터 보유
2. 최대 손실 트랜잭션:
- 1020 - 1000 = 20개 트랜잭션 손실 가능
3. 이전 마스터 복구 시:
- 슬레이브로 재구성
- Position 1000 ~ 1020 데이터는 버림 (conflict 방지)
- 새 마스터로부터 복제 시작
연습 문제 4: 충돌 해결 설계¶
문제:
다중 리더 구성의 전자상거래 시스템에서
동일한 상품의 재고를 동시에 업데이트할 때:
Leader A (서울): inventory = 100 - 5 = 95
Leader B (도쿄): inventory = 100 - 3 = 97
이 충돌을 어떻게 해결할 수 있을까요?
여러 가지 방법을 제안하고 장단점을 설명하세요.
예시 답안:
방법 1: LWW (Last Write Wins)
- 타임스탬프가 더 늦은 쓰기 적용
- 장점: 구현 단순
- 단점: 데이터 손실 (5개 또는 3개 판매 누락)
방법 2: CRDT (Counter)
- 각 리더별 판매량 기록: A: -5, B: -3
- 최종 재고 = 100 - 5 - 3 = 92
- 장점: 데이터 손실 없음
- 단점: 재고가 음수가 될 수 있음
방법 3: 분산 잠금 (권장하지 않음)
- 쓰기 전 분산 락 획득
- 단점: 지연 증가, 복잡성
방법 4: 단일 리더로 라우팅
- 재고 관련 쓰기는 특정 리더로만
- 장점: 충돌 원천 방지
- 단점: 해당 리더 과부하 가능
권장: CRDT + 재고 임계치 알림
- 음수 재고 발생 시 알림/조정
연습 문제 5: 면접 문제¶
면접관: "Read-Your-Writes 일관성을 구현하려면 어떻게 해야 하나요?"
요구사항:
- 사용자가 데이터를 쓴 후 자신의 쓰기를 항상 읽을 수 있어야 함
- 비동기 복제 환경
- 여러 복제본이 있는 상황
설계 방안을 제시하세요.
예시 답안:
방법 1: 리더에서 읽기
- 사용자가 최근 쓰기한 데이터는 리더에서 읽기
- 구현: 쓰기 후 일정 시간(예: 1분) 동안 리더 사용
방법 2: 타임스탬프 기반
- 클라이언트가 마지막 쓰기 타임스탬프 기억
- 읽기 요청 시 해당 타임스탬프 전달
- 복제본이 해당 시점까지 적용될 때까지 대기
방법 3: 복제 위치 기반
- 쓰기 후 LSN(Log Sequence Number) 반환
- 읽기 요청 시 해당 LSN 이상 적용된 복제본에서 읽기
구현 예시 (방법 2):
```python
class Client:
def __init__(self):
self.last_write_timestamp = 0
def write(self, data):
result = leader.write(data)
self.last_write_timestamp = result.timestamp
def read(self, key):
return db.read(
key,
min_timestamp=self.last_write_timestamp
)
10. 다음 단계¶
이 문서에서 학습한 내용을 바탕으로 다음 주제를 학습하세요:
- 분산 트랜잭션 - 2PC, Saga 패턴
- 합의 알고리즘 - Paxos, Raft, ZAB
- CDC (Change Data Capture) - Debezium
- 이벤트 소싱과 CQRS - 복제의 대안적 접근
학습 순서:
[현재] 데이터베이스 복제
│
├──> 분산 트랜잭션 (2PC, Saga)
│
├──> 합의 알고리즘 (Raft, Paxos)
│
└──> CDC & Event Sourcing
11. 참고 자료¶
필수 문서¶
- Designing Data-Intensive Applications - Martin Kleppmann
- Chapter 5: Replication
-
Chapter 9: Consistency and Consensus
-
Database Internals - Alex Petrov
- Part II: Distributed Systems
데이터베이스별 문서¶
- PostgreSQL Streaming Replication
-
https://www.postgresql.org/docs/current/warm-standby.html
-
MySQL Replication
-
https://dev.mysql.com/doc/refman/8.0/en/replication.html
-
Cassandra Documentation
- https://cassandra.apache.org/doc/latest/cassandra/architecture/dynamo.html
논문¶
- Dynamo: Amazon's Highly Available Key-value Store (2007)
- In Search of an Understandable Consensus Algorithm (Raft) (2014)
- Chain Replication for Supporting High Throughput and Availability (2004)
요약¶
┌─────────────────────────────────────────────────────────────────┐
│ 핵심 개념 요약 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 복제 유형: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 단일 리더: 쓰기는 리더만, 읽기는 팔로워도 가능 │ │
│ │ 다중 리더: 여러 노드에서 쓰기, 충돌 해결 필요 │ │
│ │ 리더 없음: 모든 노드 동등, Quorum으로 일관성 보장 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 동기/비동기: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 동기: 강한 일관성, 느린 쓰기, 장애 시 쓰기 불가 │ │
│ │ 비동기: 빠른 쓰기, 데이터 손실 가능성 │ │
│ │ 반동기: 최소 1개 동기 + 나머지 비동기 (균형) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Quorum: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ R + W > N 일 때 강한 일관성 보장 │ │
│ │ 내결함성 = min(N-W, N-R) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ 면접 핵심: │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 1. 복제 유형별 트레이드오프 설명 │ │
│ │ 2. 장애 복구(Failover) 과정과 문제점 │ │
│ │ 3. Quorum 계산과 일관성 수준 │ │
│ │ 4. Split Brain 방지 전략 │ │
│ │ 5. Read-Your-Writes 구현 방법 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘