이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Scale up, scale out, partitioning, sharding은 각각 어떤 운영 비용을 새로 만드는가?
- 확장 후 배포, 관측, 백업, 마이그레이션, 장애 대응은 어떻게 달라지는가?
- 성능 개선보다 운영 복잡도가 더 큰 경우를 어떻게 판단하는가?
개요
확장은 capacity를 올리는 선택이지만 운영 비용도 같이 올린다. 서버가 1대에서 20대가 되면 배포와 관측의 단위가 바뀐다. DB가 1개에서 4개 shard가 되면 backup, migration, incident response의 단위도 바뀐다. 성능만 보고 확장하면 다음 장애 때 문제를 찾는 비용이 커진다. 좋은 설계 문서는 “얼마나 빨라지는가”와 함께 “무엇을 더 운영해야 하는가”를 말해야 한다.
이 문서가 바꾸는 설계 축
| 축 | 바뀌는 내용 |
|---|---|
| 성능 | 더 많은 처리량과 저장 용량을 얻는다. |
| 정합성 | replica lag, shard별 migration, dual write 검증 같은 새 경계가 생긴다. |
| 장애 격리 | 장애 범위를 나눌 수 있지만 routing과 공통 dependency 장애가 새로 중요해진다. |
| 운영 복잡도 | 배포, 모니터링, 비용, 백업, 복구, 테스트 조합이 늘어난다. |
대표 시나리오
초기 서비스는 app 2대와 DB 1대로 운영됐다. 성장 후에는 app 40개 Pod, Redis cluster, read replica 3개, DB shard 4개가 됐다. 처리량은 늘었지만 운영 질문도 바뀐다.
이전: API가 느리다 -> app log와 DB slow query를 본다.
이후: API가 느리다 -> 어느 Pod, 어느 shard, 어느 replica, 어느 routing version인지 본다.확장 전에는 장애 원인이 하나의 큰 병목으로 보인다. 확장 후에는 작은 병목 여러 개와 관측 누락이 함께 나타난다.
확장 선택별 운영 비용
| 선택 | 얻는 것 | 새 운영 비용 |
|---|---|---|
| scale up | 빠른 완화, 낮은 구조 변경 | 비용 증가, 큰 노드 장애 영향, 축소 판단 |
| app scale out | app 처리량과 instance 장애 흡수 | 배포 전략, HPA, pool 총합, log volume |
| read replica | read 부하 분산 | replica lag, read-your-write UX, query routing |
| partitioning | 큰 table 관리와 retention | partition key 관리, query plan 검증 |
| sharding | write/storage 분산, 장애 범위 축소 | routing, migration, backup, rebalancing, cross-shard query |
| 이 표는 도입 반대 근거가 아니다. | ||
| 도입 후 운영할 항목을 설계에 포함하라는 뜻이다. |
비용 계산
확장 전후의 숫자를 대략이라도 계산해야 한다.
app_pods = 40
db_pool_per_pod = 10
max_db_connections_from_app = 400
shards = 4
replicas_per_shard = 2
db_nodes = 4 * (1 primary + 2 replicas) = 12
metrics_per_pod = 300
time_series_for_pods = 40 * 300 = 12,000Pod와 shard가 늘면 infrastructure cost만 늘지 않는다. Metric cardinality, log volume, alert rule, test matrix, on-call 판단 비용도 늘어난다.
배포 비용
App scale out은 rolling deploy를 가능하게 하지만 설정이 없으면 배포 중 장애를 만든다.
| 항목 | 확인할 질문 |
|---|---|
| readiness | 준비되지 않은 Pod가 traffic을 받지 않는가? |
| graceful shutdown | 처리 중 요청을 끊지 않는가? |
| max unavailable | 배포 중 capacity가 충분한가? |
| config rollout | routing, pool, feature flag 변경 순서가 맞는가? |
| rollback | old/new code가 같은 DB schema를 읽을 수 있는가? |
| Scale out 후에는 배포 실패도 더 넓게 퍼진다. | |
| 따라서 배포 전략은 확장 설계의 일부다. |
관측 비용
Instance와 shard가 늘면 평균 지표는 점점 쓸모가 줄어든다. 필요한 지표는 분해되어야 한다.
| 관측 축 | 예시 |
|---|---|
| instance | latency_by_pod, restart_count, cpu_by_pod |
| dependency | db_pool_pending, redis_timeout, external_api_error |
| shard | qps_by_shard, lock_wait_by_shard, storage_by_shard |
| routing | routing_version, bucket_id, tenant_id |
| user impact | error_rate_by_api, p95_by_plan, degraded_response_count |
| 관측 label을 무작정 늘리면 비용과 cardinality가 폭발한다. | |
| 운영에 필요한 차원만 남기고 high-cardinality 값은 log나 trace로 옮긴다. |
백업과 복구 비용
DB가 하나일 때는 backup 하나와 restore 절차 하나로 시작할 수 있다. Shard가 4개가 되면 질문이 늘어난다.
- 특정 shard만 복구할 수 있는가?
- 전체 shard를 같은 시점으로 복구할 수 있는가?
- routing table과 data backup의 시점이 맞는가?
- restore 후 read model과 search index를 어떻게 재생성하는가?
- backup window가 peak traffic과 충돌하지 않는가? Sharding은 성능 설계이면서 복구 설계다. 복구 기준이 없으면 shard 장애는 곧 데이터 신뢰 문제로 바뀐다.
Migration 비용
확장 후 schema 변경은 더 조심스럽다.
single_db_migration_time = 20 minutes
shard_count = 8
parallelism = 2
estimated_window = 20 * (8 / 2) = 80 minutes단순 계산이어도 배포 창구와 rollback 가능성을 검토하게 만든다. Shard별 migration은 일부 성공, 일부 실패 상태가 가능하다. Old code와 new code가 동시에 동작하는 기간도 길어진다. 따라서 expand-contract 방식, backward compatible schema, shard별 진행 상태 저장이 필요하다.
장애 대응 비용
확장 후 장애 대응은 “어디가 아픈가”를 찾는 시간이 커진다.
| 장애 질문 | 단일 구조 | 확장 구조 |
|---|---|---|
| API가 느린가? | app 또는 DB | 특정 Pod, shard, replica, route |
| 데이터가 안 보이나? | DB 조회 실패 | replica lag, wrong shard, read model 지연 |
| 일부 사용자만 실패하나? | 권한 또는 데이터 문제 | tenant shard, hot key, routing version |
| rollback 가능한가? | code rollback | code, schema, data, routing 분리 |
| Runbook도 이 구조를 따라가야 한다. | ||
| 장애 중 담당자가 shard topology를 머릿속으로 복원해야 한다면 운영 설계가 부족한 것이다. |
확장하지 않는 판단
확장 설계에서 중요한 답은 “아직 하지 않는다”일 때도 많다. 다음 상황이면 sharding보다 다른 선택이 낫다.
- slow query 하나가 전체 latency를 만든다.
- retention 정책이 없어 오래된 데이터가 무한히 쌓인다.
- cache TTL이나 index 개선으로 목표 p95를 만족할 수 있다.
- write 병목이 hot row 하나에서 나온다.
- 운영팀이 shard backup과 restore를 훈련하지 않았다. 복잡도를 도입하기 전에는 더 단순한 완화가 목표를 만족하는지 먼저 확인한다.
설계 결정 기록 예시
확장 판단은 문서로 남겨야 한다.
decision: "order db sharding 보류"
reason:
- "p95 900ms의 주 원인은 missing index와 180일 retention 부재"
- "write TPS는 단일 DB safe limit의 35%"
accepted_tradeoff:
- "3개월 뒤 storage growth 재평가"
- "admin report는 read model로 분리"
rollback:
- "index 배포 후 plan regression 시 이전 index policy로 복귀"
metrics:
- "orders_table_size"
- "member_order_list_p95"
- "db_lock_wait"이 기록은 기술 이름보다 판단 근거를 남긴다. 나중에 트래픽이 바뀌면 같은 기준으로 다시 결정할 수 있다.
개인 프로젝트 기준
- 확장 구조를 그렸다면 운영 비용 표를 함께 넣는다.
- app instance 수와 DB pool 총합을 계산한다.
- sharding을 넣었다면 backup, migration, routing rollback을 설명한다.
- “아직 sharding하지 않는 이유”를 숫자로 말할 수 있게 한다.
- 포트폴리오에서는 성능 수치와 함께 장애 대응 질문을 준비한다.
기업 운영 기준
- 확장 도입 전 cost review와 capacity review를 함께 한다.
- 새 shard, replica, queue, cache는 owner와 on-call 책임을 가진다.
- 배포, schema migration, routing 변경은 독립적으로 rollback 가능해야 한다.
- restore drill과 migration dry run을 정기적으로 수행한다.
- 관측 label과 alert rule은 비용, cardinality, 사용자 영향 기준으로 관리한다.
- 확장 후 실제 비용과 latency 개선을 decision record에 다시 기록한다.
위험 신호
- 확장 설계에 비용, 백업, 복구 항목이 없다.
- app Pod 수만 늘리고 DB pool 총합을 계산하지 않는다.
- shard별 장애 대응 절차 없이 sharding을 적용한다.
- 평균 latency만 보고 특정 tenant나 shard 영향을 놓친다.
- migration 일부 실패 상태를 고려하지 않는다.
- 구조가 복잡해졌는데 운영 문서는 단일 DB 시절 그대로다.
확인 질문
확인 질문
- 확인 질문: 확장 설계가 바꾸는 것은 성능뿐인가?
- 답변: 아니다. 정합성 경계, 장애 격리 방식, 배포와 관측, 백업과 복구 비용도 함께 바뀐다.
- 확인 질문: Sharding 도입 전에 반드시 계산할 운영 비용은 무엇인가?
- 답변: shard별 routing, backup/restore, migration window, cross-shard query, metric cardinality, 장애 대응 절차다.
- 확인 질문: 확장하지 않는 판단이 합리적인 경우는 언제인가?
- 답변: query/index/retention/cache 개선으로 목표를 만족하거나 운영팀이 새 복잡도를 아직 감당할 준비가 없을 때다.