내구 테스트(Soak/Endurance Testing)로 장시간 부하에서 누수를 잡는 법
메모리 누수, 커넥션 누수, GC 스톨처럼 단시간 부하 테스트로는 드러나지 않는 결함을 8~72시간 지속 부하로 찾아내는 내구 테스트 설계·측정·k6 예시.
2026-08-12 · 최초 발행 2025-12-22
부하 테스트를 통과한 서비스가 3일 뒤 메모리 부족으로 죽는 경우가 있다. 원인은 대개 스트레스가 아니라 시간이다. 짧은 부하 테스트는 순간의 처리 능력을 검증하지만, 메모리 누수나 커넥션 누수, GC 스톨처럼 시간이 누적돼야 드러나는 결함은 잡아내지 못한다. 내구(Soak/Endurance) 테스트는 이 공백을 메우기 위해 정상 범위의 부하를 장시간(수 시간~수 일) 지속 주입하며 자원 사용 추이와 성능 드리프트를 관찰하는 활동이다.
스트레스 테스트와 무엇이 다른가
스트레스 테스트가 한계점 도달을 목표로 부하를 급격히 올렸다 내렸다 하는 반면, 내구 테스트는 정상 범위의 지속 부하와 일중 변동(diurnal pattern)을 사용해 "얼마나 버티는가"가 아니라 "얼마나 오래 정상 상태를 유지하는가"를 검증한다. 대상 범위도 애플리케이션 계층에 그치지 않는다. 데이터베이스, 캐시, 메시징, 네트워크, OS/런타임, 배치/스케줄러까지 엔드투엔드 스택을 운영에 준하는 구성과 데이터 볼륨으로 태워야 실제로 의미 있는 결과가 나온다.
지속시간을 얼마로 잡을지가 설계의 핵심이다
내구 테스트에서 가장 흔한 실수는 지속시간을 임의로 정하는 것이다. 지속시간은 배치 주기, 캐시 갱신 주기, 토큰 만료, 리밸런싱 주기 등 시스템이 가진 주기적 이벤트를 23회 이상 포함하도록 정해야 한다. 한 번의 주기만 관찰하면 문제가 주기적 현상인지 우연인지 구분할 수 없다. 일반적으로 872시간을 권장하는데, 이 범위는 대부분의 배치/캐시 주기를 여러 번 포함하면서도 테스트 비용이 감당 가능한 수준이기 때문이다.
부하 프로파일 자체도 현실적이어야 한다. 고정 RPS만 주입하는 것이 아니라 시간대별 피크/저점, 버스트 이벤트, 사용자 시나리오 혼합을 반영하고 램프업 → 정상 구간 → 램프다운 단계로 구성한다.
관측 항목: 결국 기울기를 본다
내구 테스트의 판정 근거는 절대값이 아니라 시간에 따른 기울기다. p95/p99 지연, 에러율, 스루풋 같은 성능 지표와 함께 메모리, GC, FD/소켓, 스레드, 커넥션 풀, 큐 지연/적체를 추적해야 하는데, 여기서 메모리(RSS)가 선형으로 계속 늘어나면 누수를 의심하고, FD·커넥션 수가 줄지 않고 쌓이면 리소스 반환이 누락된 것이다. 드리프트 감지 기준으로 지연 p95 변화율, 메모리 성장 기울기, GC 시간 비율, DB 대기(Event) 비중에 임계치를 미리 정해두면 테스트 도중 육안 판단에 의존하지 않아도 된다.
환경은 프로덕션과 최대한 동등하게 맞춘다. 동일한 스케일링 정책과 리밸런싱, 보안/미들웨어 설정을 유지하고, 외부 의존성은 샌드박스나 스테이징 엔드포인트로 대체한다. 데이터는 마스킹/합성 데이터를 사용하되 볼륨과 카디널리티는 실제와 유사하게 재현하고 캐시 웜업 전략도 명시해둔다.
중단 기준도 사전에 정의해야 한다. 에러율, 레이턴시, 메모리 누수의 선형 성장, 큐 적체, 리밸런싱 폭주 등을 조건으로 잡고, 장시간 테스트에서 에러 버짓 누적 소진률을 계산해 배포 게이팅에 반영한다.
실제로 어떤 문제가 나오는가
대규모 API 백엔드에서는 72시간 동안 diurnal 패턴 RPS와 배치/캐시 만료 이벤트를 함께 주입했을 때 스레드풀 고갈과 연결 누수가 드러난 사례가 있다. 커넥션 타임아웃 튜닝과 풀 크기 조정으로 해결한다.
메시징/스트리밍 시스템은 주말 48시간 동안 Kafka 소비자 지연과 파티션 리밸런싱 행태를 관찰하는 방식이 유효하다. 컨슈머 그룹 리밸런스 빈도를 줄이고 배치 사이즈/ACK 정책을 조정하면 지연이 안정화된다.
데이터베이스/스토리지에서는 인덱스 블로트, Autovacuum 지연, long-running 트랜잭션 누수가 대표적인 발견이다. VACUUM/ANALYZE 윈도우를 재설계하고 커넥션 풀 최대 수를 조정해 대응한다.
모바일/IoT처럼 장기 연결을 유지하는 환경에서는 WebSocket/MQTT 세션 유지와 재연결 폭풍을 검증하고, 토큰 만료/재발급 정책을 테스트해 인증 오류를 줄인다. 배치/스케줄러 영역에서는 크론 작업 중첩과 DST(서머타임) 전환 시각 이슈가 장시간 실행에서만 재현되는데, 오버랩 방지 락과 타임존 안전 스케줄링으로 예방한다.
측정 지표와 기준
| 지표 축 | 모니터링 항목 | 기준/경향 | 도구 |
|---|---|---|---|
| 성능 | p95/p99 지연, 스루풋 | p95 드리프트 ≤ 5%/24h, 에러율 < 0.1% | Prometheus, Grafana |
| 확장성 | 오토스케일 이벤트, 큐 적체 | 스케일 이벤트 안정화, 소비 지연 정체 없음 | HPA/KEDA 메트릭 |
| 일관성 | 성공률, 데이터 무결성 검사 | 중복/유실 0건, 재시도 보상 트랜잭션 정상 | 로그/검증 잡 |
| 안정성 | RSS/GC/FD/스레드 수 추이 | RSS 기울기 ≈ 0, GC STW < 5% | JFR, eBPF, Node Exporter |
| 운영 편의 | 경보 소음, 로그 로테이션 | 노이즈 < 5건/24h, 로그 디스크 사용 안정 | Alertmanager, Loki |
k6로 내구 테스트 스크립트 짜기
k6나 JMeter는 시나리오 스크립팅과 장시간 실행을 지원하고, Prometheus/Grafana나 Loki/ELK, OpenTelemetry는 메트릭·로그·트레이싱을 수집해 상관분석을 돕는다. 커널/런타임 병목은 JVM의 JFR, .NET의 dotnet-counters, eBPF 계측으로 파고든다.
사전 조건은 k6 v0.49+ 설치, 테스트 대상 환경과 네트워크 안정성 확보, NTP 동기화 완료, 모니터링 스택 기동과 로그 로테이션/보관 정책 설정이다.
// 파일: soak.js
// k6 v0.49+
// 실행: k6 run --out json=results.json soak.js
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
soak: {
executor: 'constant-arrival-rate',
rate: 200, // 초당 200 요청
timeUnit: '1s',
duration: '12h', // 실제 운영에서는 24~72h 권장
preAllocatedVUs: 50,
maxVUs: 200,
},
},
thresholds: {
http_req_failed: ['rate<0.001'], // 에러율 < 0.1%
http_req_duration: ['p(95)<500', 'p(99)<900'], // p95/p99 지연 기준
},
tags: { release: __ENV.RELEASE || 'dev', test: 'soak' },
};
export default function () {
const res = http.get(__ENV.TARGET || 'https://example.com/api/health');
check(res, {
'status is 2xx': (r) => r.status >= 200 && r.status < 300,
});
// 현실적 think time
sleep(0.2 + Math.random() * 0.3);
}
애플리케이션 메트릭만으로는 부족하다. node_exporter, cAdvisor, DB 관측(autovacuum, waits)을 병행 수집하고, 결과 분석 시 p95/p99 드리프트와 메모리 성장 기울기(선형 회귀), 리밸런싱/오토스케일 이벤트의 상관관계를 함께 본다.
트레이드오프
프로덕션 등가 부하와 정책을 적용하고 외부 의존성을 샌드박스화하며 데이터 마스킹을 지키는 것이 기본이지만, 여기엔 비용과 시간이 든다. 야간/주말 스케줄과 비용 예산 관리가 필요하고, 오토스케일을 켜고 끄는지에 따라 관찰 가능성이 달라지므로 재현성과 현실성 중 무엇을 우선할지 선택이 필요하다. 실제 외부 API 호출을 차단하면 테스트 충실도가 떨어질 수 있어 샌드박스/레이트리밋과 벤더 비용을 함께 고려해야 한다.
기대할 수 있는 효과
안정성 측면에서는 p95 지연 드리프트 5%/24h 이내, 에러율 0.1% 미만, 경보 소음 50% 이상 감소를 기대할 수 있다. 누수를 조기에 발견하면 RSS/FD/커넥션의 선형 성장을 제거해 MTBF를 늘리고 무중단 기간을 연장할 수 있다. 정상 상태 자원 사용과 피크 대비 헤드룸을 산정하면 용량 계획이 고도화되고 비용 최적화로 이어진다. 배치/갱신/리밸런싱 창에서 나오는 이슈를 사전에 검출해 릴리스 게이팅 품질도 함께 올라간다.
내구 테스트는 한 번 하고 끝내는 이벤트가 아니라 배포 파이프라인과 운영 캘린더에 주기적으로 통합할 때 효과가 누적된다.