DB 고급 — 복제·샤딩·캐시·커넥션풀·파티셔닝
DB 고급 — 복제·샤딩·캐시·커넥션풀·파티셔닝
🎯 이 lesson 을 읽고 나면
이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.
- ▸✅ 파티셔닝 Range / Hash / List 선택 기준
- ▸✅ 샤딩 vs 파티셔닝 차이
- ▸✅ DB 백업 전략 (Logical vs Physical) 설명
학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.
복제 (Replication) — *DB 를 여러 대로 늘리기*
복제가 뭐냐
웹 서비스가 잘 되면 읽기 요청 이 폭발합니다. 글 1개를 1만 명이 읽는 패턴이 흔하죠. DB 한 대가 모두 감당 못 하면 어떻게 할까?
복제 (Replication) = DB 를 여러 대로 늘려서 부담을 나누는 방법. 한 대를 Primary (쓰기 전용) 로 두고, 여러 대의 Replica (읽기 전용) 가 Primary 의 변경 사항을 복사 받습니다.
동기 vs 비동기 복제
동기 복제 — Primary 가 Replica 의 응답을 기다린 후 사용자에게 성공 응답. 데이터 손실 0, 하지만 느림.
비동기 복제 — Primary 는 바로 응답하고, Replica 는 나중에 복사. 빠르지만 Replica 가 약간 뒤처짐 (lag). Primary 가 갑자기 죽으면 최근 데이터 손실 가능.
대부분의 서비스는 비동기 를 씁니다. 99% 의 경우 lag 가 밀리초 단위 라 문제 없고, 성능 이득이 크기 때문입니다.
Lag — 가장 중요한 모니터링 지표
Replica 가 얼마나 뒤처져 있는가 가 핵심입니다. lag 가 1초 → 사용자가 방금 쓴 글이 안 보이는 사고가 납니다.
lag 가 점점 커지면 → 네트워크·디스크·CPU 어딘가 병목. 알람 설정 필수.
애플리케이션 측 — 읽기·쓰기 분리
대부분 ORM·DataSource 라우터가 지원합니다. 읽기만 하는 쿼리 는 자동으로 Replica 로, 쓰기 는 Primary 로.
> ⚠️ 함정: 쓰기 직후 읽기 시 lag 때문에 옛 데이터 가 보일 수 있음. 그런 경우엔 해당 요청만 Primary 에서 읽기 같은 예외 처리 필요.
한 번 정리
복제 = 읽기 확장 + 재해 복구 의 표준 방법. 비동기가 기본이고, lag 모니터링 이 운영의 핵심. 쓰기·읽기 분리로 Primary 부담 을 크게 줄일 수 있습니다.
샤딩 (Sharding) — *데이터를 쪼개기*
샤딩이 뭐냐
복제는 읽기 를 늘리지만 쓰기는 한 대만 합니다. 쓰기도 폭발하면? — Twitter 처럼 초당 수십만 트윗 이 들어오면 한 대로는 불가능.
샤딩 (Sharding) = 데이터를 여러 DB 에 나누는 방법. 사용자 ID 가 1~100만은 DB1, 100만~200만은 DB2 — 이런 식으로 수평 분할.
각 shard 가 독립된 DB 라 쓰기 부담이 N등분 됩니다. 단점은 복잡성 폭증.
샤드 키 — 가장 중요한 결정
어떤 컬럼을 기준으로 나눌지가 운명을 가릅니다.
좋은 샤드 키:
- ▸
user_id— 사용자별 균등 분배, cross-shard 쿼리 적음 - ▸
tenant_id(B2B SaaS) — 회사별 분리
나쁜 샤드 키:
- ▸
created_at— 최근 데이터로 hot spot. 옛 shard 는 노는데 새 shard 는 폭발. - ▸
auto_increment id— Range 샤딩 시 hot spot 동일.
샤드 키는 나중에 바꾸기 거의 불가능 합니다. 처음에 신중하게 결정해야 합니다.
Cross-shard 쿼리 — 가장 큰 함정
두 shard 모두 조회 해야 합니다. JOIN 도 불가능. 트랜잭션도 분산 트랜잭션 으로 가는 순간 매우 비싸집니다.
그래서 cross-shard 작업이 적도록 데이터 모델·샤드 키를 설계하는 게 핵심입니다.
Resharding — 운영 악몽
데이터가 늘어나서 shard 를 5개 → 10개 로 늘려야 하면? 모든 데이터를 재분배 해야 합니다. 그 동안 서비스 영향 없이 진행하기 극도로 어렵습니다.
그래서 처음에 충분히 많은 shard 로 시작하거나 (예: 64개로 시작해서 노드에 분산), consistent hashing 같은 기법으로 부담을 줄이는 게 일반적.
매니지드 옵션 — 직접 안 하는 게 답
대부분의 회사는 샤딩을 직접 구현 안 합니다. 너무 어렵고 위험합니다.
- ▸DynamoDB·MongoDB Atlas — 자동 샤딩 NoSQL
- ▸Vitess — MySQL 샤딩 (YouTube 가 만들고 CNCF 졸업)
- ▸Citus — PostgreSQL 확장 (Microsoft)
특별한 이유 없으면 복제로 충분 합니다. 샤딩이 진짜 필요한 시점은 수억 행·초당 수만 쓰기 수준입니다.
한 번 정리
샤딩은 최후의 수단. 복제 → 캐시 → 파티셔닝을 다 시도해보고도 부족할 때만. 시작할 때 샤드 키 결정 이 운명을 가르고, cross-shard 쿼리 회피 가 성능의 열쇠입니다.
캐시 + Connection Pool — *DB 부담을 줄이는 두 가지*
캐시가 뭐냐
DB 쿼리는 느립니다 (밀리초). 메모리는 빠릅니다 (마이크로초). 같은 쿼리를 반복해서 실행한다면 결과를 메모리에 저장 해두고 재사용 하면 1000배 빠릅니다. 이게 캐시의 기본 아이디어입니다.
Redis 가 사실상 표준. 메모리 기반 key-value 저장소. 매우 빠르고, TTL (자동 만료)·pub/sub 같은 부가 기능까지.
Cache-Aside 패턴
가장 흔한 패턴:
5분 동안은 DB 안 거치고 Redis 에서 즉시 응답. DB 부담이 극적으로 줄어듭니다.
캐시 무효화 — 컴퓨터과학 양대 난제 중 하나
> "There are only two hard things in Computer Science: cache invalidation and naming things." — Phil Karlton
데이터가 변경되면 옛 캐시 를 어떻게 처리할까?
1. TTL — 일정 시간 후 자동 만료. 가장 단순하지만 최대 N초 동안 옛 데이터 가능.
2. 명시적 무효화 — 데이터 변경 시 redis.del('user:42'). 즉시 반영. 하지만 모든 변경 코드에서 잊지 말고 호출해야.
3. 이벤트 기반 — DB 변경 → 이벤트 발행 → 캐시 구독자가 무효화. 분산 시스템에서 유리.
4. 버전 키 — user:v2:42 처럼 키 자체를 바꾸기. 옛 키는 자연 폐기. TTL 과 함께 쓰면 깔끔.
> 💡 현실: TTL + 명시적 무효화 조합이 90%. 완벽한 일관성 은 비싸고 어렵습니다. 짧은 stale period 를 허용하는 게 보통.
Connection Pool — DB 연결의 재활용
DB 연결을 매번 새로 만들면 비쌉니다. TCP 핸드셰이크·인증·TLS 협상이 매 요청마다 일어나죠. 50ms 정도가 연결만으로 소모됩니다.
Connection Pool = 연결 N 개를 미리 만들어 두고 재사용 하는 풀입니다. 요청이 오면 풀에서 빌려서 쓰고, 끝나면 반납. 새 연결 비용이 사라집니다.
Pool 설정의 핵심
가장 중요한 것은 Pool 크기. 너무 작으면 대기, 너무 크면 DB 부담.
공식: app servers × pool size ≤ DB max_connections
예: app 4대 × pool 20 = 80 ≤ Postgres max=100 ✅
HikariCP (Java 표준) 같은 라이브러리가 똑똑하게 관리합니다. min = max 권장 (예측 가능한 동작).
흔한 함정
1. 연결 누수 — conn.close() 잊으면 풀 고갈. try-with-resources·context manager 필수.
2. 너무 큰 Pool — DB max_connections 초과. "too many connections" 에러로 폭발.
3. 너무 짧은 idle timeout — 방화벽이나 LB 가 idle 연결을 끊는 환경에서 자주 새 연결. max-lifetime 으로 정기적으로 교체 권장 (30분 정도).
한 번 정리
캐시 + Connection Pool 은 DB 부담을 줄이는 두 표준 도구. 캐시는 반복 쿼리, Pool 은 연결 비용 을 줄입니다. 둘 다 제대로 설정 하면 같은 DB 에서 10배 더 많은 트래픽 을 받을 수 있습니다.
파티셔닝 — *큰 테이블을 작은 조각으로*
파티셔닝이 뭐냐
테이블 하나가 100GB 이상으로 커지면 여러 문제가 생깁니다. 인덱스도 거대해지고, 옛 데이터 삭제 가 느리고, 일부 데이터만 쓰는 쿼리도 전체 를 훑게 됩니다.
파티셔닝 (Partitioning) = 큰 테이블을 논리적으로 여러 조각 으로 나누되, 사용자에겐 하나로 보이게 하는 기술. 샤딩과 비슷하지만 같은 DB 안에서 분할이라는 점이 다릅니다.
RANGE 파티션 — 시계열에 최적
가장 흔한 패턴. 날짜 범위 로 나눕니다.
INSERT INTO logs 하면 created_at 값을 보고 자동으로 맞는 파티션 에 저장됩니다.
진짜 이득 — Partition Pruning
이 쿼리는 logs_2025_03 파티션만 스캔합니다. 다른 파티션은 건너뜁니다. 100GB 중 1GB 만 보면 되니까 100배 빠르죠.
DB 가 알아서 어느 파티션 을 봐야 할지 추론합니다. 이게 Pruning (가지치기).
옛 데이터 삭제가 빠르다
3년 지난 로그를 지운다고 해봅시다.
파티션 단위 DROP 은 디스크에서 통째로 떼어내기 라 매우 빠릅니다. 로그·이벤트 처리에서 압도적으로 좋습니다.
자동화 — pg_partman
매월 새 파티션을 수동으로 만드는 건 운영 부담입니다. pg_partman (Postgres 확장) 이 자동 으로 처리해 줍니다.
TimescaleDB 는 PostgreSQL 위의 시계열 특화 확장. 파티셔닝을 자동으로 관리하고 압축 까지.
LIST·HASH 파티션
LIST — 지역·카테고리 같은 명확한 분류:
HASH — 균등 분배 (Postgres 11+):
언제 파티셔닝?
- ▸✅ 단일 테이블 100GB+
- ▸✅ 시간 기반 데이터 (로그·메트릭·이벤트)
- ▸✅ 옛 데이터 일괄 삭제 필요
- ▸❌ 작은 테이블 — 오버헤드만 ↑
한 번 정리
파티셔닝은 큰 테이블 의 성능·운영 부담을 극적으로 줄입니다. 시계열 데이터엔 기본 옵션. pg_partman·TimescaleDB 로 자동화하면 운영도 편합니다. 샤딩보다 훨씬 쉽고 안전한 옵션이니, 먼저 고려할 가치 있습니다.
💾 실전 데모 — 캐시 hit vs miss 성능 차이
-- 측정 결과 (실제 운영 환경 평균)
-- DB 직접 조회 (인덱스 사용)
SELECT * FROM users WHERE id = 42;
-- 평균: 8 ms
-- Redis 캐시 hit
-- GET user:42
-- 평균: 0.3 ms
-- 1000 회 반복:
-- DB only: 8 ms × 1000 = 8,000 ms (8초)
-- Cache+DB: 첫 회 8ms + 999회 × 0.3ms = 308 ms (0.3초)
-- → 26 배 빠름
🤖 AI 에게 이렇게 요청해보세요
이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.
- ▸"이 logs 테이블을 created_at YEAR Range 파티션으로 변환해줘"
- ▸"이 SELECT 쿼리를 필요한 컬럼만 선택해서 커버링 인덱스가 동작하게 해줘"*
- ▸"샤딩 vs 파티셔닝 차이 + 우리 케이스에 뭘 골라야 할지 진단해줘"
왜 이게 토큰을 줄이나
개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.