부하 테스트

검증. SLO를 먼저 정하고 dev에서 stg까지 실측

← HomeLab

기준 사례는 2024년 한국시리즈 극장 생중계 예매 대기 16만 명이고, 목표는 10만 명입니다. 사용자 단계(첫 화면 → 대기열 진입 → 순번 조회 → 입장 → 예매)마다 SLO를 테스트 전에 확정하고, 같은 여정을 k6로 재현했습니다. 테스트마다 먼저 막힌 곳을 찾아 구조를 바꾸며 dev 1만 명에서 stg 5만 명까지 통과했습니다.

SLO, 테스트 전 확정

단계성공 기준과 목표이유
1 첫 화면
frontend
응답 2xx, 99.9%실패하면 뒤 단계에 사용자가 오지 않아 다른 SLO가 통과처럼 보임
2 대기열 진입
queue, Redis, Kafka
진입 요청 성공, 99.9%대기열의 역할이 전원 수용이라 가장 엄격
3 순번 조회
queue, Redis
1초 이내 성공, 99%가장 짧은 갱신 주기가 1초라, 넘으면 대기 화면이 밀림
4 입장 반영
queue → Kafka → booking
입장 후 1초 이내 booking에 반영, 99.9%반영 전 좌석을 누르면 거부(403)인데 요청 지표에는 실패로 잡히지 않음
5 예매 여정
booking → Redis 선점 → MySQL 확정
회차, 좌석, 선점, 확정 각 1초 이내 성공, 99%확정만 세면 앞 단계 실패가 가려짐. 매진 응답(409)은 제외
  • 목표값: 지연 기준 1초는 사람이 지연을 느끼는 선이고, 99.9%는 업계 표준선에서 시작한 초기값
  • 불변식: 정원 초과 0, 처리하지 못한 입장 메시지 0, 5xx 0, 입장 후 거부(403) 0. 하나라도 어기면 무효
  • 판정: k6가 아닌 서버 지표(Mimir) 기준으로, 예매 오픈 1분 전부터 5분 뒤까지. 매번 좌석 4,000, 대기 0에서 시작

결과, stg 1만과 2.5만, 5만 명

 1만 명2.5만 명5만 명
SLO 5개전부 100.000%전부 100.000%순번 조회 99.501%, 나머지 100.000%
불변식 위반000
입장 반영 최대0.251초0.401초0.362초
최대 대기 인원9,00023,95849,000
예매 여정 요청29,51946,20845,468
  • 예매 여정 요청은 대기 인원이 아닌 정원에 비례: 사용자가 5배(1만에서 5만)로 늘어도 1.54배이고, 동시 입장 정원 1,000이 상한
흐름 대시보드 0행. SLO 다섯 100.000%, 정원 초과 0, 처리하지 못한 입장 메시지 0
(판정) 결과 표와 별개로 다시 돌린 2.5만 명 실행의 SLO 5개와 불변식.
흐름 대시보드 2행. 순번 조회와 대기열 진입 지연 p99, 대기와 입장, 정원 인원
(queue) 정원 1,000명을 유지하며 대기 2만 4천 명이 약 6분 뒤 0, 순번 조회 p99는 5ms 이하.
흐름 대시보드 4행. booking 단계별 p99와 확정 성공, 좌석 선점 409 초당 건수
(booking) 단계별 p99 250ms 이하. 좌석이 줄자 확정 대신 이미 선점된 좌석 응답(409)으로 바뀌고 5xx는 없음.
Tempo trace. booking POST /api/bookings에서 Kafka bookings-completed 발행, queue-go 소비와 Redis 명령까지 서비스 2개, 21 span, 250ms
(trace) 부하 중 예매 한 건의 trace. 서비스 2개, 21 span, 250ms.

병목이 옮겨 간 순서

규모와 증상원인조치 → 결과
dev 3만 명에서 k3s 재시작사전 계산이 지목한 Redis는 CPU 44%였고, Traefik 동시 연결이 1만 명 458에서 3만 명 22,866으로 늘어 노드 메모리가 고갈노드 RAM 8GB 3대가 한계라 부하 측정을 stg(EKS)로 이전
stg 1만 명에서 예매 오픈 순간 입장 반영 최대 4.59초새로 뜬 booking의 JVM 컴파일이 CPU를 117초 점유해, 같은 노드 Kafka 브로커는 CPU 경합 시 배분이 7%booking 전용 노드그룹으로 브로커 CPU 보장 → 0.251초
stg 2.5만 명에서 대기열 진입 1초 초과 10,197건노드를 4대에서 3대로 줄여 queue 파드 2대가 한 노드에 몰림. CPU 차례를 기다리는 것이고 Redis는 여유노드 4대에 파드 1대씩 분산 강제 → 0건
stg 3만 명에서 k6 connection reset 다발부하 발생기 EC2 1대의 초당 패킷 한도. 초과 패킷을 오류 없이 폐기하고 서버는 여유발생기 4대에 12,500명씩 분산
stg 5만 명은 SLO 통과했으나 대기열 진입 1-2.5초 13,641건요청은 4대에 같게 분배되는데 연결 수는 2대에 최대 27배라, 그 2대만 Redis 연결 풀에서 대기미해결. 응답이 늦은 파드에 ALB 연결이 더 쌓이는 순환으로 추정

10만 명으로 가려면

  • 사용자 수만큼 늘어나는 대기 요청은 queue와 Redis가 처리: 먼저 한계에 닿은 곳은 Redis 서버가 아니라 queue 파드당 동시 요청이었다(사전 계산은 두 번 다 Redis를 지목했고, 실측은 두 번 다 동시 연결). 여유가 가장 적은 SLO는 순번 조회(5만 명 99.501%, 목표 99%)
  • queue 파드 수는 8대: 5만 명 실측(파드 4대, 파드당 약 12,500명, SLO 통과)의 파드당 인원을 10만 명에 적용. 여유분은 넣지 않음
  • 파드 간 분산 방법은 별도 과제: 5만 명처럼 연결이 일부 파드에 몰리면 파드를 늘려도 같은 병목이다. 조정 대상은 ALB 분산 방식(처리 중 요청이 적은 파드 우선)과 파드당 동시 요청 상한이고, 확인은 ALB 접근 로그로
  • 입장과 좌석 계산: 정원 1,000에 초당 최대 100명 입장이면 10만 명 전원 입장에 약 16분. 좌석 4,000은 실측상 입장 시작 약 80초에 소진되므로, 약 9만 6천 명은 성능이 아니라 제품 정책의 영역

한계

  • 실측 상한 5만 명: 5만 명에서 파드당 부하와 다음 과제(파드 간 분산)를 확인한 뒤 AWS 크레딧 소진으로 종료. 10만 명은 계산으로 대체했고 분산 방법과 Redis 주 노드의 10만 명 부하는 확인하지 못함
  • 장시간(soak) 테스트 없음: stg는 3일간 가동해 검증한 뒤 정리했고, 메모리와 커넥션 누수는 확인하지 못함

기술 스택

k6, Terraform(부하 발생기), Grafana, Mimir, Loki, Tempo