🎯 타임딜 EC2 부하테스트 분석
📑 목차
1. 실험 배경
목표
“EC2 단일 인스턴스의 한계를 직접 경험하고, 수평 확장이 실제로 효과가 있는지 검증한다.”
- Terraform으로 EC2 + Docker Compose 구성
- Node.js Express 백엔드 (MySQL + Redis 연결)
- k6로 최대 500 VU 부하 테스트
2. 인프라 구성
Phase 1 - 단일 EC2
[k6] → EC2 t3.medium
└── Docker Compose
├── Node.js (8080)
├── MySQL 8.0
└── Redis 7
Phase 2 - 3대 + ALB
[k6] → ALB
├── EC2-1: App + MySQL + Redis ← 공유 DB
├── EC2-2: App only → 10.0.1.14
└── EC2-3: App only → 10.0.1.14
핵심 튜닝 (Phase 1에서 발견):
| 항목 | 전 | 후 |
|---|---|---|
| MySQL connectionLimit | 10 | 50 |
| MySQL queueLimit | 0 (무한) | 100 (빠른 실패) |
| HTTP server backlog | 511 | 1024 |
| keepAliveTimeout | 5s | 65s |
3. 테스트 결과
k6 시나리오
stages: [
{ duration: '30s', target: 50 }, // 워밍업
{ duration: '60s', target: 200 }, // 정상 부하
{ duration: '30s', target: 500 }, // 극한 부하
{ duration: '30s', target: 0 }, // 복구 확인
]결과 비교
| 지표 | 단일 EC2 | 3대 + ALB |
|---|---|---|
| 총 요청 | 64,551 | 64,258 |
| 주문 성공 | 1,000 (1.5%) | 1,000 (1.6%) |
| 재고 소진 409 | 57,579 (89.2%) | 57,269 (89.1%) |
| 서버 에러 | 5,020 (7.8%) | 4,964 (7.7%) |
| 타임아웃 | 952 (1.5%) | 1,025 (1.6%) |
| p95 응답시간 | 65ms | 66ms |
가설 검증 결과
3대 + ALB 구성이 단일 EC2와 거의 동일한 결과를 냈다. 처리량 3배 증가 가설은 이 시나리오에서 검증되지 않았다.
4. 왜 3대인데 차이가 없었나
요청 흐름 분석
재고: 1,000개
총 요청: 64,258건
[요청 분류]
1,000건 → Redis DECR → MySQL INSERT (무거운 처리)
63,258건 → Redis DECR → 음수 → Redis INCR → 409 반환 (초경량)
실질적으로 MySQL에 부하를 주는 요청: 1.6%
나머지 98.4%: Redis 조회 1~2회로 종료
핵심 인사이트
타임딜의 특성상 재고 소진 후 대부분의 요청은 Redis에서 즉시 거절된다. App 서버를 아무리 늘려도 Redis와 MySQL이 단일 지점이면 처리량은 늘지 않는다.
에러 7.7%의 원인
connection reset by peer - App 서버가 죽은 게 아니라 “튕긴” 것.
- Crash (죽음): 컨테이너 종료, OOM → 수동 재시작 필요
- Throttle (튕김): TCP 연결 수용 한계 초과 → 부하 감소 시 자동 복구
에러는 초반 폭주 구간(200500 VU) 1020초에 집중 발생.
재고 소진 이후 구간은 Redis 빠른 처리로 에러 거의 없음.
5. DB 분리 - 핵심 개념
현재 구조의 문제
EC2-1: [App] + [MySQL] + [Redis] ← 3역할 동시 수행
EC2-2: [App] ──────────────────→ EC2-1 DB 호출
EC2-3: [App] ──────────────────→ EC2-1 DB 호출
App 서버가 3대여도 DB는 EC2-1 1대
→ DB가 새로운 병목
음식점 비유
주방장(DB)이 1명인데 홀 직원(App)을 3명으로 늘린 상황. 손님이 주문을 3배 빠르게 받아도 음식 나오는 속도는 그대로. 병목은 주방장 1명이다.
DB 분리가 필요한 이유
현재 (DB 미분리)
요청 1000건이 MySQL에 도달
→ EC2-1의 CPU: App + MySQL + Redis 세 가지 동시 처리
→ EC2-1이 과부하되면 DB까지 느려짐
→ App이 3대여도 의미 없음
DB 분리 후
요청 1000건이 MySQL에 도달
→ RDS 전용 서버: MySQL만 처리 (CPU 100% DB에 집중)
→ EC2 App 서버들: 비즈니스 로직만 처리
→ 각자 하는 일이 분명해짐
Redis 역할 재확인
Redis 없이 주문 요청:
1. DB SELECT - 재고 조회
2. DB UPDATE - 재고 차감
3. DB INSERT - 주문 생성
→ DB 3번 접근, 동시성 문제 발생 가능
Redis 있을 때 주문 요청:
1. Redis DECR - 원자적 재고 차감 (1ms 이하)
2. (재고 있으면) DB INSERT만
→ DB 1번만 접근, 동시성 문제 없음
재고 소진 후:
1. Redis DECR → 음수 → Redis INCR → 즉시 409
→ DB 접근 0번
Redis는 "충격 흡수재"
수만 명의 동시 요청이 DB까지 가지 않도록 앞에서 걸러준다. 재고 있는 소수만 DB로 통과시킨다.
6. Phase 2 아키텍처
목표 구성
[k6 / 실제 유저]
↓
[ALB]
┌────┼────┐
App App App ← EC2 t3.medium x3 (App만)
└────┼────┘
↓
┌─────┴─────┐
RDS ElastiCache
(MySQL) (Redis)
전용 서버 전용 서버
비용 비교
| 구성 | 월 비용 | 특징 |
|---|---|---|
| 단일 EC2 | ~$38 | 테스트 가능, 실서비스 불가 |
| 3대 + ALB (현재) | ~$130 | DB 공유라서 실질 효과 제한 |
| 3대 + RDS + ElastiCache | ~$250 | 진짜 수평 확장 가능 |
예상 처리량 변화
| 단계 | 구성 | 예상 RPS |
|---|---|---|
| Phase 1 | EC2 1대 | ~430 RPS |
| Phase 2 (현재) | EC2 3대 + ALB, DB 공유 | ~430 RPS (동일) |
| Phase 3 | EC2 3대 + RDS + ElastiCache |
다음 실험 방향
재고를 999,999개로 설정하면 모든 요청이 MySQL INSERT까지 도달한다. 이 상태에서 단일 EC2 vs 3대+ALB를 비교하면 진짜 App 서버 분산 효과를 측정 가능. 단, DB가 EC2-1에 있는 한 EC2-1 CPU가 새 병목이 될 것으로 예상.
7. 오늘 얻은 인사이트
- Crash vs Throttle 구분: 에러가 나도 컨테이너가 살아있으면 설정 문제
- 재고 정합성 완벽: Redis DECR 원자성으로 500 VU에서도 초과 판매 0건
- 타임딜의 특성: 재고 소진 후 98%는 Redis에서 처리 → DB 부하는 재고 수만큼만
- 수평 확장의 전제: App 서버 확장 전에 DB 분리가 먼저
- 병목은 항상 이동한다: 한 곳을 고치면 다음 병목이 드러남
connectionLimit 10 → DB 커넥션 고갈
↓ 해결
Node.js TCP 연결 한계
↓ 확인
EC2 3대로 분산
↓ 효과 없음
DB 단일 지점이 진짜 병목
↓ 다음 과제
RDS + ElastiCache 분리