qa/load/k6/에는 단발 회귀 시나리오 4종(facility-search·booking-create-throughput·ticket-seat-select-spike·cart-add-item-concurrency)과 공통 헬퍼(lib/auth.js·lib/metrics.js), 멱등 시드 SQL(seeds/cart-add-item-concurrency.sql)이 있다. 이들은 CI·수동 트리거로 단발 실행되는 회귀 도구다(qa/load/README.md — “측정값은 회귀 추세 추적용, 절대치 비교 금지”). 상시(24시간)·일주기·온오프·상태 조회가 가능한 시스템은 없다. docker-compose.yml에는 backend·LB가 없다(⑧가 편입).
Overview
항목
내용
무엇을
prod 대상 24시간 상시 일주기 트래픽 발생기(k6 ramping-arrival-rate 곡선) + 10분 주기 소진성 자원 재충전(reseed) 배치 + backend 다중 인스턴스(3) + nginx LB + 상태 조회(⑤ Grafana) + 온오프 제어
왜
”일회성 부하 테스트”가 아니라 “실제 운영처럼 24시간 흐르는 상시 트래픽”이 없다. 소진성 자원(슬롯·재고·좌석) 고갈로 상시 트래픽이 붕괴되고, 다중 인스턴스+LB 인프라가 없어 고부하 흡수 전제가 없다
어떻게
애플리케이션 도메인 코드·DB 스키마 변경 0. 전부 qa/load/ 부하 자산 + ⑦ 전용 compose 오버레이(docker-compose.lb.yml·docker-compose.sim.yml) + nginx LB 설정 + reseed 컨테이너(멱등 synthetic SQL/Redis 복원). base·dev/prod override(⑧)와 observability(⑤)는 미수정, 추가 -f로만 얹는다
도메인 영향
없음 — QA/부하·인프라 계층 작업. Kotlin 코드·Flyway 마이그레이션·Kafka 토픽·Redis 앱 키 신규 0
Terminology
용어
정의
일주기 곡선(diurnal curve)
24시간을 시간대별 목표 TPS 배율(평시 저부하→점심·저녁 피크)로 표현한 트래픽 강도 곡선
ramping-arrival-rate
k6 executor — 구간별 목표 도착률(iterations/sec)을 지정해 서버 부하를 웜업·피크·감쇠시키는 open model executor
arrival rate
초당 요청(iteration) 발생률. TPS와 등가로 취급
reseed(재충전)
소진된 synthetic 자원(슬롯·재고·좌석)을 baseline으로 복원하는 10분 주기 배치
synthetic 자원
부하 전용으로 격리된 데이터(더미 owner_id·상품·시설·이벤트·유저 풀). 실사용자 데이터와 ID 네임스페이스로 구분
유저 풀(user pool)
사전 준비된 synthetic 유저·토큰 집합. 대량 동시 인증이 병목이 되지 않게 재사용
추종 오차
목표 TPS 대비 실제 도달 TPS의 편차. 10분 버킷 단위로 집계(±10% 이내 버킷 90%)
격차 리포트(gap report)
목표 미달 시 달성률(%)과 병목 추정(클라이언트 k6 한계 vs 서버 한계)을 담은 종료 리포트
시간 축약(time-scale)
24시간 곡선을 검증용으로 비율 압축(기존 스크립트 1/5 관례) — 곡선 형상 보존
Define Problem
AS-IS
실제 코드(/Users/biuea/sports-application)를 읽어 확인.
요소
현재 상태
근거
부하 시나리오
단발 실행 4종. 고정 부하·단순 스파이크만 표현
qa/load/k6/*.js
곡선 표현
stages로 선형 ramp만. 24시간 일주기 곡선 없음
booking-create-throughput.js:22-27 등
공통 헬퍼
assertSafeTarget(로컬 외 차단), issueToken(setup 1회 발급·전 VU 공유), headerAuth(userId)(X-User-Id)
미채택(부분 보조) — ① 가산 restore는 반복 호출 시 재고 무한 증가(드리프트) ② GOODS_SELLER 토큰 필요·개별 booking ID 추적 필요 ③ 관측 대상 앱에 reseed 요청 부하가 실려 측정 왜곡(NFR 위반). 단 SQL이 부적합한 극단(도메인 불변식 필요)에서만 폴백
C. 앱에 admin reseed API 신설
POST /admin/load/reseed 등 신규 컨트롤러
미채택 — 애플리케이션 코드·엔드포인트 신설(무변경 원칙 위반), prod 앱에 부하 전용 경로 상주. 지금 규모에 과함
reseed는 도메인의 복원 의미(restore semantics) 를 그대로 활용하되(재고 quantity·슬롯 가용·좌석 TTL), 실행은 synthetic 시드행에 대한 멱등 SQL로 한다 — “기존 도메인 메서드 활용”과 “관측 왜곡 금지” NFR을 동시에 만족.
방안 비교 — backend 다중 인스턴스 + LB (ADR-003)
방안
설명
왜 채택 / 미채택
A. docker-compose.lb.yml 오버레이 — backend replicas:3 + nginx LB(신규 lb.conf)
⑦ 전용 파일에서 backend를 deploy.replicas:3(내부 8080만)으로 override, nginx LB가 80/443 노출. upstream sports_backend를 Docker DNS backend:8080로 재정의(resolver + 변수 proxy_pass로 replica 재해석)
채택 — ⑧ 경계표가 ⑦에 부여한 파일. base·override 미수정, 추가 -f로 병합. sports_backend upstream 이름 재사용(mcp.conf 무수정, 신규 lb.conf)
B. mcp.conf의 sports_backend를 in-place로 다중 서버로 수정
기존 파일에 server backend-1..3 추가
미채택 — mcp.conf는 MCP 전용·TLS placeholder·127.0.0.1:8080(컨테이너 내 미해석). in-place 수정은 MCP 동작 변경 + Single Writer 충돌. 신규 lb.conf가 안전
C. compose --scale backend=3 CLI만
파일 없이 실행 시 스케일
미채택 — 선언적이지 않아 재현성 낮음, nginx upstream이 replica를 못 봄(정적 서버 목록)
방안 비교 — 상태 조회·제어 수단 (FR-6/FR-7, ADR-004)
방안
설명
왜 채택 / 미채택
A. ⑤ Grafana(k6 remote-write) + 구조화 로그 + compose lifecycle 제어
실제 도달 TPS·활성 시나리오·거부율을 k6→Prometheus→Grafana로. 목표 곡선은 참조선. 제어는 make sim-up/down/pause(graceful ramp-to-0)
미채택 — 앱/신규 서버 코드 유발. FR-6은 조회 수단만 요구, Grafana로 충분(단순함 우선)
Detail Design
도메인 바운디드 컨텍스트 경계 판단 (의무)
결정: 신규 도메인 분리 없음, 기존 도메인 합류도 없음. 이 과제는 부하·QA·인프라 계층 작업으로 애플리케이션 도메인 코드(Kotlin)·DB 스키마·Kafka 토픽·Redis 앱 키를 만들거나 바꾸지 않는다.
산출물은 전부 부하/인프라 자산: qa/load/k6/**(스크립트·라이브러리), qa/load/reseed/**(멱등 SQL·스크립트), qa/load/provision/**(synthetic 시드), docker-compose.lb.yml·docker-compose.sim.yml, infra/nginx/lb.conf, Grafana 대시보드 JSON, 제어 스크립트.
미채택(신규 도메인 생성) 사유: 트래픽 시뮬레이터는 라이프사이클·데이터 소유가 없는 외부 부하 발생기다. 도메인 경계 대상이 아니며, 대상 도메인(booking/goods/ticketing/②partner/③limited-drop)은 그 엔드포인트를 외부에서 호출당할 뿐 이 과제가 그 코드를 소유·변경하지 않는다.
도메인 간 참조: 시뮬레이터는 대상 앱과 HTTP(k6) 및 synthetic DB/Redis(reseed)로만 접점. 앱 내부 코드 결합 0.
서버 토폴로지 설계 (의무)
과제 특성 = “외부 부하 발생(비동기 지속) + 주기 배치 + 대상 앱 수평 확장”. 요청-응답 처리 서버를 새로 만드는 게 아니라, 부하를 생성·격리·분산한다.
flowchart LR
subgraph Sim["시뮬레이터 컨테이너 (격리)"]
K6["k6-runner (곡선/스파이크)"]
RS["reseed (10분 배치)"]
end
subgraph LB["nginx-lb"]
NG["upstream sports_backend"]
end
subgraph App["backend replicas x3"]
B1["backend-1"]
B2["backend-2"]
B3["backend-3"]
end
subgraph Data["synthetic 자원"]
DB["MySQL/Redis"]
end
subgraph Obs["⑤ 관측"]
PROM["Prometheus"]
GRAF["Grafana 상태 대시보드"]
end
K6 -->|HTTP 부하| NG
NG --> B1
NG --> B2
NG --> B3
B1 --> DB
RS -->|멱등 복원| DB
K6 -->|remote-write| PROM
PROM --> GRAF
Sequence Diagram — 일주기 곡선 + reseed + 스파이크
sequenceDiagram
participant Op as 운영자
participant K6 as k6-runner
participant LB as nginx-lb
participant BE as backend replicas
participant RS as reseed
participant GR as Grafana(⑤)
Op->>K6: sim-up (곡선 시작)
loop 시간대별 목표 TPS
K6->>LB: arrival-rate 부하
LB->>BE: 라운드로빈 분산
BE-->>K6: 200/409(자원 소진 시)
K6->>GR: 도달 TPS remote-write
end
loop 10분 주기
RS->>BE: (synthetic DB 직접 복원)
end
Op->>K6: 예약 스파이크 시각
K6->>LB: 0→20000 TPS (±30초)
K6->>GR: 달성률·병목 gap report
ERD
해당 없음 — 이 과제는 DB 스키마·컬렉션을 신규·변경하지 않는다(Flyway 마이그레이션 0건). reseed는 기존 테이블의 synthetic 시드행을 멱등 복원할 뿐 스키마를 만들지 않는다. 선행 인프라 DB 작업 없음.
Testing Plan
부하/인프라 자산이므로 Kotest 앱 테스트 대신 정적 검증 + 구동 검증 + 부하 실측이 테스트다.
레벨
대상
검증 방법
정적
k6 스크립트
k6 inspect <script>로 시나리오·stage 파싱 유효(exit 0). 배율표→stage 확장 단위 검증(합계·피크 시점)
reseed가 실사용자 데이터를 건드리지 않음(synthetic 범위 밖 UPDATE 0건) 검증.
Release Scenario — 무중단 배포 (의무)
이 과제는 전부 additive 오버레이(신규 부하 자산 + -f 오버레이 compose + 신규 nginx conf + 별도 컨테이너)다. 애플리케이션·base·override(⑧)·observability(⑤)를 수정하지 않으므로 기존 서비스는 무영향. LB 도입만 트래픽 경로 전환을 수반한다.
배포 순서 (오버레이 = 피처 플래그 등가)
Expand(부하 자산 추가) — qa/load/k6/lib/**·스크립트·reseed/**·provision/**를 dev에 머지. 기존 4종 회귀 스크립트·seeds/*.sql 무변경. -f로 얹기 전엔 아무 컨테이너도 안 뜸 → 기존 흐름 무영향.
provision(synthetic 준비) — provision.sh로 synthetic 유저 풀·B2B 파트너 키·마케팅 drop 사전 발급(멱등). 실데이터 미접촉.
LB 오버레이 전환(무중단) — docker-compose.lb.yml을 -f로 추가해 replicas:3 + nginx-lb 기동. 전환 전 backend 직접 노출과 병존 가능(expand) → nginx 헬스 확인 후 진입점을 nginx로 전환 → 구 직접 노출 포트 제거(contract).
marketing-spike.js → ③ POST /limited-drops/{dropId}/orders
③ 의존: limited-drop 엔드포인트·대량 수량 synthetic drop. 20000TPS·오버셀0은 ③ 소관, ⑦는 부하 인가·도달 TPS 기록
② B2B 파트너
b2b-diurnal.js → ② Bearer partner_.. → /api/goods-seller/products·/api/event-host/events
② 의존: partner 인증·POST /api/admin/partners(synthetic 파트너 키 발급). ⑦는 그 키로 100TPS·1000건/일 부하
⑥ 지능형 알림
목표 50% 미만 지속 통지, source=simulator 태깅
⑥ 소관(알림 규칙·채널). ⑦는 지표·태깅 소스 제공
① 도메인 경계
대상 도메인 엔드포인트 확인
직접 의존 없음(HTTP 외부 호출)
Open Questions
20000TPS 물리 도달성(PRD Open Q): 개인 머신 단일 러너로 20000TPS는 미달 가능. 목표는 유지하되(Success Metric) handleSummary가 도달 TPS·병목(client vs server)을 기록(FR-8). 확장은 distributed k6/클라우드 러너로 M4 중 재검토(벤치 k6-operator).
synthetic 데이터 정리 주기(PRD Open Q): reseed가 10분마다 synthetic 쓰기 결과(예약·주문·좌석)를 정리하되, 장기 누적분은 일 1회 심야(배율 최저 구간) 전체 정리 배치를 reseed 컨테이너 cron에 추가. ID 네임스페이스(900000+·owner_id=1·loadtest.local)로 실데이터와 구분.
prod 스택 물리 분리(PRD Open Q): ⑧ 결정에 따름(-p sports-prod 네임스페이스). ⑦는 prod 스택 위에 오버레이.
⑤ remote-write receiver 활성: ⑤ Prometheus 기동 플래그 조율 필요(⑦가 ⑤ 파일 미수정). 미활성 시 임시로 k6 로컬 summary + 별도 경량 output로 폴백.
Document History
날짜
변경 내용
2026-07-03
최초 작성 — AS-IS 실측(k6 4종·seeds 관례·소진성 자원 복원 메서드·nginx upstream), 일주기 곡선(ramping-arrival-rate 배율표), reseed(멱등 synthetic SQL·앱 부하 0), backend replicas+nginx LB(lb.yml·lb.conf), 상태=⑤ Grafana, compose 소유 경계(⑤⑧), 무중단 오버레이 배포, ②③⑤⑧⑥ 접점, ADR 4건·티켓 9건