대규모 서비스 데이터베이스 분리는 백엔드 시스템 설계 면접과 실무 모두에서 가장 핵심적인 주제 중 하나입니다. 사용자 100명짜리 서비스와 사용자 1억 명짜리 서비스는 같은 DB 구조로 운영할 수 없습니다. 카카오톡 메시지 DB, 네이버 검색 인덱스, 쿠팡 주문 데이터가 하나의 서버에 들어있다고 상상해보세요. 단 하나의 DB 서버가 다운되는 순간 전체 서비스가 멈춥니다. 이 글에서는 단일 DB의 한계부터 수직·수평 분할, 샤딩, 레플리케이션, MSA의 DB 분리 전략까지 아키텍처 다이어그램과 실전 코드로 완전히 정리합니다.
목차
- 단일 데이터베이스의 한계 – 언제 분리를 고민해야 하는가
- 수직 분할(Vertical Partitioning) – 테이블과 서비스 단위 분리
- 수평 분할과 샤딩(Sharding) – 데이터를 여러 서버에 나누기
- 레플리케이션(Replication) – 읽기 성능과 고가용성 확보
- CQRS와 MSA 환경의 DB 분리 – 마이크로서비스 시대의 전략
- DB 분리의 트레이드오프와 실전 의사결정 가이드
1. 단일 데이터베이스의 한계 – 언제 분리를 고민해야 하는가
모든 대규모 서비스는 처음에는 단일 DB로 시작합니다. 스타트업의 MVP, 사이드 프로젝트, 초기 서비스 모두 단일 DB로 충분합니다. 문제는 서비스가 성장하면서 발생합니다.
단일 DB 아키텍처의 구조
[단일 DB 구조 – 초기 서비스]
클라이언트 (Web / App)
↓
애플리케이션 서버
↓
┌─────────────┐
│ 단일 DB │ ← 모든 데이터가 한 곳에
│ (MySQL / │ users, orders, products,
│ PostgreSQL│ payments, logs 모두 여기
│ 등) │
└─────────────┘
장점: 단순한 구조, 트랜잭션 관리 용이, 운영 비용 낮음
단점: 트래픽 급증 시 단일 장애 지점(SPOF), 확장성 한계
단일 DB 한계 증상 체크리스트
⚠️ 아래 증상이 보이면 DB 분리를 고민해야 합니다
□ 쿼리 응답 시간이 평균 500ms를 초과하기 시작
□ 피크 타임마다 DB CPU 사용률이 80% 이상
□ 하나의 쿼리가 다른 서비스의 응답을 지연시킴
(예: 배치 리포트 쿼리가 주문 처리를 느리게 만듦)
□ 테이블 수가 200개를 넘어 스키마 변경이 두려워짐
□ 장애 발생 시 전체 서비스가 동시에 다운됨
□ 특정 팀의 스키마 변경이 다른 팀 서비스에 영향을 줌
DB 분리 전략의 전체 지도
[DB 분리 전략 전체 로드맵]
① 수직 분할 (Vertical Partitioning)
"어떤 테이블/컬럼을 분리할 것인가?"
└── 기능별 DB 분리, 컬럼 분리
② 수평 분할 / 샤딩 (Horizontal Sharding)
"같은 테이블의 데이터를 여러 서버에 분산"
└── Range, Hash, Directory 샤딩
③ 레플리케이션 (Replication)
"동일 데이터를 여러 서버에 복제"
└── Primary-Replica 구조, 읽기 부하 분산
④ CQRS + 이벤트 소싱
"읽기/쓰기 모델 분리"
└── Command DB / Query DB 완전 분리
⑤ MSA DB 분리
"서비스마다 독립 DB 소유"
└── Saga 패턴, 분산 트랜잭션
2. 수직 분할(Vertical Partitioning) – 테이블과 서비스 단위 분리
수직 분할은 하나의 거대한 DB를 기능·도메인·컬럼 단위로 여러 DB로 나누는 전략입니다. 가장 먼저 시도하는 분리 방식이며, 효과 대비 구현 복잡도가 낮아 현실적인 첫 번째 선택입니다.
기능별 DB 분리 (Functional Partitioning)
[수직 분할 전후 비교]
분리 전:
단일 DB
├── users (사용자)
├── orders (주문)
├── products (상품)
├── payments (결제)
├── notifications (알림)
└── logs (로그)
분리 후:
User DB → users, user_profiles, user_sessions
Commerce DB → orders, products, inventory, carts
Payment DB → payments, refunds, billing_info
Notification → notifications, push_tokens, email_queue
Analytics DB → logs, events, metrics (별도 분석용 DB)
컬럼 단위 수직 분할
한 테이블 내에서도 자주 접근하는 컬럼과 거의 접근하지 않는 컬럼을 분리할 수 있습니다. 사용자 프로필에서 기본 정보와 상세 정보를 나누는 것이 대표적입니다.
sql
-- 분리 전: 모든 컬럼이 한 테이블
CREATE TABLE users (
id BIGINT PRIMARY KEY,
email VARCHAR(255), -- 자주 조회
username VARCHAR(100), -- 자주 조회
-- 아래는 상세 프로필 페이지에서만 조회
bio TEXT,
avatar_url VARCHAR(500),
address TEXT,
birth_date DATE,
preferences JSON -- 수백 바이트~수 KB
);
-- 분리 후: 핫(Hot) 데이터와 콜드(Cold) 데이터 분리
CREATE TABLE users ( -- 핫 테이블: 빠른 조회
id BIGINT PRIMARY KEY,
email VARCHAR(255),
username VARCHAR(100),
created_at TIMESTAMP
);
CREATE TABLE user_profiles ( -- 콜드 테이블: 필요할 때만 JOIN
user_id BIGINT PRIMARY KEY, -- FK → users.id
bio TEXT,
avatar_url VARCHAR(500),
address TEXT,
birth_date DATE,
preferences JSON,
FOREIGN KEY (user_id) REFERENCES users(id)
);
수직 분할의 장단점과 실전 주의사항
python
# 수직 분할 후 조인이 필요한 경우 – 애플리케이션 레벨 처리
# (서비스 간 DB가 달라 SQL JOIN 불가 → 코드로 조합)
class UserService:
def __init__(self, user_db, profile_db):
self.user_db = user_db # User DB 커넥션
self.profile_db = profile_db # Profile DB 커넥션
def get_full_user_info(self, user_id: int) -> dict:
"""
기존: SELECT u.*, p.* FROM users u JOIN user_profiles p ...
분리 후: 두 DB를 별도 호출 후 애플리케이션에서 합산
"""
# DB 1에서 기본 정보 조회
user = self.user_db.query(
"SELECT id, email, username FROM users WHERE id = %s",
(user_id,)
)
# DB 2에서 상세 프로필 조회 (필요할 때만)
profile = self.profile_db.query(
"SELECT bio, avatar_url, address FROM user_profiles WHERE user_id = %s",
(user_id,)
)
# 애플리케이션 레벨에서 병합
return {**user, **profile} if user else None
# ⚠️ 주의: 두 DB 간 트랜잭션 보장이 어려움
# → 결제·주문처럼 원자성이 중요한 데이터는 신중하게 분리할 것
3. 수평 분할과 샤딩(Sharding) – 데이터를 여러 서버에 나누기
수직 분할이 “어떤 테이블을 분리할까”라면, 샤딩은 “같은 테이블의 행(Row)을 여러 DB 서버에 분산” 하는 전략입니다. 수억 개의 행을 가진 테이블을 단일 서버로는 처리할 수 없을 때 사용합니다.
샤딩의 핵심 개념
[샤딩 구조 시각화]
users 테이블 (총 1억 명)
↓ 샤딩 키(shard key) = user_id
↓
┌────────────────────────────────────┐
│ 샤드 라우터 │
│ (어느 샤드로 보낼지 결정) │
└────┬──────────┬──────────┬─────────┘
↓ ↓ ↓
Shard 0 Shard 1 Shard 2
(id 0~33M) (id 33M~66M)(id 66M~1억)
┌───────┐ ┌───────┐ ┌───────┐
│ DB 0 │ │ DB 1 │ │ DB 2 │
└───────┘ └───────┘ └───────┘
각 샤드는 독립적인 DB 서버 → 병렬 처리 가능
3가지 샤딩 전략
① 범위 기반 샤딩 (Range-based Sharding)
python
class RangeShardRouter:
"""
범위 기반 샤딩 라우터
user_id 범위에 따라 샤드 결정
"""
def __init__(self):
# (시작값, 끝값, 샤드_인덱스)
self.shard_ranges = [
(0, 33_333_333, 0), # Shard 0
(33_333_334, 66_666_666, 1), # Shard 1
(66_666_667, 99_999_999, 2), # Shard 2
]
self.shards = {
0: DatabaseConnection("shard-0.db.internal"),
1: DatabaseConnection("shard-1.db.internal"),
2: DatabaseConnection("shard-2.db.internal"),
}
def get_shard(self, user_id: int) -> DatabaseConnection:
for start, end, shard_idx in self.shard_ranges:
if start <= user_id <= end:
return self.shards[shard_idx]
raise ValueError(f"user_id {user_id}에 해당하는 샤드 없음")
# 장점: 범위 쿼리(WHERE id BETWEEN x AND y) 효율적
# 단점: 핫스팟 발생 위험 (신규 가입자가 항상 Shard 2에 집중)
② 해시 기반 샤딩 (Hash-based Sharding)
python
import hashlib
class HashShardRouter:
"""
해시 기반 샤딩 라우터
user_id의 해시값으로 샤드 균등 분배
"""
def __init__(self, shard_count: int = 4):
self.shard_count = shard_count
self.shards = {
i: DatabaseConnection(f"shard-{i}.db.internal")
for i in range(shard_count)
}
def get_shard_index(self, shard_key: str | int) -> int:
"""
MD5 해시로 균등 분배 (일관된 결과 보장)
"""
key_bytes = str(shard_key).encode('utf-8')
hash_value = int(hashlib.md5(key_bytes).hexdigest(), 16)
return hash_value % self.shard_count
def get_shard(self, user_id: int) -> DatabaseConnection:
shard_idx = self.get_shard_index(user_id)
return self.shards[shard_idx]
def get_all_shards(self) -> list:
"""
전체 샤드 대상 쿼리(집계 등) 시 모든 샤드 반환
"""
return list(self.shards.values())
# 장점: 데이터 균등 분배, 핫스팟 방지
# 단점: 범위 쿼리 불가, 샤드 수 변경 시 리샤딩 필요
③ 디렉터리 기반 샤딩 (Directory-based Sharding)
python
class DirectoryShardRouter:
"""
디렉터리 기반 샤딩 라우터
별도 매핑 테이블로 유연한 샤드 할당
"""
def __init__(self, lookup_db: DatabaseConnection):
self.lookup_db = lookup_db # 샤드 매핑 정보 저장 DB
self._cache = {} # 로컬 캐시로 조회 오버헤드 감소
def get_shard(self, user_id: int) -> DatabaseConnection:
# 캐시 확인
if user_id in self._cache:
return self._cache[user_id]
# 룩업 DB에서 샤드 정보 조회
result = self.lookup_db.query(
"SELECT shard_endpoint FROM shard_map WHERE user_id = %s",
(user_id,)
)
shard_conn = DatabaseConnection(result['shard_endpoint'])
self._cache[user_id] = shard_conn # 캐싱
return shard_conn
# 장점: 유연한 샤드 할당, 핫 유저 샤드 이동 가능
# 단점: 룩업 DB가 단일 장애 지점, 추가 조회 오버헤드
샤딩의 최대 난제 – 크로스 샤드 쿼리
python
class CrossShardQueryHandler:
"""
여러 샤드에 걸친 집계 쿼리 처리
예: "전체 사용자 수", "연령대별 통계"
"""
def __init__(self, shard_router: HashShardRouter):
self.router = shard_router
def count_all_users(self) -> int:
"""
모든 샤드에 병렬로 COUNT 쿼리 실행 후 합산
"""
import concurrent.futures
def count_on_shard(shard: DatabaseConnection) -> int:
result = shard.query("SELECT COUNT(*) as cnt FROM users")
return result['cnt']
all_shards = self.router.get_all_shards()
# 병렬 실행으로 지연 최소화
with concurrent.futures.ThreadPoolExecutor() as executor:
counts = list(executor.map(count_on_shard, all_shards))
return sum(counts)
# ⚠️ 크로스 샤드 JOIN은 사실상 불가능
# → 비정규화(Denormalization) 또는 별도 집계 DB 사용 권장
4. 레플리케이션(Replication) – 읽기 성능과 고가용성 확보
샤딩이 “데이터를 나눈다”면, 레플리케이션은 “동일한 데이터를 여러 서버에 복제” 합니다. 대부분의 서비스는 읽기(Read) 요청이 쓰기(Write) 요청보다 훨씬 많습니다. 레플리케이션은 이 비대칭 트래픽을 해결하는 핵심 전략입니다.
Primary-Replica 구조
[레플리케이션 아키텍처]
쓰기 요청 (INSERT/UPDATE/DELETE)
↓
┌─────────────────┐
│ Primary DB │ ← 모든 쓰기는 여기로
│ (Master) │
└────────┬────────┘
│ 복제 (Replication Log 전송)
┌────────┼────────┐
↓ ↓ ↓
┌───────┐ ┌───────┐ ┌───────┐
│Replica│ │Replica│ │Replica│ ← 읽기 요청 분산 처리
│ 1 │ │ 2 │ │ 3 │
└───────┘ └───────┘ └───────┘
↑ ↑ ↑
읽기 요청 (SELECT) 로드밸런싱
애플리케이션에서 읽기/쓰기 분리 구현
python
import random
from contextlib import contextmanager
from typing import List
class DatabaseRouter:
"""
Primary-Replica 라우팅 구현
쓰기는 Primary, 읽기는 Replica로 자동 분배
"""
def __init__(
self,
primary_dsn: str,
replica_dsns: List[str]
):
self.primary = DatabaseConnection(primary_dsn)
self.replicas = [
DatabaseConnection(dsn) for dsn in replica_dsns
]
def get_primary(self) -> DatabaseConnection:
"""INSERT / UPDATE / DELETE → Primary"""
return self.primary
def get_replica(self) -> DatabaseConnection:
"""
SELECT → Replica 중 랜덤 선택 (라운드로빈도 가능)
헬스체크 실패한 레플리카는 자동 제외
"""
healthy_replicas = [
r for r in self.replicas if r.is_healthy()
]
if not healthy_replicas:
# 모든 레플리카 다운 시 Primary로 폴백
return self.primary
return random.choice(healthy_replicas)
@contextmanager
def write_connection(self):
"""쓰기 전용 컨텍스트 매니저"""
conn = self.get_primary()
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
@contextmanager
def read_connection(self):
"""읽기 전용 컨텍스트 매니저"""
conn = self.get_replica()
try:
yield conn
finally:
conn.close()
class UserRepository:
def __init__(self, router: DatabaseRouter):
self.router = router
def create_user(self, email: str, username: str) -> int:
"""쓰기: Primary로 라우팅"""
with self.router.write_connection() as db:
return db.execute(
"INSERT INTO users (email, username) VALUES (%s, %s)",
(email, username)
)
def get_user_by_id(self, user_id: int) -> dict:
"""읽기: Replica로 라우팅"""
with self.router.read_connection() as db:
return db.query(
"SELECT * FROM users WHERE id = %s",
(user_id,)
)
복제 지연(Replication Lag) 문제와 해결책
[복제 지연 문제 시나리오]
Time 0: Primary에 user_id=999 생성 (쓰기 완료)
Time 1: 복제 지연으로 Replica에는 아직 미반영 (약 10~100ms)
Time 2: 클라이언트가 방금 만든 user_id=999 조회 요청
Time 3: Replica에서 조회 → "사용자 없음" 반환 ← 버그!
해결책:
① 쓰기 후 즉시 읽기는 Primary에서 처리 (Read-Your-Writes)
방금 INSERT한 데이터를 바로 조회할 때는 Replica 대신 Primary 사용
② 세션 일관성 (Session Consistency)
특정 사용자의 쓰기 이후 동일 세션의 읽기는
같은 Replica 또는 Primary에서 처리
③ 동기 복제 (Synchronous Replication)
Primary가 최소 1개 Replica 복제 완료 확인 후 커밋
→ 지연 없지만 쓰기 성능 저하
5. CQRS와 MSA 환경의 DB 분리 – 마이크로서비스 시대의 전략
샤딩과 레플리케이션이 단일 서비스 내 DB 확장이라면, CQRS와 MSA는 서비스 아키텍처 차원의 DB 분리 전략입니다.
CQRS (Command Query Responsibility Segregation)
CQRS는 쓰기 모델(Command)과 읽기 모델(Query)의 데이터 저장소를 완전히 분리하는 패턴입니다.
[CQRS 아키텍처]
클라이언트
├── 쓰기 요청 (주문 생성, 결제 등)
│ ↓
│ Command Handler
│ ↓
│ Command DB (PostgreSQL) ← 정규화된 쓰기 최적화 DB
│ ↓
│ 이벤트 발행 (Kafka / RabbitMQ)
│ ↓
│ Event Handler (비동기)
│ ↓
│ Query DB 업데이트 (Elasticsearch / Redis / MongoDB)
│
└── 읽기 요청 (주문 목록 조회, 검색 등)
↓
Query Handler
↓
Query DB ← 조회 최적화 (비정규화, 캐싱 친화적)
python
# CQRS 패턴 구현 예시
# ─── Command 측 (쓰기) ────────────────────────────────────
class CreateOrderCommand:
"""주문 생성 커맨드 데이터 클래스"""
def __init__(self, user_id: int, items: list, total: float):
self.user_id = user_id
self.items = items
self.total = total
class OrderCommandHandler:
def __init__(self, command_db, event_bus):
self.db = command_db # PostgreSQL: 정규화 쓰기 DB
self.event_bus = event_bus # Kafka 이벤트 버스
def handle_create_order(self, cmd: CreateOrderCommand) -> int:
# 1. 정규화된 Command DB에 쓰기
order_id = self.db.execute("""
INSERT INTO orders (user_id, total_amount, status)
VALUES (%s, %s, 'PENDING') RETURNING id
""", (cmd.user_id, cmd.total))
for item in cmd.items:
self.db.execute("""
INSERT INTO order_items (order_id, product_id, quantity)
VALUES (%s, %s, %s)
""", (order_id, item['product_id'], item['quantity']))
# 2. 이벤트 발행 → Query DB 동기화 트리거
self.event_bus.publish('order.created', {
'order_id': order_id,
'user_id': cmd.user_id,
'items': cmd.items,
'total': cmd.total
})
return order_id
# ─── Query 측 (읽기) ─────────────────────────────────────
class OrderQueryHandler:
def __init__(self, query_db):
self.db = query_db # Elasticsearch or MongoDB: 조회 최적화
def get_user_orders(self, user_id: int) -> list:
"""
비정규화된 Query DB에서 단일 쿼리로 모든 정보 반환
JOIN 없이 이미 조합된 데이터 구조 저장
"""
return self.db.search({
"query": {"term": {"user_id": user_id}},
"sort": [{"created_at": "desc"}]
})
# ─── 이벤트 핸들러 (Command → Query DB 동기화) ────────────
class OrderEventHandler:
def __init__(self, query_db):
self.query_db = query_db
def on_order_created(self, event: dict):
"""
주문 생성 이벤트 수신 시 Query DB에 비정규화 문서 생성
이후 조회는 여기서 처리 (JOIN 없이 빠르게)
"""
self.query_db.index('orders', {
'order_id': event['order_id'],
'user_id': event['user_id'],
'item_count': len(event['items']),
'total': event['total'],
'status': 'PENDING',
'created_at': 'now'
})
MSA(마이크로서비스)의 DB 분리 원칙
MSA에서는 각 서비스가 독자적인 DB를 소유합니다. 다른 서비스의 DB에 직접 접근하는 것은 엄격히 금지됩니다.
[MSA DB 분리 구조]
❌ 잘못된 MSA DB 설계:
Order Service ──┐
User Service ──┼──→ 공유 DB (강결합 발생)
Payment Service─┘
✅ 올바른 MSA DB 설계:
Order Service → Order DB (PostgreSQL)
User Service → User DB (MySQL)
Product Service → Product DB (MongoDB)
Payment Service → Payment DB (PostgreSQL)
Search Service → Search DB (Elasticsearch)
서비스 간 데이터 공유 방법:
→ API 호출 (동기)
→ 이벤트 메시지 (비동기, Kafka/RabbitMQ)
→ 공유 캐시 (Redis)
분산 트랜잭션 – Saga 패턴
MSA에서 여러 서비스에 걸친 트랜잭션은 전통적 ACID 트랜잭션으로 처리할 수 없습니다. Saga 패턴은 각 서비스의 로컬 트랜잭션을 이벤트로 연결합니다.
[Saga 패턴 – 주문 처리 흐름]
Step 1: Order Service → 주문 생성 (PENDING)
↓ 성공 이벤트 발행
Step 2: Inventory Service → 재고 차감
↓ 성공 이벤트 발행
Step 3: Payment Service → 결제 처리
↓
성공 → Order Service: 주문 CONFIRMED
실패 → 보상 트랜잭션(Compensating Transaction) 실행
← Inventory Service: 재고 복원
← Order Service: 주문 CANCELLED
핵심: 각 단계가 실패하면 이전 단계들을 '되돌리는' 보상 이벤트 발행
→ 분산 환경에서 최종적 일관성(Eventual Consistency) 보장
6. DB 분리의 트레이드오프와 실전 의사결정 가이드
DB를 분리할수록 성능·확장성은 올라가지만, 복잡도·일관성 관리·운영 비용도 함께 증가합니다. 올바른 선택은 기술 수준이 아니라 서비스의 규모와 요구사항에 달려 있습니다.
CAP 정리와 DB 분리의 관계
[CAP 정리 (분산 DB 설계의 핵심 원칙)]
C (일관성)
Consistency
/\
/ \
/ \
/ CA \ ← 단일 DB (일관성 + 가용성, 분산 없음)
/──────────\
/ CP │ AP \
/ │ \
P ────────────────── A
(분할 허용) (가용성)
Partition Availability
Tolerance
CP: 일관성 + 분산 (HBase, Zookeeper) → 분산 환경에서 일관성 우선
AP: 가용성 + 분산 (Cassandra, DynamoDB) → 가용성 우선, 최종 일관성
분산 시스템에서는 P(네트워크 분할)가 필수 → C와 A 중 선택해야 함
분리 전략별 트레이드오프 비교
| 전략 | 해결하는 문제 | 발생하는 복잡도 | 도입 시점 |
|---|---|---|---|
| 수직 분할 | 스키마 복잡도, 팀 경계 | 크로스 DB 조인 불가 | 도메인이 명확히 나뉠 때 |
| 레플리케이션 | 읽기 트래픽 과부하 | 복제 지연, 정합성 이슈 | 읽기:쓰기 = 5:1 이상 |
| 샤딩 | 쓰기/저장 용량 한계 | 크로스 샤드 쿼리, 리샤딩 | 단일 DB 수백 GB 초과 |
| CQRS | 읽기/쓰기 모델 불일치 | 이벤트 동기화, 최종 일관성 | 복잡한 조회 요구사항 |
| MSA DB | 서비스 간 강결합 | 분산 트랜잭션, Saga | 팀·서비스 독립 배포 필요 시 |
실전 의사결정 플로차트
[DB 분리 의사결정 가이드]
현재 DB 성능 문제가 있는가?
│
├── NO → 분리 불필요 (단순함 유지가 최선)
│
└── YES
↓
읽기 부하가 주된 문제인가?
├── YES → 레플리케이션 (Read Replica) 먼저 적용
│ 효과 부족 시 → Redis 캐시 레이어 추가
│
└── NO (쓰기/저장 용량 문제)
↓
단일 테이블 데이터가 수억 건 이상인가?
├── YES → 샤딩 검토
│ 샤딩 키 설계가 가장 중요
│
└── NO
↓
도메인이 명확히 나뉘는가?
├── YES → 수직 분할 (기능별 DB 분리)
└── NO → 인덱스 최적화·쿼리 튜닝 우선
단계적 DB 분리 로드맵 – 실제 서비스 성장 경로
[단계별 DB 진화 경로]
Phase 1 (MAU ~10만)
단일 RDB + 인덱스 최적화
→ 이 단계에서 샤딩 도입은 오버엔지니어링
Phase 2 (MAU ~100만)
단일 Primary + Read Replica 2~3개
+ Redis 캐시 레이어 추가
→ 읽기 부하 80~90% 감소
Phase 3 (MAU ~1000만)
기능별 수직 분할 (User DB / Order DB / Log DB)
+ 각 DB에 Replica 구성
+ 분석용 별도 DW(Data Warehouse) 분리
Phase 4 (MAU ~1억 이상)
핵심 테이블 샤딩 (users, orders 등)
+ CQRS 도입 (복잡한 조회 서비스)
+ 완전한 MSA DB 분리
+ 글로벌 멀티 리전 레플리케이션
결론
대규모 서비스 데이터베이스 분리는 단일 정답이 없는 아키텍처 의사결정입니다. 수직 분할로 도메인을 나누고, 레플리케이션으로 읽기 부하를 분산하고, 샤딩으로 쓰기·저장 한계를 돌파하고, CQRS·MSA로 서비스 독립성을 확보하는 것이 단계적 진화 경로입니다. 가장 중요한 원칙은 현재 규모에 맞는 복잡도를 선택하는 것입니다. 섣부른 샤딩 도입은 불필요한 복잡도를 만들고, 너무 늦은 분리는 장애와 성능 위기를 불러옵니다. 오늘 배운 전략을 기반으로 현재 서비스의 병목 지점을 진단하고 적절한 분리 전략을 선택해보세요.
답글 남기기