소크 테스트
고친 사람 github-actions[bot]
소크 테스트는 대개 평소만큼의 부하를 몇 시간에서 며칠 동안 끊지 않고 걸어 둡니다. 짧게 돌리면 멀쩡하다가 시간이 지나야 쌓여서 터지는 문제를 운영에 내보내기 전에 찾아냅니다. 조금씩 새는 메모리가 대표적인 표적입니다.
쉽고 빠른 이해
대개 평소만큼의 요청을 오래 걸어 두는 시험입니다. 그동안 서버가 달라지는지 지켜봅니다. 평소만큼의 사용자를 흉내 내어 여덟 시간 내내 요청을 보내는 식입니다.
서버는 운영에서 며칠씩 재시작 없이 돕니다. 요청마다 조금씩 새는 문제는 10분짜리 시험에서는 티가 안 납니다. 운영에 나간 지 며칠 뒤 서버가 쓰러져야 처음 드러나기도 합니다. 그 며칠을 운영에 나가기 전에 시험 환경에서 겪어 봅니다.
어떻게 하나:
- 평소 수준까지 부하를 올린 뒤 오래 유지합니다
- 메모리 · 연결 수 · 디스크 · 응답 시간을 같은 간격으로 적습니다
- 시간이 갈수록 한쪽으로 오르는 값이 있는지 봅니다. 메모리라면 치운 직후에도 남는 양을 봅니다
오래 돌려야 해서 결과가 늦게 나옵니다. 그동안 운영과 닮은 시험 환경을 붙잡아 두는 비용이 대가입니다.
상세
수도꼭지를 잠깐 틀어 볼 때는 멀쩡해 보이던 배관이 있습니다. 밤새 물을 흘려 두면 그 배관의 이음새 밑 바닥이 젖어 있곤 합니다. 한 방울씩 새는 곳은 오래 두어야 보입니다.
응답에 걸리는 시간과 처리하는 양을 재는 시험을 통틀어 성능 테스트라고 합니다. 소크 테스트는 그 한 갈래입니다. 대개 운영에서 예상하는 만큼의 부하를 몇 시간에서 며칠 동안 유지합니다. 그동안 성능과 자원 사용이 시간이 지나며 나빠지는지 봅니다.
예를 들면 평소 낮 시간만큼의 요청을 여덟 시간 동안 끊지 않고 흘려 둡니다. 그 긴 시간 내내 시스템이 멈추지 않고 버티는지도 함께 봅니다.
부하는 시스템에 들어오는 일감의 양입니다. 초당 요청 수로 세기도 합니다. 사람 한 명의 흐름을 흉내 내는 가상 사용자를 몇 명 띄웠는지로 세기도 합니다.
부하를 거는 시험은 대개 세 구간으로 나뉩니다. 목표 부하까지 올리는 구간과 그 부하를 유지하는 구간, 그리고 내리는 구간입니다.
소크 테스트는 예상 부하를 감당하는지 보는 부하 테스트와 같은 구간을 씁니다. 거는 부하도 대개 같은 평소 수준입니다. 갈리는 것은 유지하는 구간의 길이입니다. 부하 테스트가 몇 분 유지하는 구간을 소크 테스트는 몇 시간, 길면 며칠로 늘립니다.
예상 범위를 넘겨 한계까지 미는 시험은 스트레스 테스트라고 합니다. 스트레스 테스트는 시스템이 어디까지 버티나를 묻습니다. 소크 테스트는 시간이 지나며 무엇이 쌓이나를 묻습니다.
soak 는 물에 푹 담가 둔다는 뜻의 영어 낱말입니다. 시스템을 부하 속에 오래 담가 두는 시험이라고 읽으면 이름이 풀립니다. 같은 시험을 내구 테스트(endurance testing)나 스태미나 테스트(stamina test)라고도 부릅니다.
오래 걸어야 드러나는 고장
오래 걸어야만 보이는 고장은 대개 무언가가 쌓이는 모양입니다. 쓰고 나서 돌려줘야 할 자원을 돌려주지 않는 것을 누수라고 부릅니다. 누수는 한 번에는 티가 안 납니다. 모여야 보입니다.
아래 표에 나오는 자원 둘을 먼저 풉니다. 커넥션 풀은 미리 열어 둔 데이터베이스 연결을 여러 요청에 돌려 가며 빌려주는 묶음입니다. 연결을 여는 비용을 요청마다 치르지 않으려고 둡니다.
파일 디스크립터는 운영체제가 프로세스에 붙여 주는 번호입니다. 프로세스가 파일이나 소켓을 하나 열 때마다 번호가 하나씩 붙습니다. 한 프로세스가 가질 수 있는 번호 수에는 상한이 있습니다.
| 쌓이는 것 | 오래 지나면 보이는 모습 |
|---|---|
| 메모리 누수 — 다 쓴 객체를 어딘가에서 계속 붙잡고 있다 | 메모리가 꾸준히 올라 결국 프로세스가 죽는다 |
| 연결 누수 — 커넥션 풀에서 빌린 연결을 안 돌려준다 | 풀에 남은 연결이 바닥나 요청이 기다리다 실패한다 |
| 파일 누수 — 연 파일이나 소켓을 안 닫는다 | 파일 디스크립터가 상한에 닿아 새 연결을 못 받는다 |
| 지우는 규칙이 없는 캐시나 목록 | 항목이 늘기만 해서 메모리가 계속 오른다 |
| 로그 파일과 임시 파일 | 디스크가 차서 파일 쓰기가 실패한다 |
표의 다섯은 모양이 같습니다. 한 번에 새는 양은 작습니다. 시간이 곱해져야 커집니다.
요청 하나마다 메모리를 1킬로바이트씩 돌려주지 않는 서버를 가정해 봅니다. 초당 요청이 100개면 쌓이는 양은 시험 길이에 따라 이렇게 달라집니다.
| 시험 길이 | 쌓인 메모리 |
|---|---|
| 10분 | 약 60메가바이트 |
| 1시간 | 약 360메가바이트 |
| 하루 | 약 8.6기가바이트 |
10분짜리 시험에서 60메가바이트는 평소 사용량의 출렁임에 묻혀 안 보입니다. 하루가 지나면 8기가바이트를 넘습니다. 프로세스에 준 메모리 한도를 넘기면 프로세스가 죽습니다. 이 서버에서는 거는 부하의 크기보다 거는 시간의 길이가 결과를 가릅니다.
쌓임이 프로세스를 죽이는 모습으로만 드러나지는 않습니다. 응답이 서서히 늘어지는 모습으로도 나타납니다.
가비지 컬렉션은 안 쓰는 메모리를 언어의 실행 환경이 알아서 치우는 일입니다. 붙잡힌 객체가 늘면 치울 때마다 훑을 것이 많아집니다. 치우는 동안 요청 처리가 멈추는 시간도 따라서 길어질 수 있습니다.
처리가 밀리는 것도 오래 걸어야 보입니다. 요청이 들어오는 속도를 시스템이 따라가지 못하면 그 시스템을 부르는 쪽의 성능까지 함께 나빠집니다. 부르는 쪽까지 끌어내리는 이 밀림을 백프레셔(back pressure)라고 부릅니다. 백프레셔가 이어지면 처리를 기다리는 줄이 넘칠 수 있습니다.
메모리가 그리는 두 모양
시간에 따라 메모리 사용량을 그려 보면 누수가 모양으로 드러납니다. 아래는 가상의 두 서버를 여덟 시간 동안 잰 모습입니다. 값은 설명을 위해 정한 것입니다.
xychart-beta
title "가상의 두 서버의 메모리 사용량"
x-axis ["0시간", "1", "2", "3", "4", "5", "6", "7", "8"]
y-axis "메가바이트" 0 --> 800
line [300, 420, 310, 430, 320, 420, 310, 430, 320]
line [300, 440, 370, 510, 450, 590, 530, 670, 610]
한 선은 톱니처럼 오르내리다 300메가바이트 근처로 돌아옵니다. 치우는 일이 제때 되는 서버입니다. 다른 선도 오르내립니다. 그런데 바닥이 300에서 610메가바이트까지 한 칸씩 올라갑니다. 무언가를 붙잡고 놓지 않는 서버입니다.
그래서 봐야 할 것은 꼭대기가 아니라 바닥입니다. 치운 직후에도 남는 양이 시간에 따라 오르면 누수를 의심합니다. 짧은 시험에서는 톱니 한두 개만 보여서 이 두 모양을 가를 수 없습니다.
배경
부하를 거는 시험은 흔히 몇 분 안에 끝납니다. 그동안 시스템은 막 켜진 상태에 가깝습니다. 메모리에는 여유가 있습니다. 연결도 새것입니다.
운영은 다릅니다. 대부분의 시스템은 며칠, 몇 주, 몇 달 동안 사람이 손대지 않아도 켜진 채 돌아가야 합니다. 요청마다 조금씩 쌓이는 것은 짧은 시험의 출렁임에 묻힙니다. 그런 문제는 운영에서 며칠이 지나서야 처음 드러나기도 합니다.
그래서 같은 부하를 오래 끊지 않고 걸어 두는 시험이 따로 필요했습니다. 몇 시간이나 며칠에 걸쳐 드러나는 메모리 누수는 이렇게 오래 거는 시험이라야 보입니다. 이 시험을 소크 테스트라고 부릅니다. 오래 버티는 힘을 본다는 뜻에서 내구 테스트라고도 부릅니다.
예시
k6 의 stages 설정
k6 는 시험 시나리오를 자바스크립트로 적는 부하 생성 도구입니다. k6 에서 소크 테스트는 평균 부하 테스트(average-load test)의 한 변형입니다. 평균 부하 테스트는 이 글이 앞에서 부하 테스트라 부른 시험과 같은 것입니다.
부하의 모양은 options 객체의 stages 에 적습니다. 한 줄이 앞에서 말한 구간 하나입니다.
export const options = {
// Key configurations for Soak test in this section
stages: [
{ duration: '5m', target: 100 }, // traffic ramp-up from 1 to 100 users over 5 minutes.
{ duration: '8h', target: 100 }, // stay at 100 users for 8 hours!!!
{ duration: '5m', target: 0 }, // ramp-down to 0 users
],
};
첫 줄은 5분 동안 가상 사용자를 1명에서 100명까지 올립니다. 둘째 줄은 100명을 8시간 유지합니다. 셋째 줄은 5분에 걸쳐 0명으로 내립니다.
평균 부하 테스트도 꼭대기는 100명입니다. 소크 테스트는 그 꼭대기를 몇 분 대신 8시간 유지합니다. 그래서 되도록 평균 부하 테스트 스크립트를 고치지 않고 재사용합니다. 바꾸는 값은 유지 시간뿐입니다.
길이는 다른 어느 시험보다 눈에 띄게 길게 잡습니다. 흔히 드는 값 몇 가지는 3 · 4 · 8 · 12 · 24시간, 그리고 48시간에서 72시간입니다.
gRPC 의 rpc_soak 와 channel_soak
원격 프로시저 호출, 곧 RPC(Remote Procedure Call)는 다른 기계에 있는 함수를 내 함수처럼 부르는 방식입니다. gRPC 는 이 호출을 주고받는 라이브러리입니다. gRPC 프로젝트는 언어별 구현이 서로 맞물려 도는지 보는 상호운용 시험 명세를 둡니다. 그 안에 소크 시험이 둘 있습니다.
채널은 클라이언트가 서버 쪽으로 맺어 두고 RPC 를 실어 보내는 연결입니다. rpc_soak 는 같은
채널 위로 RPC 를 잇달아 보냅니다. channel_soak 는 RPC 를 보내기 직전에 채널을
새로 만듭니다. RPC 가 끝나면 그 채널을 바로 없앱니다.
두 시험은 길이를 RPC 횟수로 정합니다. 명령줄 플래그와 기본값은 이렇습니다.
| 플래그 | 정하는 것 | 기본값 |
|---|---|---|
soak_iterations |
보낼 RPC 의 총 횟수 | 10 |
soak_max_failures |
실패를 몇 번까지 봐주나. 이 수까지는 통과다 | 0 |
soak_per_iteration_max_acceptable_latency_ms |
RPC 하나를 성공으로 치는 지연 상한 | 1000 |
soak_overall_timeout_seconds |
이 시간이 지나도 횟수를 못 채우면 멈추고 실패한다 | 지연 상한 × soak_iterations |
soak_min_time_ms_between_rpcs |
잇단 두 RPC 사이의 최소 간격(밀리초) | 적혀 있지 않다 |
soak_num_threads |
RPC 를 동시에 보내는 스레드 수 | 1 |
기본값대로 두면 RPC 열 번으로 끝납니다. 길이는 soak_iterations 가 정합니다. 오래 걸려면 이
횟수를 키웁니다.
전체 제한 시간의 기본값은 밀리초 단위인 지연 상한에 횟수를 곱한 값입니다. 플래그 이름은 초 단위입니다. 이 곱을 초로 읽을지 밀리초로 읽을지는 적혀 있지 않습니다(미확인).
판정은 RPC 를 다 보낸 뒤 결과를 한 번 더 훑어서 합니다. 정한 횟수를 다 채우지 못했으면
실패입니다. 성공이 아닌 상태로 끝났거나 지연 상한을 넘긴 RPC 의 수가 soak_max_failures 를
넘어도 실패입니다.
channel_soak 는 채널을 만드는 시간까지 그 RPC 의 지연에 넣습니다. 채널을 닫는 시간은 넣지
않습니다. 채널을 닫는 방식이 언어마다 크게 다르기 때문입니다.
권하는 것도 있습니다. 필수는 아닙니다. RPC 마다 걸린 밀리초를 기록에 남깁니다. 시험 끝에는 지연을 몇 개의 값으로 줄여 남깁니다.
그 값이 백분위수입니다. 백분위수는 값을 작은 것부터 세웠을 때 몇 퍼센트 지점에 오는 값입니다. 지연이 100개라면 90번째 백분위수는 짧은 쪽부터 90번째 값입니다. 중앙값은 한가운데 값입니다.
백분위수는 지연을 구간별 개수로 모은 히스토그램에서 뽑습니다. 예를 들면 중앙값 · 90번째 백분위수 · 최댓값처럼 눈여겨볼 값을 남깁니다.
k6 설정은 길이를 시간으로 정합니다. gRPC 의 두 소크 시험은 길이를 횟수로 정합니다. 시간은 상한으로만 둡니다.
동작
소크 테스트는 한 시점의 값보다 시간에 따른 추세로 합격을 매깁니다. 성능과 자원 사용이 시간이 지나도 안정되거나 예상한 출렁임 안에 머물면 통과입니다. 이 절은 서비스 하나에 그 판정을 내리기까지의 순서를 따라갑니다.
돌리기 전에 갖출 것
소크 테스트는 첫 시험으로 돌리지 않습니다. 작은 부하로 기본 동작만 확인하는 스모크 테스트와 부하 테스트를 먼저 돌립니다. 시험마다 드러내는 문제가 다르기 때문입니다. 소크 테스트를 먼저 돌리면 혼란과 자원 낭비가 생길 수 있습니다.
시험 환경은 운영과 닮게 꾸립니다. 운영만큼의 자원을 다 붙여서 돌립니다.
시험을 처음 갖추는 팀이라면 부하 테스트부터 시작합니다. 위험과 경험이 쌓이는 대로 스트레스 테스트로 넓힙니다. 부하를 짧게 확 올렸다 내리는 스파이크 테스트도 이때 넓히는 갈래입니다. 소크 테스트는 그 뒤에 더합니다.
한 번 도는 순서
먼저 평균만큼의 사용자나 처리량에 닿을 때까지 부하를 올립니다. 그 부하를 다른 시험보다 훨씬 길게 유지합니다. 그동안 아래 「재는 값」의 값들을 같은 간격으로 적습니다. 끝나면 시험에 따라 바로 멈추거나 천천히 내립니다.
판정은 달라진 값이 있는지에서 갈립니다. 달라진 값은 예상한 출렁임을 넘어 움직인 값입니다. 시간이 갈수록 서서히 오르는 값이 여기에 듭니다. 어느 때 갑자기 바뀐 값도 듭니다.
flowchart TD
A["부하를 올려 오래 유지하며 값을 적는다"] --> B{"달라진 값이 있나"}
B -->|"없다"| C["통과"]
B -->|"있다"| D["그 자원을 쥔 코드를 찾아 고친다"]
D -->|"같은 길이로 다시 건다"| A
재는 값
소크 테스트 동안 같은 간격으로 적어 두는 값은 아래 여덟입니다. 오른쪽 칸은 그 값이 어떻게 달라지면 의심하나입니다.
| 값 | 무엇을 보나 |
|---|---|
| 메모리 사용량 | 치운 직후의 바닥이 오르나 |
| CPU(Central Processing Unit, 중앙 처리 장치) 사용량 | 부하가 같아도 오르나 |
| 네트워크 사용량 | 같은 요청량에 오가는 양이 늘어나나 |
| 디스크 사용량 | 줄지 않고 차오르기만 하나 |
| 열린 연결 수와 파일 수 | 닫히지 않고 늘기만 하나 |
| 쓰는 클라우드 자원의 양 | 시간이 갈수록 불어나나 |
| 응답 시간의 오래 걸린 쪽 | 뒤로 갈수록 더 오래 걸리나 |
| 오류율 | 뒤로 갈수록 실패가 늘어나나 |
응답 시간은 평균 대신 99번째 백분위수처럼 오래 걸린 쪽 끝의 값을 봅니다. 가끔 길게 멈추는 문제는 평균에는 잘 안 드러납니다. 오래 걸린 쪽을 보는 값에 먼저 드러납니다.
서서히 나빠지는 것만 보지 않습니다. 응답 시간이나 자원 사용이 갑자기 바뀌는 때도 봅니다. 갑자기 좋아진 쪽도 달라진 값으로 셉니다.
표의 값은 두 군데서 나옵니다. 응답 시간과 오류율은 부하를 거는 도구가 셉니다. 메모리 · CPU · 네트워크 · 디스크는 서버를 지켜보는 모니터링이 셉니다. 둘을 같은 시간 축에 놓아야 응답이 늘어진 때와 자원이 차오른 때를 맞춰 볼 수 있습니다.
그래서 여러 시험 가운데 소크 테스트에서 서버 쪽 모니터링을 특히 강하게 권합니다. 디스크 · 메모리 · CPU · 네트워크 넷은 서로 얽혀 있습니다. 한 곳이 나빠지면 다른 곳의 성능도 따라 나빠집니다.
요청률을 고정하는 까닭
요청률은 1초에 보내는 요청의 수입니다. 부하를 거는 도구가 요청률 대신 동시 사용자 수를 고정하면 저하가 다른 모습으로 보입니다.
서버가 늘어지면 부하를 거는 도구의 스레드가 응답을 기다리느라 묶입니다. 그만큼 도구가 보내는 요청률이 내려갑니다. 저하가 처리한 요청 수의 감소로만 보입니다.
초당 요청 수를 고정하는 도구는 서버가 늘어질 때 동시에 보내는 요청을 늘려 같은 요청률을 맞춥니다. 그러면 저하가 응답 시간과 실패한 요청으로 드러납니다. 실패도 더 일찍 보입니다.
flowchart TD
A["서버가 늘어진다"] --> B{"도구가 무엇을 고정하나"}
B -->|"동시 사용자 수"| C["도구의 스레드가 묶여 요청률이 내려간다"]
C --> D["저하가 처리한 요청 수의 감소로만 보인다"]
B -->|"요청률"| E["동시에 보내는 요청을 늘려 요청률을 맞춘다"]
E --> F["저하가 응답 시간과 실패한 요청으로 드러난다"]
앞의 k6 예시는 가상 사용자 수를 100명으로 고정합니다. 동시 사용자 수를 고정하는 꼴입니다. 가장 믿을 만한 결과를 바란다면 요청률을 고정할 수 있는 도구를 고릅니다.
달라진 값을 찾았을 때
달라진 값을 찾으면 그 자원을 누가 쥐고 있는지 좇습니다. 메모리라면 어떤 객체가 쌓였는지를 봅니다. 연결이라면 어느 코드가 빌린 연결을 안 돌려줬는지를 봅니다.
고친 뒤에는 같은 길이로 다시 겁니다. 쌓이는 문제는 짧게 돌려서는 고쳐졌는지도 안 보이기 때문입니다.
시간과 환경이라는 대가
가장 큰 대가는 시간입니다. 결과가 길게는 며칠 뒤에 나옵니다. 코드를 고칠 때마다 돌리기는 어렵습니다.
그동안 시험 환경도 붙잡힙니다. 운영과 닮은 환경을 자원까지 다 붙인 채 며칠씩 켜 두는 비용이 듭니다.
몇 분 돌고 끝나는 프로그램에는 쌓일 시간이 없습니다. 이런 프로그램에는 이 시험이 드러낼 것이 적습니다. 오래 떠 있는 서버일수록 드러낼 것이 많아집니다.
경계
센 부하를 오래 걸어도 소크 테스트인가. 평균보다 센 부하를 몇 시간 동안 유지한 시험을 두고 하는 물음입니다.
맞습니다. 시간이 지나며 쌓이는 변화를 보려고 오래 걸었다면 소크 테스트입니다. 이 시험을 가르는 축은 길이입니다.
거는 부하의 크기는 정의마다 조금씩 다릅니다. 대개는 평균이나 운영에서 예상하는 만큼을 겁니다. 시험 용어 표준인 ISTQB(International Software Testing Qualifications Board) 용어집은 「상당한 부하(significant load)」라고 적습니다. 같은 시험을 「꾸준히 높은 부하 시험(constant high load test)」이라고 부르기도 합니다.
길이에는 갈림이 없습니다. 어느 정의든 오래 걸어 시간에 따른 저하를 봅니다.
센 부하를 30분 남짓 거는 시험은 소크 테스트가 아닙니다. 스트레스 테스트입니다. 유지 시간이 부하 테스트보다 조금 긴 정도입니다. 그 시험이 묻는 것은 평소보다 센 부하를 견디나입니다.
관련 항목
소크 테스트를 분류해 둔 상위 체계
성능 테스트 · 비기능 테스트 · QA와 테스트 · ISTQB
소크 테스트와 목적이 갈리는 성능 시험
부하 테스트 · 평균 부하 테스트 · 스트레스 테스트 · 스파이크 테스트 · 스모크 테스트 · 벤치마크 · 용량 테스트 · 회귀 테스트
오래 걸어야 드러나는 고장
메모리 누수 · 자원 누수 · 커넥션 누수 · 커넥션 풀 고갈 · 파일 디스크립터 고갈 · 메모리 단편화 · 스레드 누수 · 메모리 부족 · 백프레셔 · 데이터 포화 · 스토리지 고갈
시간에 따라 추세를 읽는 지표
응답 시간 · 백분위수 · 꼬리 지연 · 히스토그램 · 오류율 · 처리량 · 메모리 사용량 · 가비지 컬렉션 · 사용률
오래 도는 부하를 만들어 거는 도구와 설정값
k6 · Locust · JMeter · 부하 생성기 · 가상 사용자 · 램프업 · 임계값 · 초당 요청 수
rpc_soak 가 기대는 gRPC 구성 요소
쌓이는 원인을 좇을 때 쓰는 도구
모니터링 · 메트릭 · 힙 덤프 · 프로파일링 · 대시보드 · 경보 · 관측성
긴 시험 도중에 돌아오는 정기 작업
배치 처리 · 로그 로테이션 · 크론 · 토큰 만료 · 인증서 갱신
시험 결과를 받아 쓰는 운영 작업
다른 이름: soak testing · soak test · 내구 테스트 · endurance testing · 소크 시험 · stamina test