2026. 8. 31. 15:45ㆍLinux 완전 정복/Linux 운영 및 배포
목표: 서버가 실제로 어느 정도의 요청을 안정적으로 처리할 수 있는지 부하 테스트를 통해 측정하는 방법을 익힌다
결과: k6로 단계적인 부하를 발생시키고 Prometheus·Grafana 지표와 함께 분석해 최대 처리량, 안전 운영 구간, 서버 증설 시점을 판단할 수 있다
1. 서버 사양만 보고 처리량을 알 수 있을까?
운영 서버를 구축할 때 흔히 이런 질문을 한다.
CPU 8코어면 몇 명까지 접속할 수 있을까?
RAM 32GB면 초당 요청을 몇 개 처리할 수 있을까?
서버 한 대면 충분할까?
언제 서버를 한 대 더 추가해야 할까?
하지만 CPU와 메모리 사양만으로 정확한 처리량을 알 수는 없다.
같은 8코어 서버라도 애플리케이션 구조에 따라 성능은 크게 달라진다.
예:
서비스 A
HTTP 요청
→ 메모리 조회
→ 응답
서비스 B
HTTP 요청
→ DB Query
→ 외부 API
→ 파일 I/O
→ JSON 변환
→ 응답
두 서비스의 CPU와 RAM이 같아도 처리 가능한 요청량은 완전히 다를 수 있다.
따라서 실제 시스템에 부하를 발생시키고 측정해야 한다.
2. Capacity Planning이란?
Capacity Planning은 현재 시스템이 얼마나 많은 작업을 처리할 수 있는지 파악하고 앞으로 필요한 자원을 예측하는 작업이다.
예:
현재 Peak Traffic
150 RPS
3개월 후 예상
250 RPS
현재 서버 안전 처리량
200 RPS
이 경우:
250 RPS 예상
>
200 RPS 현재 안전 용량
이므로 서버 증설이나 성능 개선이 필요하다는 판단을 할 수 있다.
3. Capacity Planning에서 확인할 것
대표적인 항목은 다음과 같다.
현재 평균 Traffic
현재 Peak Traffic
서버 최대 처리량
안전한 운영 처리량
CPU 사용률
메모리 사용률
DB 부하
Disk I/O
Network
Error Rate
p95 / p99 Latency
중요한 것은 단순 최대 요청 수가 아니다.
서비스 품질을 유지하면서
얼마나 처리할 수 있는가?
가 핵심이다.
4. 최대 처리량과 안전 처리량은 다르다
예를 들어 테스트 결과가 다음과 같다고 하자.
200 RPS → 정상
250 RPS → 정상
300 RPS → 정상
350 RPS → p95 증가
400 RPS → 오류 발생
450 RPS → 서비스 불안정
그렇다고 운영 서버를:
400 RPS까지 사용 가능
이라고 판단하면 위험하다.
안전한 운영 구간은 보통 최대 한계보다 낮게 잡아야 한다.
예:
최대 SLO 만족 처리량
300 RPS
운영 목표
200~220 RPS
남는 영역을 Headroom으로 확보하는 것이다.
5. Headroom이 필요한 이유
운영 Traffic은 항상 일정하지 않다.
예:
평균 100 RPS
평소 Peak 180 RPS
이벤트 발생
300 RPS
또한 다음 상황이 동시에 발생할 수 있다.
DB가 평소보다 느림
배치 작업 실행
로그 증가
GC 발생
외부 API 지연
서버 한 대 장애
따라서 서버를 항상 최대 처리량 근처에서 운영하면 작은 변화에도 장애가 발생한다.
6. 부하 테스트란?
Load Test는 시스템에 의도적으로 요청을 발생시키면서 성능을 측정하는 테스트다.
예:
10 사용자
↓
50 사용자
↓
100 사용자
↓
200 사용자
↓
500 사용자
이때 다음 지표가 어떻게 변하는지 확인한다.
RPS
Latency
Error Rate
CPU
Memory
Disk I/O
Network
DB Connection
Queue
7. 부하 테스트의 목적
단순히 서버를 죽이는 것이 목적이 아니다.
대표적인 목적은 다음과 같다.
정상 처리량 확인
성능 기준 검증
병목 위치 탐색
최대 처리량 확인
증설 시점 판단
배포 전 성능 회귀 확인
SLO 만족 여부 확인
특히 54편에서 만든 SLO와 연결하면 부하 테스트의 기준이 명확해진다.
8. k6란?
이번 편에서는 Grafana k6를 사용한다.
k6는 HTTP API 등에 부하를 발생시키고 성능을 측정할 수 있는 오픈소스 부하 테스트 도구다.
기본적으로 JavaScript로 테스트 시나리오를 작성한다.
예:
import http from 'k6/http';
export default function () {
http.get(
'http://192.168.0.100:8080/api/status'
);
}
실행:
k6 run test.js
k6는 Virtual User 수와 테스트 시간을 지정하거나 단계적으로 부하를 증가시켜 테스트할 수 있다.
9. k6 실행 환경
k6는 여러 방식으로 실행할 수 있다.
Linux 직접 설치
Docker
CI/CD 서버
분산 실행
Grafana Cloud k6
이번 편에서는 가장 이해하기 쉬운 로컬 실행을 기준으로 한다.
설치 확인:
k6 version
Docker를 사용한다면 이미지도 제공된다. Grafana 공식 문서는 Linux 패키지 설치뿐 아니라 Docker 방식도 지원하고 있다.
10. 첫 번째 k6 테스트
파일:
smoke-test.js
내용:
import http from 'k6/http';
import { check, sleep } from 'k6';
export default function () {
const response =
http.get(
'http://192.168.0.100:8080/health'
);
check(response, {
'status is 200':
(r) => r.status === 200,
});
sleep(1);
}
실행:
k6 run smoke-test.js
11. Smoke Test부터 시작하자
부하 테스트라고 처음부터 1,000명의 사용자를 발생시키면 안 된다.
먼저 아주 작은 부하로 테스트 스크립트 자체가 정상인지 확인한다.
예:
k6 run \
--vus 1 \
--duration 30s \
smoke-test.js
Grafana k6 공식 문서에서도 VU와 duration을 이용해 간단한 로컬 부하 테스트를 실행할 수 있다.
확인할 것:
URL이 맞는가?
인증이 되는가?
응답 코드가 정상인가?
테스트 데이터가 정상인가?
스크립트 오류는 없는가?
Smoke Test가 성공한 뒤 본격적인 부하를 올리는 것이 좋다.
12. VU란?
k6에서 자주 보는 개념이 VU(Virtual User)다.
예:
k6 run \
--vus 100 \
--duration 1m \
test.js
의미:
100명의 Virtual User가
1분 동안
테스트 시나리오를 반복 실행
Grafana k6 문서에서도 VU를 이용해 여러 iteration을 병렬로 실행하며, 일반적으로 VU가 증가하면 생성되는 Traffic도 증가한다고 설명한다.
13. VU와 RPS는 같은 것이 아니다
매우 중요한 부분이다.
100 VU = 100 RPS
가 아니다.
예를 들어 사용자 하나가:
요청
↓
1초 대기
↓
요청
한다면 처리량과,
요청
↓
즉시 다음 요청
하는 경우의 처리량은 다르다.
즉 VU는 동시에 행동하는 사용자 수에 가까운 개념이고 RPS는 초당 실제 요청 수다.
14. RPS란?
RPS:
Requests Per Second
즉:
초당 요청 수
예:
RPS = 200
이면 평균적으로 1초에 약 200개의 요청이 처리되는 것이다.
서버 Capacity Planning에서는 VU보다 RPS가 더 직접적인 기준이 되는 경우가 많다.
15. 단계적으로 VU 증가시키기
다음처럼 ramping-vus를 사용할 수 있다.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
load_test: {
executor: 'ramping-vus',
stages: [
{
duration: '1m',
target: 20,
},
{
duration: '2m',
target: 20,
},
{
duration: '1m',
target: 50,
},
{
duration: '2m',
target: 50,
},
{
duration: '1m',
target: 100,
},
{
duration: '2m',
target: 100,
},
{
duration: '1m',
target: 0,
},
],
},
},
};
export default function () {
const response =
http.get(
'http://192.168.0.100:8080/api/status'
);
check(response, {
'status is 200':
(r) => r.status === 200,
});
sleep(1);
}
k6의 ramping-vus executor를 사용하면 일정 시간 동안 VU를 단계적으로 증가시키고 다시 감소시키는 테스트를 만들 수 있다.
16. 테스트를 단계적으로 올리는 이유
다음 테스트보다:
0 → 500 VU
다음 방식이 분석하기 좋다.
20
↓
50
↓
100
↓
150
↓
200
각 단계에서:
CPU
Memory
p95
p99
Error Rate
RPS
가 어떻게 변하는지 볼 수 있기 때문이다.
17. Threshold를 설정하자
부하 테스트를 실행했는데 결과가 이렇게 나왔다고 하자.
p95 = 1.2초
Error Rate = 3%
이게 성공일까 실패일까?
기준이 없다면 판단하기 어렵다.
그래서 k6에서는 Threshold를 사용할 수 있다.
18. Latency와 Error Rate Threshold
예:
export const options = {
thresholds: {
http_req_failed: [
'rate<0.01',
],
http_req_duration: [
'p(95)<500',
'p(99)<1000',
],
},
};
의미:
Error Rate < 1%
p95 < 500ms
p99 < 1000ms
k6 Threshold는 성능 테스트에 Pass/Fail 조건을 정의할 수 있으며, 조건을 위반하면 k6가 non-zero exit code를 반환해 CI/CD에서도 사용할 수 있다.
19. 54편의 SLO와 연결하기
54편에서 SLO를 다음처럼 만들었다고 하자.
Availability SLO
99.9%
Latency SLO
p95 < 500ms
부하 테스트 기준도 비슷하게 만들 수 있다.
thresholds: {
http_req_failed: [
'rate<0.001',
],
http_req_duration: [
'p(95)<500',
],
},
즉 부하 테스트가 단순 성능 숫자가 아니라 서비스 목표를 실제로 만족하는지 검증하는 도구가 된다.
20. HTTP 응답도 검증해야 한다
응답 시간이 빠르다고 정상은 아니다.
예:
HTTP 500
Response Time = 10ms
굉장히 빠르게 실패한 것이다.
따라서 check()를 사용한다.
check(response, {
'HTTP 200':
(r) => r.status === 200,
});
응답 내용도 검증할 수 있다.
check(response, {
'HTTP 200':
(r) => r.status === 200,
'body contains success':
(r) => r.body.includes('success'),
});
k6의 API 테스트에서는 check를 통해 응답의 정확성을 검증하고 Threshold를 통해 성능 기준을 검증할 수 있다.
21. Checks도 Threshold로 만들기
thresholds: {
checks: [
'rate>0.99',
],
http_req_failed: [
'rate<0.01',
],
http_req_duration: [
'p(95)<500',
],
},
즉:
Check 성공률 > 99%
까지 테스트 성공 조건에 포함시킬 수 있다.
22. 정확한 RPS를 발생시키고 싶다면?
VU만 증가시키면 정확히 몇 RPS가 발생할지 예측하기 어려울 수 있다.
Capacity Planning에서는 다음처럼 테스트하고 싶을 수 있다.
100 RPS
↓
200 RPS
↓
300 RPS
↓
400 RPS
이럴 때 k6의 Arrival Rate 기반 executor를 사용할 수 있다.
23. constant-arrival-rate
예:
export const options = {
scenarios: {
capacity_test: {
executor:
'constant-arrival-rate',
rate: 100,
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 100,
maxVUs: 500,
},
},
};
의미:
초당 100개의 iteration을
5분 동안 시작
k6 공식 API Load Testing 문서에서도 constant-arrival-rate를 이용해 지정된 시간 단위당 일정한 iteration rate를 생성하는 방식을 제공한다.
24. 100 RPS 테스트
rate: 100,
timeUnit: '1s',
duration: '5m',
실행:
k6 run capacity-test.js
테스트 중 다음을 기록한다.
RPS
p95
p99
Error Rate
CPU
Memory
DB CPU
DB Connections
Disk I/O
Network
25. 다음 단계는 200 RPS
예:
rate: 200,
다시 실행한다.
결과:
100 RPS
p95 = 120ms
Error = 0%
200 RPS
p95 = 180ms
Error = 0%
아직 여유가 있어 보인다.
26. 300 RPS
300 RPS
p95 = 280ms
p99 = 450ms
Error = 0.02%
CPU = 65%
SLO:
p95 < 500ms
Error < 0.1%
라면 아직 정상 범위다.
27. 400 RPS
결과:
400 RPS
p95 = 680ms
p99 = 1.5s
Error = 1.2%
CPU = 92%
이제 SLO를 위반한다.
즉 서버가 요청을 받기는 하지만 운영 가능한 Capacity는 이미 넘어갔다고 판단할 수 있다.
28. Breaking Point 찾기
부하를 계속 증가시키면서 시스템이 어디부터 급격히 무너지는지 확인할 수 있다.
예:
100 RPS → 정상
200 RPS → 정상
300 RPS → 정상
350 RPS → Latency 증가
400 RPS → Error 증가
450 RPS → Timeout 증가
500 RPS → 서비스 불안정
Grafana k6 공식 성능 테스트 예제 역시 부하 단계를 계속 증가시키면서 Threshold를 깨는 지점을 찾는 breaking test 방식을 보여준다.
29. Breaking Point와 Capacity는 다르다
위 테스트에서:
Breaking Point
400~450 RPS
라고 해도 운영 Capacity를 400 RPS로 잡으면 안 된다.
SLO가 만족되는 마지막 구간이:
300 RPS
라면 이것이 더 중요한 값이다.
그리고 실제 운영 목표는 여기에 Headroom을 둔다.
30. 안전 Capacity 계산 예
SLO 만족 최대 처리량:
300 RPS
30% Headroom을 확보한다고 하자.
300 × 0.7
= 210 RPS
즉 운영에서는 대략:
210 RPS 이하
를 한 서버의 안전한 목표 구간으로 볼 수 있다.
Headroom 비율은 서비스 특성에 맞게 정해야 한다.
31. 서버가 몇 대 필요할까?
예상 Peak Traffic:
500 RPS
서버 한 대 안전 Capacity:
210 RPS
계산:
500 / 210
= 2.38
따라서 단순 계산에서는 최소:
3대
가 필요하다.
32. 한 대 장애까지 고려한다면?
정상 운영에 서버 3대가 필요한 상황이라고 하자.
Server 1
Server 2
Server 3
그런데 Server 1이 장애 나면:
Server 2
Server 3
두 대만 남는다.
각 서버 안전 Capacity:
210 RPS
총:
420 RPS
예상 Peak:
500 RPS
를 처리하지 못한다.
따라서 장애 상황까지 고려한다면:
4대
가 필요할 수도 있다.
33. N+1 Capacity
이런 구조를 보통 개념적으로:
정상 운영에 필요한 Capacity
+
서버 한 대 장애 여유
로 설계할 수 있다.
예:
3대 필요
+
1대 장애 대비
= 4대
서비스 중요도에 따라 N+1, N+2 또는 더 높은 여유를 둘 수 있다.
34. 서버를 늘리면 성능도 정확히 두 배가 될까?
반드시 그렇지는 않다.
예:
App Server 1대
300 RPS
App Server 2대
600 RPS?
라고 생각할 수 있다.
하지만 뒤에 하나의 DB가 있다면:
App1 ─┐
├─ DB
App2 ─┘
DB가 새로운 병목이 될 수 있다.
결과:
1대 = 300 RPS
2대 = 500 RPS
3대 = 550 RPS
처럼 증가 폭이 줄어들 수도 있다.
35. Capacity는 시스템 전체로 측정해야 한다
실제 서비스 경로:
Load Balancer
↓
App
↓
Redis
↓
Database
↓
Storage
↓
External API
Capacity는 가장 약한 구간의 영향을 받는다.
따라서 App CPU만 보면 안 된다.
36. 테스트 중 Prometheus에서 볼 지표
49~54편에서 구축한 모니터링 환경을 그대로 활용한다.
CPU
100 - (
avg by (instance) (
rate(
node_cpu_seconds_total{
mode="idle"
}[5m]
)
) * 100
)
Memory
100 *
(
1 -
node_memory_MemAvailable_bytes
/
node_memory_MemTotal_bytes
)
Application Request Rate
rate(
myapp_http_requests_total[1m]
)
Error Rate
100 *
rate(
myapp_http_errors_total[5m]
)
/
clamp_min(
rate(
myapp_http_requests_total[5m]
),
0.000001
)
37. p95 Latency도 같이 보기
histogram_quantile(
0.95,
sum by (le) (
rate(
myapp_request_duration_seconds_bucket[5m]
)
)
)
부하가 증가할 때 그래프를 보면 다음과 같은 패턴이 나타날 수 있다.
RPS 증가
↓
CPU 증가
↓
p95 조금 증가
↓
특정 시점부터
p95 급격히 증가
이 지점이 매우 중요하다.
38. 성능은 선형으로 나빠지지 않을 수 있다
예:
100 RPS → 100ms
200 RPS → 130ms
300 RPS → 180ms
350 RPS → 250ms
400 RPS → 900ms
450 RPS → 3초
처음에는 조금씩 증가하다가 특정 시점부터 급격하게 증가한다.
이런 구간을 Knee Point처럼 생각할 수 있다.
운영 Capacity는 이 급격한 성능 악화 구간보다 충분히 아래에 두는 것이 좋다.
39. CPU가 100%가 아니어도 병목일 수 있다
예:
CPU = 40%
p95 = 3초
가능한 원인:
DB Connection Pool 부족
외부 API 지연
Disk I/O
Lock
Thread Pool 부족
Queue 증가
Network 지연
따라서 Capacity Test에서는 CPU 하나만 보면 안 된다.
40. Queue가 증가하는지 보자
요청이 처리 능력보다 빠르게 들어오면 Queue가 쌓일 수 있다.
예:
처리 Capacity
300 request/sec
입력
400 request/sec
매초:
약 100 request
가 밀릴 수 있다.
결과:
Queue 증가
↓
Latency 증가
↓
Timeout
↓
Retry
↓
더 많은 Traffic
↓
장애 확대
41. Little's Law를 간단히 이해하기
Queue 시스템에서는 다음 관계를 생각할 수 있다.
동시 처리 작업 수
≈
처리량 × 평균 처리 시간
예:
200 request/sec
평균 응답 시간
0.2초
대략:
200 × 0.2
= 40
정도의 요청이 동시에 시스템 안에 존재할 수 있다.
응답 시간이 늘면 필요한 동시 처리 Capacity도 증가한다.
42. DB Connection Pool 병목 예
예:
App Thread
100개
DB Connection
10개
Traffic이 증가하면:
DB Connection 10개 사용 중
나머지 요청
↓
Connection 대기
CPU는 낮은데 응답 시간이 증가할 수 있다.
따라서 테스트 중 다음 지표가 중요하다.
Active DB Connections
Waiting Connections
Query Latency
Slow Query
DB CPU
Lock Wait
43. Load Generator도 확인해야 한다
매우 중요한 실수 중 하나다.
부하 테스트를 발생시키는 k6 서버가 먼저 한계에 도달할 수도 있다.
예:
Target Server CPU
40%
k6 Server CPU
100%
이 상태에서 Traffic이 더 이상 증가하지 않는다면 대상 서버의 Capacity를 측정한 것이 아니다.
부하 발생기의 Capacity를 측정한 것이다.
44. 부하 발생 서버는 분리하는 것이 좋다
가능하면:
Load Generator
192.168.0.50
↓
Target Server
192.168.0.100
처럼 분리한다.
피하고 싶은 구조:
한 서버에서
k6 실행
+
App 실행
+
DB 실행
이렇게 하면 부하 생성 자체가 테스트 대상 자원을 사용해 결과가 왜곡될 수 있다.
45. 네트워크 위치도 중요하다
부하 발생 서버와 대상 서버가:
같은 LAN
인지,
인터넷을 경유
하는지도 결과에 영향을 준다.
예:
App 처리 시간
50ms
Network
100ms
이면 실제 응답 시간은 네트워크 영향까지 포함된다.
따라서 테스트 목적을 명확히 해야 한다.
46. Component Test와 End-to-End Test
Component Test
k6
↓
App API
목적:
App 자체 Capacity 확인
End-to-End Test
k6
↓
Load Balancer
↓
Nginx
↓
App
↓
DB
목적:
실제 사용자 경로 전체 Capacity 확인
Grafana k6 공식 API 테스트 가이드도 단일 API에서 시작해 통합 API, End-to-End 흐름으로 테스트 범위를 확장하는 접근을 설명한다.
47. 실제 사용자 흐름을 테스트하기
실제 사용자가 다음 작업을 한다고 하자.
로그인
↓
정보 조회
↓
상세 조회
↓
결과 등록
↓
로그아웃
k6에서도 이런 흐름을 만들 수 있다.
import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
http.post(
'http://server/login',
JSON.stringify({
id: 'test',
password: 'test',
}),
{
headers: {
'Content-Type':
'application/json',
},
}
);
sleep(1);
http.get(
'http://server/api/items'
);
sleep(2);
http.get(
'http://server/api/items/1'
);
sleep(1);
}
48. Think Time을 무시하면 안 된다
실제 사용자는 요청을 쉬지 않고 보내지 않는다.
예:
페이지 확인
2초
버튼 선택
3초
입력
5초
따라서 실제 사용자 시뮬레이션에는 sleep() 등을 이용해 Think Time을 반영할 수 있다.
sleep(2);
반대로 서버 순수 처리 Capacity를 측정하는 목적이라면 Think Time 없이 Arrival Rate 기반 테스트를 사용할 수 있다.
즉 테스트 목적에 따라 모델을 다르게 해야 한다.
49. 테스트 종류를 구분하자
대표적으로 다음처럼 구분할 수 있다.
Smoke Test
Load Test
Stress Test
Spike Test
Soak Test
Breaking Test
50. Smoke Test
아주 작은 부하로 시스템과 테스트 스크립트가 정상인지 확인한다.
1~5 VU
짧은 시간
목적:
테스트 자체 검증
51. Load Test
평상시 예상되는 부하를 테스트한다.
예:
평소 Peak
200 RPS
목적:
실제 운영 Traffic에서
SLO를 만족하는가?
52. Stress Test
평소보다 높은 부하를 발생시킨다.
예:
200 RPS
↓
300
↓
400
목적:
언제 성능이 나빠지기 시작하는가?
53. Breaking Test
계속 부하를 증가시킨다.
100
200
300
400
500
600 RPS
...
목적:
시스템의 한계는 어디인가?
54. Spike Test
Traffic을 갑자기 크게 증가시킨다.
50 RPS
갑자기
500 RPS
확인:
Auto Scaling 반응
Connection Pool
Queue
Rate Limiting
Cache
복구 능력
55. Soak Test
적당한 부하를 장시간 유지한다.
예:
150 RPS
12시간
찾을 수 있는 문제:
메모리 누수
Connection 누수
로그 파일 증가
FD 누수
DB Connection 누수
Disk 증가
짧은 성능 테스트에서는 발견하기 어려운 문제를 찾는 데 유용하다.
56. Capacity Test 결과표 만들기
테스트 결과는 표로 남겨두면 좋다.
예:
RPSCPUMemoryp95p99Error
| 100 | 25% | 40% | 120ms | 180ms | 0% |
| 200 | 42% | 44% | 180ms | 260ms | 0% |
| 300 | 65% | 48% | 280ms | 450ms | 0.02% |
| 350 | 78% | 50% | 410ms | 800ms | 0.08% |
| 400 | 92% | 52% | 680ms | 1.5s | 1.2% |
| 450 | 97% | 53% | 2.1s | 4.2s | 8.5% |
SLO:
p95 < 500ms
Error < 0.1%
따라서:
최대 SLO 만족 구간
≈ 350 RPS
라고 판단할 수 있다.
57. Headroom 적용
최대 SLO 만족 Capacity:
350 RPS
30% 여유:
350 × 0.7
= 245
따라서 안전 운영 목표:
약 245 RPS / Server
58. 예상 Peak으로 서버 수 계산
3개월 후 예상 Peak:
650 RPS
한 서버 안전 Capacity:
245 RPS
계산:
650 / 245
= 2.65
최소:
3 Servers
하지만 한 서버 장애까지 고려한다면:
4 Servers
구성을 검토할 수 있다.
59. Traffic 성장률까지 고려하기
현재 Peak:
400 RPS
월 Traffic 성장률:
10%
단순 예측:
다음 달
440 RPS
2개월 후
484 RPS
3개월 후
532 RPS
Capacity Planning에서는 현재 Traffic만 보는 것이 아니라 앞으로의 성장도 함께 고려해야 한다.
60. 언제 증설해야 할까?
나쁜 방식:
CPU 100%가 되면 서버 추가
그때는 이미 장애가 발생할 가능성이 높다.
더 좋은 방식:
예상 Peak Traffic이
안전 Capacity의 70~80%에 도달
↓
증설 계획 시작
처럼 여유 있게 움직이는 것이다.
정확한 기준은 서비스와 증설 소요 시간에 따라 다르게 정해야 한다.
61. Auto Scaling 기준도 Capacity Test에서 나온다
예:
Server Capacity
300 RPS
운영 목표:
200 RPS / Instance
그렇다면 Auto Scaling 기준을 설계할 때:
CPU
Request Rate
Queue Length
Latency
등을 조합할 수 있다.
단순히 CPU 80%만 기준으로 정하는 것보다 실제 Capacity 테스트 결과가 훨씬 좋은 근거가 된다.
62. 배포 전 성능 회귀 테스트
이전 Version:
v1.2
300 RPS
p95 250ms
새 Version:
v1.3
300 RPS
p95 600ms
기능 테스트는 모두 성공하더라도 성능이 크게 떨어졌다.
Threshold를 사용하면:
thresholds: {
http_req_duration: [
'p(95)<500',
],
}
v1.3 빌드를 실패로 판단할 수 있다.
k6 Threshold가 실패하면 non-zero exit code를 반환하므로 CI/CD 성능 검증 단계에도 연결할 수 있다.
63. CI/CD에 부하 테스트 넣기
개념:
Git Push
↓
Build
↓
Unit Test
↓
Deploy Staging
↓
k6 Performance Test
↓
Threshold 통과?
│
├─ YES → Production
│
└─ NO → 배포 중단
모든 배포마다 대규모 Stress Test를 할 필요는 없다.
예:
매 배포
→ 짧은 Performance Test
야간
→ 중간 Load Test
정기적
→ Stress / Soak Test
처럼 구분할 수 있다.
64. 운영 서버에 Breaking Test를 바로 하면 안 된다
Breaking Test의 목적은 말 그대로 시스템 한계를 확인하는 것이다.
따라서 운영 서버에서 무계획하게 실행하면 실제 장애를 만들 수 있다.
특히 다음 테스트는 주의해야 한다.
회원 생성
결제
파일 업로드
DB INSERT
메일 발송
외부 API 호출
장비 Command
가능하면:
Staging
성능 테스트 전용 환경
격리된 테스트 데이터
를 사용하는 것이 좋다.
운영 환경에서 반드시 해야 한다면 영향 범위, 트래픽 제한, 중단 기준과 복구 절차를 사전에 정해야 한다.
65. 테스트 데이터도 중요하다
나쁜 테스트:
모든 VU가
user_id = 1
실제 운영:
수천 개의 서로 다른 User
같은 데이터를 반복 조회하면 Cache가 과도하게 잘 동작해 실제보다 높은 성능이 나올 수 있다.
따라서 가능한 한 운영 상황과 비슷한 데이터 패턴을 사용하는 것이 좋다.
66. Cache Warm 상태와 Cold 상태
테스트 결과는 Cache 상태에 따라서도 달라진다.
Cold
Cache 없음
초기 요청이 느릴 수 있다.
Warm
Cache 충분히 생성
응답이 훨씬 빠를 수 있다.
따라서 테스트 보고서에:
Cache Warm 여부
를 기록하는 것이 좋다.
67. Retry가 Capacity를 망가뜨릴 수도 있다
서비스가 느려지면 Client가 재시도한다고 하자.
요청 실패
↓
Retry
↓
또 실패
↓
Retry
원래:
300 RPS
였는데 Retry 때문에:
500 RPS
가 될 수도 있다.
이를 Retry Storm 같은 문제로 볼 수 있다.
Capacity 테스트에서는 실제 Client의 Retry 정책도 고려해야 한다.
68. Rate Limiting도 테스트해야 한다
서비스가 처리할 수 있는 최대 용량을 넘는 요청은 무조건 받아들이는 것보다 제한하는 것이 나을 수 있다.
예:
Capacity
300 RPS
Incoming
1,000 RPS
모두 처리하려다 전체 서비스가 죽는 것보다:
300 RPS 처리
나머지 일부
429 Too Many Requests
처럼 제한하면서 핵심 기능을 유지하는 전략도 가능하다.
69. 장비 프로그램의 Capacity Planning
웹 서버뿐 아니라 검사 장비 프로그램에도 적용할 수 있다.
예:
동시 Slot 수
초당 FPGA Command 수
DB Upload 처리량
Result Parsing 처리량
Log 처리량
예를 들어:
24 Slot 동시 Test
↓
Result 생성
↓
DB Upload Queue
에서 DB Upload가 실제 병목일 수 있다.
70. 장비 프로그램 테스트 예
정상 환경:
24 Slot
DB Upload Queue = 0~3
CPU = 30%
부하 증가:
48 Simulated Slot
DB Upload Queue = 150
CPU = 35%
CPU는 정상이다.
하지만 Queue가 계속 증가한다.
즉 병목:
DB Upload 처리량
일 가능성이 높다.
71. 처리량 Metric 만들기
51편 방식으로 다음 Metric을 만들 수 있다.
equipment_tests_total
equipment_db_upload_total
equipment_db_upload_failures_total
equipment_pending_jobs
초당 Test 완료:
rate(
equipment_tests_total[1m]
)
초당 Upload:
rate(
equipment_db_upload_total[1m]
)
Queue:
equipment_pending_jobs
72. Queue가 계속 증가한다면
예:
Input
100 jobs/sec
Process
80 jobs/sec
차이:
20 jobs/sec
Queue는 계속 증가한다.
이런 구조에서는 일시적으로 시스템이 살아 있어도 결국 문제가 발생한다.
따라서 Capacity를 볼 때:
CPU 정상
만 볼 것이 아니라:
Queue가 안정되는가?
도 반드시 확인해야 한다.
73. 부하 테스트 결과 기록 양식
테스트마다 다음을 기록하는 것이 좋다.
Test Date
Application Version
Server Spec
DB Spec
Test Scenario
VU / RPS
Duration
CPU
Memory
Disk I/O
Network
p50
p95
p99
Error Rate
Max Queue
Result
예:
Version
1.4.2
Scenario
300 RPS / 10min
CPU
65%
Memory
48%
p95
280ms
p99
450ms
Error
0.02%
Result
PASS
74. 테스트 결과는 Git에 남겨두자
예:
performance/
├── scripts/
│ ├── smoke.js
│ ├── load.js
│ └── stress.js
│
└── reports/
├── 2026-08-01-v1.4.1.md
└── 2026-08-15-v1.4.2.md
버전별 Capacity 변화를 확인할 수 있다.
예:
v1.4.1
Safe Capacity
250 RPS
v1.4.2
Safe Capacity
320 RPS
성능 개선 효과도 수치로 확인할 수 있다.
75. 자주 하는 실수
테스트 시작부터 최대 Traffic을 발생시킴
VU와 RPS를 같은 개념으로 생각함
CPU 사용률만 보고 서버 Capacity를 결정함
최대 Breaking Point를 운영 Capacity로 사용함
p95와 p99 없이 평균 응답 시간만 확인함
Error Rate를 확인하지 않음
DB, Redis, Queue 등 의존 서비스를 모니터링하지 않음
k6 Load Generator 자체가 CPU 100%인데 테스트를 계속함
운영 서버에서 무계획하게 Breaking Test를 실행함
모든 테스트에 같은 데이터 하나만 사용해 Cache 효과를 과장함
서버를 두 배로 늘리면 Capacity도 무조건 두 배가 된다고 생각함
테스트 결과를 기록하지 않아 Version별 성능 변화를 비교하지 못함
76. 55편 요약
- Capacity Planning은 현재 시스템의 처리 능력을 측정하고 미래 Traffic에 필요한 자원을 예측하는 과정이다
- 서버 사양만으로 실제 처리 가능한 요청량을 정확하게 판단할 수 없다
- k6를 이용해 VU 또는 Arrival Rate 기반으로 실제 부하를 발생시킬 수 있다
- VU는 가상 사용자의 개념이고 RPS는 실제 초당 요청 수이므로 서로 다르다
- Threshold를 이용해 p95, p99, Error Rate 같은 SLO 기준을 자동으로 검증할 수 있다
- 최대 Breaking Point가 아니라 SLO를 만족하는 최대 처리량을 먼저 찾아야 한다
- 실제 운영 Capacity는 그 처리량보다 낮게 잡아 Headroom을 확보하는 것이 좋다
- Capacity Test 중에는 CPU뿐 아니라 Memory, DB, Disk I/O, Queue, Network, Error Rate를 함께 봐야 한다
- 서버를 추가해도 DB 같은 공유 자원이 병목이면 성능이 선형적으로 증가하지 않을 수 있다
- Load Generator가 먼저 병목이 되지 않는지도 확인해야 한다
- Smoke, Load, Stress, Spike, Soak, Breaking Test는 각각 목적이 다르다
- Capacity 결과를 이용하면 서버 증설, Auto Scaling, 배포 성능 검증의 근거를 만들 수 있다
Capacity Planning에서 가장 중요한 질문은:
서버가 완전히 죽기 전까지
얼마나 처리할 수 있는가?
가 아니다.
실제 운영에서는:
SLO를 만족하면서
충분한 장애 여유를 남긴 상태로
얼마나 처리할 수 있는가?
를 알아야 한다.
그 값을 알고 있어야 Traffic이 늘어나기 전에 서버를 증설하고, 장애가 발생하기 전에 시스템의 병목을 개선할 수 있다.
메인 키워드
- 리눅스 부하 테스트
- k6 부하 테스트
- Capacity Planning
- 서버 처리량 측정
서브 키워드
- k6 VU RPS 차이
- k6 Threshold 사용법
- k6 ramping vus
- constant arrival rate k6
- 서버 최대 처리량 테스트
- Stress Test
- Spike Test
- Soak Test
- Breaking Test
- 서버 증설 기준
- Prometheus 부하 테스트
- Grafana 성능 모니터링