개념
목표 숫자가 없으면 성능 테스트는 끝나지 않는다
레슨 1에서 부하의 모양을 만들었다. 이제 남은 질문은 "그래서 얼마나 빨라야 통과인가" 다. 이 질문에 답이 없으면 성능 테스트는 두 가지 방식으로 실패한다. 영원히 "좀 더 빨라야 할 것 같다"로 끝나거나, 아무나 "이 정도면 됐다"고 말하는 순간 끝난다.
현장에서 가장 자주 받는 요구사항은 이런 모양이다.
성능 요구사항
- 주요 화면은 3초 이내에 응답할 것
- 동시 접속자 1,000명을 지원할 것이 두 줄에는 판정에 필요한 정보가 하나도 없다. 3초는 평균인가 최댓값인가 p95인가. 어떤 부하에서인가. 어디서 잰 값인가(브라우저인가 로드밸런서인가 애플리케이션 로그인가). "동시 접속자 1,000명"은 세션이 1,000개라는 뜻인가, 초당 1,000건이 들어온다는 뜻인가, 서버 안에 요청 1,000건이 동시에 떠 있다는 뜻인가. 셋은 전혀 다른 숫자이고, 그 셋을 연결하는 것이 이 레슨의 핵심 도구다.
리틀의 법칙 — 세 숫자를 잇는 다리
리틀의 법칙(Little's Law) 은 대기 시스템에서 성립하는 단순한 항등식이다.
L = λ × W
L (concurrency) 시스템 안에 동시에 존재하는 항목의 평균 개수
λ (arrival rate) 단위 시간당 도착(=처리)하는 항목 수
W (residence) 항목 하나가 시스템 안에 머무는 평균 시간이 식이 성능 테스트에서 강력한 이유는 세 숫자 중 둘을 알면 나머지 하나가 결정된다 는 점이다. 그리고 "시스템"의 경계를 어디로 잡느냐에 따라 같은 식이 다른 질문에 답한다.
| 시스템 경계 | L | λ | W | 이 계산으로 답하는 질문 |
|---|---|---|---|---|
| 서버 전체 | 처리 중인 요청 수 | 초당 요청 수 | 응답 시간 | 스레드 풀·워커 수를 얼마로 잡나 |
| 사용자 세션 포함 | 동시 사용자 수 | 초당 시나리오 수 | 응답 + think time | 부하 도구의 VU를 몇으로 잡나 |
| DB 커넥션 풀 | 사용 중 커넥션 수 | 초당 쿼리 수 | 쿼리 소요 시간 | 커넥션 풀 크기를 얼마로 잡나 |
세 번째 열의 W에 think time이 들어가느냐 아니냐가 "동시 접속자" 혼동의 원인이다. 화면을 보며 5초를 쉬는 사용자도 세션은 살아 있으므로 "동시 사용자"에는 포함되지만, 서버 안에 요청은 없다. 둘을 같은 말로 쓰면 서버를 10배 과잉 산정하게 된다.
세 가지 "동시"를 분리한다
용량 산정 회의에서 반드시 먼저 정리해야 할 표다.
| 용어 | 정의 | 위 커머스 예시 값 |
|---|---|---|
| 동시 접속자(concurrent users) | 세션이 살아 있는 사용자 수 | 약 4,100명 |
| 동시 요청(concurrent requests) | 서버가 지금 처리 중인 요청 수 | 5.7건 |
| 도착률(throughput) | 초당 처리 요청 수 | 19 rps |
레슨 1의 커머스 워크로드로 숫자를 채워 보자. 피크 도착률 19 rps, 평균 응답 시간 0.30초, 요청 사이 think time 중앙값 약 4.5초다.
[서버 안의 동시 요청]
L = λ × W = 19 rps × 0.30 s = 5.7 건
[동시 사용자(생각하는 시간 포함)]
W' = 응답 0.30 s + think 4.5 s = 4.8 s
L' = 19 × 4.8 = 91.2 명
[세션 기준 동시 접속자]
세션 유지 시간 = 15분(세션 타임아웃), 세션 시작률 = 4.6 세션/초
L'' = 4.6 × 900 s = 4,140 명같은 트래픽이 5.7, 91, 4,140 세 숫자로 표현된다. "동시 접속자 1,000명을 지원하라"는 요구사항을 받았을 때 가장 먼저 할 일은 이 셋 중 무엇인지를 되묻는 것이다. 되묻지 않으면 십중팔구 세션 수를 서버 스레드 수로 오해해 스레드 800개짜리 설정이 나오고, 그 설정이 오히려 컨텍스트 스위칭으로 성능을 떨어뜨린다.
부하 도구의 VU 수도 같은 식으로 정한다
레슨 1에서 "VU는 부하의 단위가 아니라 rps를 내기 위한 수단"이라고 했다. 그 수단의 크기를 정하는 것도 리틀의 법칙이다.
필요 VU = 목표 rps × (평균 응답 시간 + think time)
= 19 × (0.30 + 4.5) = 92 VU
스파이크 시나리오(121 rps, think time 없는 API 연속 호출, 응답 0.5s 가정)
= 121 × 0.5 = 61 VU → 응답이 2초로 느려지면 242 VU 필요두 번째 줄이 k6에서 preAllocatedVUs · maxVUs 를 정하는 근거다. 응답이 느려지면 같은 rps를 유지하는 데 더 많은 VU가 필요하다. 이 여유를 안 두면 dropped_iterations 가 발생해 목표 부하를 주지 못한 채 "통과"가 찍힌다.
이용률 법칙 — 서버 대수를 계산한다
리틀의 법칙과 짝을 이루는 것이 이용률 법칙(Utilization Law) 이다.
U = X × S
U 자원 이용률 (0~1)
X 처리량 (초당 요청 수)
S 요청 1건이 그 자원을 점유하는 시간 (service demand)CPU를 예로 들자. APM이나 프로파일러에서 요청 1건당 CPU 시간이 40 ms로 측정됐다면, 19 rps에서 필요한 CPU는 이렇게 나온다.
U = 19 × 0.040 = 0.76 코어
4 vCPU 인스턴스 1대 기준 이용률 = 0.76 / 4 = 19 %여기서 곧바로 "1대면 충분"이라고 결론 내면 안 된다. 대기 이론상 이용률이 높아질수록 대기 시간이 비선형으로 늘어난다. 단일 대기열 근사(M/M/1)에서 대기 시간은 대략 S / (1 - U) 로 늘어나므로, 이용률 50%에서 2배, 80%에서 5배, 90%에서 10배가 된다. 그래서 실무 산정은 피크에서도 목표 이용률(보통 60~70%)을 넘지 않도록 잡는다.
[인스턴스 1대의 처리 용량]
가용 CPU = 4 코어 × 목표 이용률 0.60 = 2.4 코어-초/초
용량 = 2.4 / 0.040 = 60 rps
[필요 대수]
일상 피크 19 rps → 19 / 60 = 0.32 대
1분 스파이크 121 rps → 121 / 60 = 2.02 대
장애 허용(N+1) → 2.02 → 3대 (1대가 죽어도 2대로 121 rps 감당 가능한지 재확인:
2 × 60 = 120 rps ≒ 121 → 빠듯하므로 4대로 결정)
연 성장률 30% 반영 → 121 × 1.3 = 157 rps → 157 / 60 = 2.6 → N+1 적용 4대
결론: 4 vCPU 인스턴스 4대 (또는 오토스케일 최소 2 / 최대 6)이 계산의 값어치는 정확도가 아니다. 어떤 가정을 바꾸면 결론이 어떻게 바뀌는지가 한눈에 보인다 는 점이다. "요청당 CPU 40 ms"를 튜닝으로 25 ms까지 줄이면 1대 용량이 96 rps가 되고 필요 대수가 2대로 줄어든다. 성능 개선의 비용 효과를 이 표로 설명할 수 있다.
자원별 산정 — CPU만이 병목은 아니다
같은 이용률 법칙을 자원마다 적용한다. 요청 1건의 시간 예산을 계층별로 쪼갠 서비스 수요 프로파일 을 만든다.
| 자원 | 요청 1건당 점유 | 121 rps에서의 필요량 | 현 설정 | 판정 |
|---|---|---|---|---|
| 앱 CPU | 40 ms | 4.84 코어 | 4대 × 4 = 16 코어 | 이용률 30%, 여유 |
| 앱 스레드 | 응답 350 ms | 121 × 0.35 = 42.4 개 | 4대 × 16 = 64 | 여유 |
| DB 커넥션 | 쿼리 12 ms | 121 × 0.012 = 1.45 개 | 풀 20 | 여유 |
| DB CPU | 8 ms | 0.97 코어 | 8 코어 | 여유 |
| 외부 결제 API | 420 ms (주문 1.8%만) | 2.2 × 0.42 = 0.92 동시 | 동시 호출 제한 5 | 빠듯 |
| 네트워크 | 응답 본문 평균 18 KB | 121 × 18 KB = 2.2 MB/s | 1 Gbps | 여유 |
DB 커넥션 계산이 직관을 배반하는 대표 사례다. 121 rps인데 커넥션이 1.45개면 되느냐고 물으면 대부분 놀란다. 평균으로는 맞다. 문제는 분산이다. 쿼리가 평소 12 ms지만 p99가 300 ms라면 그 순간 필요한 커넥션은 121 × 0.3 = 36개로 뛴다. 그래서 커넥션 풀은 평균이 아니라 꼬리 시간으로 산정하고, 동시에 풀 크기를 무한정 키우지 않는다 — 풀이 DB의 CPU 코어 수를 크게 넘어가면 DB 안에서 경합만 늘어난다.
외부 결제 API 줄도 눈여겨본다. 비율로는 1.8%뿐이지만 응답이 420 ms라 동시 점유가 크고, 게다가 우리가 튜닝할 수 없는 구간 이다. 이런 항목은 용량 산정이 아니라 타임아웃·서킷브레이커·큐잉 설계의 대상이 된다.
응답 시간 예산을 쪼갠다
목표 응답 시간도 계층별로 나눠야 판정과 개선이 가능하다. "p95 800 ms"를 아래처럼 쪼갠다.
p95 800 ms 예산 배분 — 상품 상세 화면
┌──────────────────────────────┬────────┬──────────┐
│ 구간 │ 예산 │ 측정 지점 │
├──────────────────────────────┼────────┼──────────┤
│ CDN/네트워크 왕복 │ 120 ms │ 합성 모니터링 │
│ 로드밸런서 → 앱 │ 10 ms │ LB 액세스 로그 │
│ 앱 처리(직렬 로직) │ 180 ms │ APM 스팬 │
│ DB 조회 3건 │ 240 ms │ APM DB 스팬 │
│ 캐시 조회 2건 │ 30 ms │ APM 스팬 │
│ 추천 서비스 호출(병렬) │ 150 ms │ APM 외부 스팬 │
│ 직렬화·응답 전송 │ 70 ms │ 앱 로그 │
├──────────────────────────────┼────────┼──────────┤
│ 합계 │ 800 ms │ │
└──────────────────────────────┴────────┴──────────┘이 표가 있으면 병목 분석(레슨 3)이 "어디가 느린가"에서 "어느 칸이 예산을 초과했는가" 로 바뀐다. 개발팀과의 대화도 달라진다. "느려요"가 아니라 "DB 조회 예산 240 ms인데 실측 p95가 610 ms입니다"가 된다.
판정 가능한 성능 요구사항 쓰기
이제 처음의 나쁜 요구사항을 고쳐 쓸 수 있다. 성능 요구사항 한 줄에는 여섯 가지 가 들어가야 한다.
| 요소 | 빠지면 생기는 일 |
|---|---|
| 워크로드 | 어떤 부하에서 잰 값인지 모른다 |
| 측정 지점 | 브라우저·LB·앱 중 어디 값인지 달라 3배 차이가 난다 |
| 통계량(백분위) | 평균으로 통과하고 꼬리에서 장애가 난다 |
| 측정 기간 | 5분 통과, 8시간 실패가 흔하다 |
| 허용 오류율 | 빠르지만 5%가 실패하는 결과를 막지 못한다 |
| 자원 여유 | 통과했지만 CPU 98%인 상태를 걸러내지 못한다 |
[나쁜 요구사항]
주요 화면은 3초 이내에 응답할 것
[좋은 요구사항]
PERF-01 상품 상세 조회
워크로드 : 워크로드 모델 v1.2의 '정상 부하'(19 rps, 믹스 7:2:1, 캐시 히트율 72%)
측정 지점 : API 게이트웨이 액세스 로그의 upstream_response_time
기준 : p95 ≤ 800 ms, p99 ≤ 2,000 ms
기간 : 30분 연속, 워밍업 5분 제외
오류율 : HTTP 5xx + 타임아웃 ≤ 0.5 %
자원 : 앱 인스턴스 CPU 평균 ≤ 70 %, 힙 사용률 ≤ 80 %, GC 정지 p99 ≤ 200 ms
근거 : 2026-09 운영 p95 740 ms, 이탈률 분석상 1초 초과 시 이탈 +12 %
PERF-02 푸시 발송 직후 스파이크
워크로드 : '스파이크'(19 → 121 rps, 20초 상승)
기준 : 스파이크 구간 p99 ≤ 5,000 ms, 오류율 ≤ 2 %,
부하 복귀 후 120초 이내에 p95 가 정상 부하 기준으로 복귀
근거 : 2026-08-14 푸시 발송 시 5분간 503 발생 — 재발 방지 기준PERF-02의 마지막 줄이 특히 중요하다. 스파이크 요구사항은 "튀는 동안 안 죽는다"만으로는 부족하고 복귀 시간 을 포함해야 한다. 스파이크 직후 큐에 쌓인 요청이 10분간 지연을 만드는 패턴이 실무에서 훨씬 흔하다.
목표 숫자의 근거는 어디서 오나
"p95 800 ms"의 800이라는 숫자는 어디서 왔는가. 근거는 네 곳 중 하나여야 한다. 없으면 만들어 낸 숫자다.
| 근거 유형 | 예시 | 강도 |
|---|---|---|
| 사용자 영향 데이터 | 응답 1초 초과 시 이탈률 +12%(자사 A/B 또는 로그 분석) | 가장 강함 |
| 현행 운영 실측 | 현재 p95 740 ms — "나빠지지 않을 것"이 기준 | 강함 |
| 계약·규제·SLA | 대외 계약서의 응답 시간 조항, 공공 사업 제안요청서 기준 | 강함(협상 불가) |
| 경쟁·업계 관행 | Core Web Vitals의 LCP 2.5초(모듈 16 레슨 4) | 보통 |
사용자 영향 데이터가 없을 때 흔히 쓰는 보조 도구가 Apdex 다. 목표 응답 시간 T를 정하고, T 이내를 만족, T~4T를 인내, 4T 초과를 불만으로 분류해 하나의 점수로 만든다. 숫자가 하나라서 경영진 보고에 편하지만 T를 누가 정했느냐가 결국 같은 질문 이므로, Apdex를 쓸 때도 T의 근거는 위 표에서 가져와야 한다.
확장이 선형이 아니라는 사실을 계산에 넣는다
서버를 2배로 늘리면 처리량이 2배가 되는가. 대개 아니다. 공유 자원(DB, 캐시, 락)에서의 직렬 구간과 노드 간 정합성 비용이 붙기 때문이다. Neil Gunther의 Universal Scalability Law 는 이 손실을 직렬화 계수(contention)와 일관성 계수(coherency)로 모델링한다. 실무에서 이 식을 정밀하게 적합할 일은 드물지만, 기억해야 할 결론은 분명하다.
- 노드를 늘려도 처리량이 늘지 않는 지점(knee) 이 존재한다
- 그 지점을 넘으면 노드를 더 늘릴 때 처리량이 오히려 떨어질 수 있다
- 그러므로 용량 산정에는 "몇 대까지 선형인가"를 실측으로 확인하는 단계 가 포함돼야 한다
확인 방법은 간단하다. 인스턴스 1·2·4대로 각각 한계 처리량을 측정해 그래프를 그린다. 1대 60 rps, 2대 115 rps, 4대 190 rps가 나왔다면 4대 구간에서 이미 선형성이 깨진 것이고, 그 원인(대개 DB나 캐시)이 다음 병목이다. 이 실측 없이 "10대면 600 rps"라고 보고하는 것이 용량 산정에서 가장 흔한 과장이다.
실무에서 왜 필요한가
용량 산정은 두 방향의 비용을 동시에 막는다. 과소 산정은 장애로, 과대 산정은 인프라 비용으로 나타난다. 클라우드 환경에서는 후자가 눈에 보이는 금액이라 오히려 설득이 쉽다 — "요청당 CPU를 40 ms에서 25 ms로 줄이면 인스턴스 4대가 2대가 되고 월 얼마가 줄어듭니다"는 문장은 개발 우선순위를 실제로 움직인다.
요구사항 쪽은 더 직접적이다. 판정 기준이 모호하면 성능 테스트 결과를 두고 릴리스 직전에 논쟁이 벌어진다. "p95 900 ms인데 나갈까요"라는 질문에 답할 수 있는 조직과 없는 조직의 차이는 기준 문서 한 장이다. 기준이 미리 합의돼 있으면 그 질문은 "PERF-01 미달, 완화 조치 A·B 중 선택"이라는 결정 안건이 된다.
오토스케일을 쓰는 조직에서도 산정은 사라지지 않는다. 오히려 최소 인스턴스·스케일아웃 임계치·쿨다운을 정하는 근거 로 형태만 바뀐다. 스파이크가 20초 만에 오는데 스케일아웃에 90초가 걸린다면, 그 90초를 버티는 최소 대수를 계산해 두어야 한다. 이 계산이 바로 위의 이용률 법칙이다.
채용시장에서의 위치
"용량 산정 경험"은 QA 공고보다 SRE·백엔드 공고에 자주 등장하지만, 성능 QA가 이 언어를 쓰면 협업의 위치가 달라진다. 리틀의 법칙으로 동시 사용자와 처리량을 구분해 말할 수 있는 QA는 개발·인프라 회의에서 요구사항을 협상하는 자리에 앉는다. ISTQB CT-PT 실러버스도 성능 요구사항 도출과 결과 집계를 별도 학습 목표로 다룬다.