📋 전체 학습 목차
⚖️ Chapter 01
대규모 시스템 설계 프레임워크 — 어디서부터 시작할까?
"면접에서도, 현업에서도 통하는 시스템 설계 4단계 접근법"
🗺️ 규모 추정 치트시트 — 암기 필수!
시스템 설계 면접에서 규모 추정(Back of the envelope estimation)은 필수입니다. 이 숫자들을 외워두면 어떤 면접에서도 자신있게 답할 수 있습니다.
⏱️ 레이턴시 기준값
| 작업 | 시간 |
|---|---|
| L1 캐시 접근 | 0.5 ns |
| 메모리(RAM) 접근 | 100 ns |
| SSD 랜덤 읽기 | 150 µs |
| HDD 탐색 | 10 ms |
| 같은 DC 네트워크 왕복 | 0.5 ms |
| 서울↔미국 네트워크 | 150 ms |
📊 규모 단위 기준
| 단위 | 값 | 예시 |
|---|---|---|
| 1 KB | 10³ bytes | 텍스트 1페이지 |
| 1 MB | 10⁶ bytes | 사진 1장 |
| 1 GB | 10⁹ bytes | 영화 1편 |
| 1 TB | 10¹² bytes | 영화 1,000편 |
| 초당 요청(QPS) | DAU × 평균요청 / 86400 | - |
| 하루=86,400초 | ≈ 10만 초 | 암기! |
🔢 실전 규모 추정 예제 — 트위터 클론
# 가정
DAU = 3억 명
트윗 읽기 : 쓰기 = 100 : 1
사용자 1명이 하루 평균 트윗 1개 작성, 읽기 100개
# QPS 계산
쓰기 QPS = 3억 / 86400 ≈ 3,500 QPS
읽기 QPS = 3,500 × 100 = 350,000 QPS # 피크는 2배 = 700,000 QPS
# 저장 용량 계산 (5년)
트윗 1개 용량 = 텍스트 300B + 메타데이터 200B = 500B
하루 새 트윗 = 3억 개
5년 총 트윗 = 3억 × 365 × 5 = 5,475억 개
5년 저장 공간 = 5,475억 × 500B ≈ 274 TB
# 미디어 저장 (트윗 중 10%가 이미지, 평균 1MB)
하루 미디어 = 3억 × 0.1 × 1MB = 30 TB/일
5년 미디어 = 30 × 365 × 5 ≈ 54 PB
# 결론: 어떤 아키텍처가 필요한가?
- DB 읽기 부하: 캐시(Redis) 필수
- 쓰기 부하: DB 파티셔닝/샤딩 필요
- 미디어: CDN + 분산 스토리지(S3)
- 팔로워 피드: 팬아웃 전략 필요
🌐 Chapter 02
DNS와 CDN — 글로벌 트래픽 분산의 핵심
"서울 서버 하나로 뉴욕 사용자에게 0.1초 응답? CDN이 해냅니다"
🌍 CDN(Content Delivery Network) 완전 이해
CDN은 전 세계 각지에 엣지 서버(Edge Server)를 두고, 사용자에게 가장 가까운 서버에서 콘텐츠를 제공합니다. 마치 전국 각지에 편의점(엣지 서버)이 있어서, 본사(오리진 서버)까지 가지 않아도 되는 것과 같습니다.
변경 없는 파일 캐싱
CloudFront, Cloudflare
Edge Computing
Cloudflare Workers
실시간 스트리밍
AWS MediaPackage
# CDN 캐시 전략 — Cache-Control 헤더
// NestJS에서 정적 파일 캐시 설정
@Get('image/:id')
@Header('Cache-Control', 'public, max-age=86400, s-maxage=604800')
// max-age=86400 → 브라우저 1일 캐시
// s-maxage=604800 → CDN 7일 캐시 (CDN 전용)
getImage(@Param('id') id: string) { ... }
// API 응답은 캐시하지 않음 (실시간 데이터)
@Header('Cache-Control', 'no-store')
// 버전 관리된 파일은 영구 캐시
// main.a3b4c5.js → 파일명에 해시 포함
@Header('Cache-Control', 'public, max-age=31536000, immutable')
// immutable → 만료 전 재검증 요청 안 함 (성능 최적화)
⚡ Chapter 03
로드 밸런싱 완전 정복 — 트래픽을 고르게 나누는 법
"식당 입구에서 빈 테이블로 안내하는 직원이 바로 로드밸런서입니다"
🍽️ 로드밸런서를 식당으로 이해하기
인기 식당에 손님이 몰리면 어떻게 하나요? 여러 개의 테이블(서버)을 준비하고, 입구 직원(로드밸런서)이 빈 테이블로 안내합니다. 단, 어떤 테이블로 안내하느냐에는 다양한 전략이 있습니다.
| 알고리즘 | 방식 | 적합 상황 | 단점 |
|---|---|---|---|
| Round Robin | 순서대로 1→2→3→1... | 서버 성능이 동일할 때 | 서버 부하 무시 |
| Weighted RR | 가중치에 따라 분배 | 서버 성능 다를 때 | 설정 관리 필요 |
| Least Connections | 연결 수 가장 적은 서버 | 요청 처리 시간 다를 때 | 연결 수 추적 오버헤드 |
| IP Hash | IP 기반 특정 서버 고정 | 세션 유지 필요 시 | 서버 추가 시 재매핑 |
| Least Response Time | 응답 시간 가장 짧은 서버 | 응답 시간 중요 시 | 구현 복잡 |
# Nginx 로드밸런서 설정 — 실전
upstream api_servers {
# Least Connections 알고리즘
least_conn;
server api1.internal:3000 weight=3; # 성능 좋은 서버 3배 더
server api2.internal:3000 weight=2;
server api3.internal:3000 weight=1;
# 헬스체크: 3번 실패하면 제외, 10초마다 재시도
server api4.internal:3000 backup; # 나머지 다 죽으면 사용
# Keep-Alive 커넥션 풀 (성능 최적화)
keepalive 32;
}
server {
listen 80;
location /api/ {
proxy_pass http://api_servers;
proxy_http_version 1.1;
proxy_set_header Connection ""; # keep-alive 활성화
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 서버 장애 시 다른 서버로 자동 재시도
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 3;
# 타임아웃 설정
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
}
🏗️ L4 vs L7 로드밸런서 비교
L4 (Transport Layer)
TCP/UDP 레벨에서 동작
IP + Port 기반 라우팅
• 빠름 (패킷 내용 안 봄)
• HTTP 헤더/URL 모름
• AWS NLB
• 게임 서버, IoT에 적합
L7 (Application Layer)
HTTP/HTTPS 레벨에서 동작
URL, 헤더, 쿠키 기반 라우팅
• SSL Termination
• 경로 기반 라우팅 가능
• AWS ALB, Nginx
• 웹 API, MSA에 적합
💾 Chapter 04
캐싱 전략 완전 정복 — Redis 심화
"캐시는 마법이 아닙니다. 어디에 무엇을 얼마나 캐시할지가 핵심입니다"
🗺️ 캐시 계층 구조 — 어디에 캐시를 둘까?
0ms
~10ms
~5ms
~1ms
~50ms
🔄 캐시 패턴 4종 완전 이해
| 패턴 | 흐름 | 장점 | 단점 | 사용처 |
|---|---|---|---|---|
| Cache-Aside | 앱→캐시미스→DB→캐시저장 | 구현 쉬움 | Cold Start | 읽기 많은 API |
| Write-Through | DB쓰기+캐시쓰기 동시 | 일관성 보장 | 쓰기 지연 | 금융 데이터 |
| Write-Behind | 캐시 먼저→DB 비동기 | 쓰기 빠름 | 데이터 유실 위험 | 로그, 조회수 |
| Read-Through | 캐시가 DB 자동 조회 | 앱 로직 단순 | 첫 요청 느림 | ORM 캐시 |
💻 Redis 고급 패턴 — 실전 NestJS 구현
// Redis 고급 활용 패턴 (NestJS)
// 1. 분산 락 (Distributed Lock) — 동시성 제어
@Injectable()
export class RedisLockService {
async acquireLock(key: string, ttlMs: number = 5000): Promise<boolean> {
// SET key value NX PX ttl (존재하지 않을 때만 SET)
const result = await this.redis.set(
`lock:${key}`,
'1',
'NX', // Not eXists: 없을 때만
'PX', // 밀리초 단위 TTL
ttlMs
);
return result === 'OK';
}
async releaseLock(key: string) {
await this.redis.del(`lock:${key}`);
}
// 사용 예: 중복 결제 방지
async processPayment(orderId: string) {
const locked = await this.acquireLock(`payment:${orderId}`);
if (!locked) throw new Error('이미 처리 중인 주문입니다');
try {
// 결제 처리 로직
} finally {
await this.releaseLock(`payment:${orderId}`);
}
}
}
// 2. 캐시 스탬피드(thundering herd) 방지 — 동시 캐시 미스 처리
async getWithLock(key: string, fetchFn: () => Promise<any>) {
let data = await this.redis.get(key);
if (data) return JSON.parse(data);
// 캐시 미스 시 한 프로세스만 DB 조회하도록 락
const lockKey = `lock:${key}`;
const locked = await this.redis.set(lockKey, '1', 'NX', 'PX', 10000);
if (!locked) {
// 다른 프로세스가 채우는 중 → 잠시 대기 후 재시도
await sleep(100);
return this.getWithLock(key, fetchFn);
}
const freshData = await fetchFn();
await this.redis.setex(key, 3600, JSON.stringify(freshData));
await this.redis.del(lockKey);
return freshData;
}
// 3. Sliding Window Rate Limiter (슬라이딩 윈도우)
async isRateLimited(userId: string, limit = 100, windowSec = 60): Promise<boolean> {
const key = `ratelimit:${userId}`;
const now = Date.now();
const windowStart = now - windowSec * 1000;
// Lua 스크립트로 원자적 처리
const script = `
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
local count = redis.call('ZCARD', KEYS[1])
if count < tonumber(ARGV[2]) then
redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
return 0
end
return 1
`;
const result = await this.redis.eval(script, 1, key, windowStart, limit, now, windowSec);
return result === 1; // 1 = 제한됨
}
🗄️ Chapter 05
데이터베이스 스케일링 전략 — 1억 건 데이터 다루기
"DB 스케일링을 모르면 서비스 성장의 발목이 잡힙니다"
📈 DB 스케일링 전략 비교
⬆️ Scale-Up (수직 확장)
더 강력한 서버로 교체
CPU, RAM, SSD 업그레이드
• 구현 간단
• 단일 장애점(SPOF)
• 한계가 있음 (물리적 한계)
• 고비용
↔️ Scale-Out (수평 확장)
여러 서버로 부하 분산
읽기: Read Replica
쓰기: Sharding
• 이론상 무한 확장
• 복잡한 구현
• 분산 트랜잭션 어려움
📖 Read Replica — 읽기 부하 분산
전형적인 웹 서비스는 읽기:쓰기 = 80:20 또는 90:10 비율입니다. Master DB는 쓰기만 담당하고, 여러 Replica가 읽기를 나눠 처리합니다.
// TypeORM Read Replica 설정
TypeOrmModule.forRoot({
type: 'postgres',
replication: {
master: {
host: 'primary.db.internal', // 쓰기 전용
port: 5432,
database: 'mydb',
username: 'user',
password: 'pass',
},
slaves: [
{ host: 'replica1.db.internal', port: 5432, database: 'mydb', username: 'user', password: 'pass' },
{ host: 'replica2.db.internal', port: 5432, database: 'mydb', username: 'user', password: 'pass' },
],
},
// TypeORM이 SELECT는 자동으로 replica에서 실행!
// INSERT/UPDATE/DELETE는 master에서 실행
})
// 주의: 복제 지연(Replication Lag)!
// 쓰기 직후 읽기 시 최신 데이터가 없을 수 있음
// 해결: 방금 쓴 데이터는 Master에서 읽기 (강제 지정)
const masterEntityManager = dataSource.createEntityManager({ replication: { primary: true } });
🔀 Chapter 06
데이터베이스 파티셔닝과 샤딩
"1억 건이 넘는 데이터를 하나의 테이블에 넣으면 안 됩니다"
📂 파티셔닝 vs 샤딩 차이
📑 파티셔닝 (같은 DB 서버)
하나의 테이블을 논리적으로 분할
• Range 파티션: 날짜별 분리
• List 파티션: 지역별 분리
• Hash 파티션: 균등 분배
예: orders 테이블을 월별로 분리
orders_2024_01, orders_2024_02...
🔪 샤딩 (다른 DB 서버)
데이터를 여러 DB 서버로 분산
• User ID 기반 샤딩
• 지역 기반 샤딩
• 일관된 해싱(Consistent Hashing)
예: shard0: userId % 3 = 0
shard1: userId % 3 = 1
shard2: userId % 3 = 2
// 샤딩 라우터 구현 예시
@Injectable()
export class ShardRouter {
private shards: DataSource[];
getShardForUser(userId: string): DataSource {
// 일관된 해싱으로 샤드 결정
const hash = this.consistentHash(userId);
const shardIndex = hash % this.shards.length;
return this.shards[shardIndex];
}
// ⚠️ 샤딩의 주요 문제점
// 1. 크로스-샤드 JOIN 불가 → 앱 레벨에서 처리
// 2. 샤드 추가 시 데이터 재분배 (리샤딩) 비용
// 3. 분산 트랜잭션 매우 복잡
// → 일관된 해싱으로 리샤딩 최소화
}
📨 Chapter 07
메시지 큐와 이벤트 드리븐 아키텍처
"결합도를 낮추고 확장성을 높이는 비동기 메시지 처리"
📮 메시지 큐를 우체국으로 이해하기
메시지 큐는 우체국과 같습니다. 보내는 사람(Producer)이 편지(메시지)를 우체통(큐)에 넣으면, 받는 사람(Consumer)이 편의에 맞게 꺼내 처리합니다. 서로 동시에 연결할 필요가 없습니다.
| 비교 | Kafka | RabbitMQ | AWS SQS |
|---|---|---|---|
| 처리량 | 100만+/초 | 수만/초 | 수천~수만/초 |
| 메시지 보관 | 영구 (설정 기간) | 처리 후 삭제 | 14일 |
| 순서 보장 | 파티션 내 보장 | 큐 단위 보장 | FIFO 큐 사용 |
| 재처리 | 오프셋으로 재처리 | DLQ로 이동 | DLQ 설정 |
| 적합 사용처 | 이벤트 스트리밍, 로그 | 작업 큐, RPC | 서버리스, AWS 통합 |
// NestJS + Kafka 이벤트 드리븐 구현
// 1. Producer — 주문 이벤트 발행
@Injectable()
export class OrderService {
constructor(@Inject('KAFKA_CLIENT') private kafka: ClientKafka) {}
async createOrder(dto: CreateOrderDto) {
const order = await this.orderRepo.save(dto);
// 이벤트 발행 (비동기, 다른 서비스가 처리)
await this.kafka.emit('order.created', {
orderId: order.id,
userId: order.userId,
items: order.items,
totalAmount: order.totalAmount,
createdAt: order.createdAt,
});
// 주문 서비스는 여기서 완료! 결제/알림/재고는 각 서비스가 처리
return order;
}
}
// 2. Consumer — 결제 서비스 (order.created 이벤트 소비)
@Controller()
export class PaymentConsumer {
@MessagePattern('order.created')
async handleOrderCreated(
@Payload() data: OrderCreatedEvent,
@Ctx() context: KafkaContext,
) {
try {
await this.paymentService.processPayment(data.orderId, data.totalAmount);
context.getMessage().commit(); // 성공 시 오프셋 커밋
} catch (err) {
// 실패 시 DLQ(Dead Letter Queue)로 이동
await this.kafka.emit('order.payment.failed', data);
}
}
}
// 3. 알림 서비스도 동일한 이벤트 구독 (서비스 간 결합도 없음!)
@MessagePattern('order.created')
async handleOrderCreated(data: OrderCreatedEvent) {
await this.notificationService.sendOrderConfirmation(data.userId, data.orderId);
}
🔩 Chapter 08
마이크로서비스 아키텍처 패턴 — MSA 설계의 모든 것
"모놀리스에서 MSA로 넘어갈 때 알아야 할 핵심 패턴들"
🏗️ 모놀리스 vs MSA — 언제 MSA로 가야 하나?
| 구분 | 모놀리스 | MSA |
|---|---|---|
| 초기 개발 속도 | ✅ 빠름 | ❌ 느림 (인프라 복잡) |
| 독립 배포 | ❌ 전체 재배포 | ✅ 서비스별 독립 |
| 장애 격리 | ❌ 전체 영향 | ✅ 일부만 영향 |
| 스케일링 | ❌ 전체 스케일 | ✅ 서비스별 스케일 |
| 데이터 관리 | ✅ 단일 DB (JOIN 쉬움) | ❌ 서비스별 DB (JOIN 어려움) |
| 운영 복잡도 | ✅ 낮음 | ❌ 높음 (분산 트레이싱 필요) |
| 추천 시점 | 초기, 팀 3~5명 | 팀 50명+, 서비스 복잡할 때 |
🎯 MSA 핵심 패턴 6가지
💻 SAGA 패턴 — 분산 트랜잭션 실전 구현
// SAGA 패턴 — 주문-결제-배송 분산 트랜잭션
// Choreography 방식 (이벤트 기반)
// 정상 흐름
// 1. 주문 서비스: 주문 생성 → order.created 발행
// 2. 재고 서비스: order.created 수신 → 재고 감소 → inventory.reserved 발행
// 3. 결제 서비스: inventory.reserved 수신 → 결제 → payment.completed 발행
// 4. 배송 서비스: payment.completed 수신 → 배송 시작
// 실패 흐름 (보상 트랜잭션)
// 3. 결제 실패 → payment.failed 발행
// 2. 재고 서비스: payment.failed 수신 → 재고 복원 (보상)
// 1. 주문 서비스: inventory.release 수신 → 주문 취소 (보상)
@MessagePattern('payment.failed')
async handlePaymentFailed(event: PaymentFailedEvent) {
// 보상 트랜잭션: 재고 복원
await this.inventoryService.releaseReservation(event.orderId);
// 주문 서비스에게 알림
await this.kafka.emit('inventory.released', { orderId: event.orderId });
}
// CQRS 패턴 — 읽기/쓰기 모델 분리
// Write Model (Command): 정규화된 DB (MySQL)
export class CreateProductCommand {
constructor(public readonly name: string, public readonly price: number) {}
}
// Read Model (Query): 비정규화된 ElasticSearch (빠른 검색)
// 이벤트를 통해 Write→Read 동기화
@EventsHandler(ProductCreatedEvent)
export class ProductCreatedHandler {
async handle(event: ProductCreatedEvent) {
// ES에 읽기 최적화된 형태로 저장
await this.elasticsearchService.index({
index: 'products',
id: event.productId,
document: {
name: event.name,
price: event.price,
searchText: `${event.name} ${event.category}`,
}
});
}
}
🛡️ Chapter 09
Circuit Breaker와 장애 격리 패턴
"한 서비스의 장애가 전체를 무너뜨리지 않게 하는 방법"
💡 Circuit Breaker — 전기 회로 차단기로 이해하기
전기 차단기는 과전류 시 전기를 차단해 화재를 막습니다. 서킷 브레이커 패턴도 마찬가지입니다. 연결된 서비스가 계속 실패하면, 더 이상 요청을 보내지 않고 빠르게 실패(Fail Fast)해서 시스템을 보호합니다.
에러율 모니터링
fallback 실행
성공 시 CLOSED
// NestJS Circuit Breaker 구현
// npm install opossum
@Injectable()
export class PaymentService {
private breaker: CircuitBreaker;
constructor(private httpService: HttpService) {
// 서킷 브레이커 설정
this.breaker = new CircuitBreaker(
async (amount: number) => {
return this.httpService.post('http://payment-service/pay', { amount }).toPromise();
},
{
timeout: 3000, // 3초 내 응답 없으면 실패
errorThresholdPercentage: 50, // 50% 실패율 시 OPEN
resetTimeout: 30000, // 30초 후 HALF-OPEN으로
volumeThreshold: 10, // 최소 10번 요청 후 판단
}
);
// fallback: 서킷 OPEN 시 대안 응답
this.breaker.fallback((amount: number) => ({
success: false,
message: '결제 서비스 일시 장애. 잠시 후 다시 시도해주세요.',
retryAfter: 30,
}));
// 상태 변화 이벤트
this.breaker.on('open', () => this.logger.warn('🔴 Circuit OPEN: payment-service'));
this.breaker.on('halfOpen', () => this.logger.log('🟡 Circuit HALF-OPEN'));
this.breaker.on('close', () => this.logger.log('🟢 Circuit CLOSED: 복구됨'));
}
async pay(amount: number) {
return this.breaker.fire(amount); // 자동으로 상태 관리
}
}
🔍 Chapter 10
서비스 디스커버리와 API Gateway
"수십 개의 마이크로서비스 주소를 어떻게 관리하나요?"
🔍 서비스 디스커버리 — 자동 주소록
MSA에서 서비스는 동적으로 생성·삭제됩니다. IP도 바뀝니다. 서비스 디스커버리는 이 주소를 자동으로 관리하는 자동 주소록입니다.
📱 Client-Side Discovery
클라이언트가 레지스트리에서 서비스 주소 조회
• Netflix Eureka
• etcd, Consul
클라이언트 로드밸런싱 로직 포함
→ 클라이언트 복잡도 증가
🖥️ Server-Side Discovery
클라이언트 → LB → 레지스트리 → 서비스
• AWS ELB + Route 53
• Kubernetes Service
클라이언트는 단순해짐
→ LB가 디스커버리 담당
# Kubernetes에서 서비스 디스커버리 — DNS 자동 등록
# Service 생성 시 자동으로 DNS 등록됨
# 서비스명.네임스페이스.svc.cluster.local
# 예시
payment-svc.production.svc.cluster.local:80
user-svc.production.svc.cluster.local:80
# NestJS에서 서비스 호출 (하드코딩 대신 DNS 사용)
// ❌ 하드코딩
http://10.0.1.45:3001/pay
// ✅ DNS 기반 (K8s 내부)
http://payment-svc/pay
// ✅ 환경변수로 관리
PAYMENT_SERVICE_URL=http://payment-svc
USER_SERVICE_URL=http://user-svc
📊 Chapter 11
분산 추적과 관찰가능성 — 장애 원인을 찾는 방법
"10개 서비스를 거친 요청에서 어디서 느려졌는지 어떻게 알까요?"
🔭 관찰가능성 3대 요소 (Observability Pillars)
CPU, 메모리, QPS, 에러율
Prometheus + Grafana
에러, 요청/응답
ELK Stack, Loki
서비스 간 이동 경로
Jaeger, Zipkin, Tempo
// OpenTelemetry — 분산 추적 표준 (NestJS)
// npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node
// tracing.ts — 앱 시작 전 초기화 필수
const sdk = new NodeSDK({
serviceName: 'order-service',
traceExporter: new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations(), // HTTP, Express, DB 자동 추적
],
});
sdk.start();
// Trace ID가 자동으로 모든 서비스로 전파됨
// 요청 헤더에 traceparent: 00-TRACE_ID-SPAN_ID-01 포함
//
// Jaeger UI에서 확인:
// api-gateway (10ms)
// └─ order-service (8ms)
// │ └─ DB query (5ms) ← 병목!
// └─ payment-service (3ms)
🔐 Chapter 12
분산 시스템 일관성과 CAP 이론
"분산 시스템의 근본적인 한계와 트레이드오프를 이해합니다"
📐 CAP 이론 완전 이해
CAP 이론: 분산 시스템에서 C, A, P 세 가지를 동시에 모두 보장할 수 없습니다. 반드시 하나를 포기해야 합니다.
| 타입 | 특성 | 대표 DB | 사용처 |
|---|---|---|---|
| CP | 일관성 + 분할내성 | MongoDB, HBase, ZooKeeper | 금융, 재고, 분산 락 |
| AP | 가용성 + 분할내성 | Cassandra, CouchDB, DynamoDB | SNS 피드, 로그, 장바구니 |
| CA | 일관성 + 가용성 | MySQL, PostgreSQL (단일 노드) | 단일 서버 환경 |
⚖️ PACELC 이론 — CAP의 확장
CAP이 "네트워크 분할 시" 선택이라면, PACELC는 정상 상태에서도 Latency(지연) vs Consistency(일관성) 트레이드오프가 있다는 것을 추가로 설명합니다.
# PACELC = 네트워크 분할(Partition) 시: A vs C
# 정상(Else) 시: L(Latency) vs C(Consistency)
# 예시: 뱅킹 앱 (일관성 중시)
계좌 이체 API:
→ 쓰기: 모든 레플리카에 동기 복제 완료 후 응답
→ 지연 높음 (100ms+) but 항상 최신 데이터 보장
# 예시: SNS 좋아요 수 (가용성/지연 중시)
좋아요 카운트 API:
→ 쓰기: 하나만 쓰고 즉시 응답 (비동기 복제)
→ 지연 낮음 (10ms) but 잠시 다른 수가 보일 수 있음
→ Eventual Consistency (결국 일관성 달성)
# 결국 비즈니스 요구사항에 따라 결정!
📐 Chapter 13
실전 시스템 설계 면접 — 인스타그램/유튜브 클론
"실제 면접에서 나오는 설계 문제 2가지를 완전히 풀어드립니다"
📸 인스타그램 피드 시스템 설계
# 요구사항
DAU: 5억 명
사진/동영상 업로드: 하루 1억 건
피드 읽기: 하루 50억 건 (쓰기:읽기 = 1:50)
# 핵심 기능
1. 사진/동영상 업로드
2. 팔로워 피드 (최근 게시글)
3. 좋아요, 댓글
# 아키텍처 결정
미디어 저장:
사진 → S3 + CloudFront CDN
이미지 리사이징 → Lambda (썸네일 자동 생성)
형식: WEBP 변환 (JPEG 대비 30% 작음)
피드 생성 전략:
팔로워 < 1만 명: Fan-out on Write
→ 게시 시 팔로워의 피드 테이블에 즉시 복사
→ 읽기 빠름, 쓰기 부하 높음
팔로워 > 1만 명 (셀럽): Fan-out on Read
→ 피드 조회 시 팔로잉 목록 기반 조합
→ 쓰기 부하 없음, 읽기 복잡
데이터 모델:
posts 테이블 (샤딩: user_id 기반)
feeds 테이블 (Redis sorted set: 타임스탬프 score)
users 테이블
follows 테이블
캐싱:
인기 계정 피드: Redis에 24시간 캐싱
이미지 URL: CDN 캐싱 (7일)
좋아요 수: Redis counter (Write-Behind)
🎬 유튜브 동영상 시스템 설계
# 핵심 설계 포인트
1. 동영상 업로드 플로우:
클라이언트 → API Gateway
→ S3 원본 저장 (업로드 완료 이벤트)
→ Kafka: video.uploaded 이벤트 발행
→ 트랜스코딩 서비스 (비동기)
→ 다양한 해상도 생성 (240p/480p/720p/1080p/4K)
→ HLS 형식으로 변환 (스트리밍 최적화)
→ S3에 저장 → CloudFront CDN 배포
→ 메타데이터 DB (제목, 설명, 썸네일)
→ 검색 인덱싱 (ElasticSearch)
2. 동영상 스트리밍:
HLS(HTTP Live Streaming) 사용
→ 동영상을 수 초 단위 청크(chunk)로 분할
→ 네트워크 속도에 따라 해상도 자동 변경
→ CDN에서 가장 가까운 엣지에서 서빙
3. 인기 동영상 최적화:
상위 20% 인기 영상 = 80% 트래픽
→ 인기 영상: CDN에 더 많은 엣지 캐싱
→ 실시간 조회수: Redis 카운터 (배치로 DB 동기화)
4. 추천 시스템:
협업 필터링 + 콘텐츠 기반 필터링 혼합
→ 사용자 시청 이력 → Kafka → 추천 엔진
→ 결과를 Redis에 캐싱 (1시간)
🎯 Chapter 14
면접 Q&A + 실무 아키텍처 체크리스트
"시스템 설계 면접에서 절대 틀리면 안 되는 질문들"
💬 시스템 설계 면접 핵심 Q&A
RabbitMQ: 복잡한 라우팅(토픽, 헤더), 빠른 설정, 오래된 생태계, 메시지 우선순위, RPC 패턴. → 작업 큐, 마이크로서비스 RPC, 복잡한 라우팅 필요 시.
✅ 실무 아키텍처 설계 체크리스트
📈 성능 & 확장성
☐ DB Read Replica 설정
☐ Redis 캐시 계층 적용
☐ CDN으로 정적 파일 배포
☐ DB 인덱스 최적화 (Explain 분석)
🛡️ 가용성 & 장애
☐ Retry + Exponential Backoff 설정
☐ Timeout 설정 (모든 I/O 작업)
☐ Health Check 엔드포인트 구현
☐ Graceful Shutdown 처리
📊 관찰가능성
☐ 분산 추적 (TraceID 전파)
☐ Prometheus 메트릭 수집
☐ Grafana 대시보드 구성
☐ 에러율, 지연시간 알람 설정
🔒 보안 & 데이터
☐ 민감 데이터 암호화 (at rest/in transit)
☐ RBAC 권한 체계 설계
☐ 감사 로그 (Audit Log)
☐ 정기 보안 취약점 스캔
BackendDevGuide0013 완성!
대규모 시스템 설계와 분산 아키텍처 완전 정복 A-Z
로드밸런서, 캐싱, DB 샤딩, 메시지큐, MSA, Circuit Breaker,
분산 추적, CAP 이론, 실전 설계 면접까지 — 시니어도 보는 가이드 💪