🎯 타임딜 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 connectionLimit1050
MySQL queueLimit0 (무한)100 (빠른 실패)
HTTP server backlog5111024
keepAliveTimeout5s65s

3. 테스트 결과

k6 시나리오

stages: [
  { duration: '30s', target: 50  },   // 워밍업
  { duration: '60s', target: 200 },   // 정상 부하
  { duration: '30s', target: 500 },   // 극한 부하
  { duration: '30s', target: 0   },   // 복구 확인
]

결과 비교

지표단일 EC23대 + ALB
총 요청64,55164,258
주문 성공1,000 (1.5%)1,000 (1.6%)
재고 소진 40957,579 (89.2%)57,269 (89.1%)
서버 에러5,020 (7.8%)4,964 (7.7%)
타임아웃952 (1.5%)1,025 (1.6%)
p95 응답시간65ms66ms

가설 검증 결과

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 (현재)~$130DB 공유라서 실질 효과 제한
3대 + RDS + ElastiCache~$250진짜 수평 확장 가능

예상 처리량 변화

단계구성예상 RPS
Phase 1EC2 1대~430 RPS
Phase 2 (현재)EC2 3대 + ALB, DB 공유~430 RPS (동일)
Phase 3EC2 3대 + RDS + ElastiCache1,2001,500 RPS

다음 실험 방향

재고를 999,999개로 설정하면 모든 요청이 MySQL INSERT까지 도달한다. 이 상태에서 단일 EC2 vs 3대+ALB를 비교하면 진짜 App 서버 분산 효과를 측정 가능. 단, DB가 EC2-1에 있는 한 EC2-1 CPU가 새 병목이 될 것으로 예상.


7. 오늘 얻은 인사이트

  1. Crash vs Throttle 구분: 에러가 나도 컨테이너가 살아있으면 설정 문제
  2. 재고 정합성 완벽: Redis DECR 원자성으로 500 VU에서도 초과 판매 0건
  3. 타임딜의 특성: 재고 소진 후 98%는 Redis에서 처리 → DB 부하는 재고 수만큼만
  4. 수평 확장의 전제: App 서버 확장 전에 DB 분리가 먼저
  5. 병목은 항상 이동한다: 한 곳을 고치면 다음 병목이 드러남
connectionLimit 10 → DB 커넥션 고갈
        ↓ 해결
Node.js TCP 연결 한계
        ↓ 확인
EC2 3대로 분산
        ↓ 효과 없음
DB 단일 지점이 진짜 병목
        ↓ 다음 과제
RDS + ElastiCache 분리