소크 테스트
고친 사람 github-actions[bot]
소크 테스트는 평소만큼의 부하를 몇 시간에서 며칠 동안 끊지 않고 걸어 둡니다. 짧게 돌리면 멀쩡하다가 시간이 지나야 쌓여서 터지는 문제를 운영에 내보내기 전에 찾아냅니다. 조금씩 새는 메모리가 대표적인 표적입니다.
쉽고 빠른 이해
평소만큼의 요청을 오래 걸어 두는 시험입니다. 그동안 서버가 달라지는지 지켜봅니다. 예를 들면 평일 낮만큼의 요청을 주말 내내 흘려 둡니다.
서버는 운영에서 며칠씩 재시작 없이 돕니다. 요청마다 조금씩 새는 문제는 10분짜리 시험에서는 티가 안 납니다. 그러다 배포하고 며칠 뒤 한밤중에 서버를 쓰러뜨리기도 합니다. 그 며칠을 미리 겪어 보는 것이 이 시험입니다.
어떻게 하나:
- 평소 수준까지 부하를 올리고 오래 유지합니다
- 메모리 · 연결 수 · 디스크 · 응답 시간을 같은 간격으로 적습니다
- 처음과 끝을 견줘 꾸준히 오르는 값이 있는지 봅니다
오래 돌려야 해서 결과가 늦게 나옵니다. 그동안 시험 환경을 붙잡아 두는 비용이 대가입니다.
상세
수도꼭지를 잠깐 틀어 볼 때는 멀쩡해 보이던 배관이 있습니다. 밤새 물을 흘려 두면 그 배관의 이음새 밑 바닥이 젖어 있곤 합니다. 한 방울씩 새는 곳은 오래 두어야 보입니다.
소크 테스트는 성능 테스트의 한 갈래입니다. 예상하는 평소 수준의 부하를 긴 시간 유지합니다. 그동안 시스템이 시간에 따라 달라지는지를 봅니다. 평일 낮만큼의 요청을 금요일 저녁부터 월요일 아침까지 흘려 두는 식입니다.
부하는 시스템에 들어오는 일감의 양입니다. 초당 요청 수로 세기도 하고, 사람 한 명의 흐름을 흉내 내는 가상 사용자를 몇 명 띄웠는지로 세기도 합니다. 소크 테스트는 이 양을 키우지 않습니다. 같은 양을 두고 시간만 늘립니다.
soak 는 물에 푹 담가 둔다는 뜻의 영어 낱말입니다. 시스템을 부하 속에 오래 담가 둔다는 그림에서 온 이름입니다. 오래 버티는 힘을 본다는 뜻에서 내구 테스트(endurance testing)라고도 부릅니다.
짧은 시험이 놓치는 문제
부하를 거는 시험은 흔히 짧게 돌립니다. 목표 부하까지 올리고 잠시 유지한 뒤 내립니다.
그 짧은 동안 시스템은 막 켜진 상태에 가깝습니다. 메모리는 넉넉합니다. 로그 파일은 작습니다. 연결도 새것입니다.
운영은 다릅니다. 서버는 배포 뒤 며칠에서 몇 주씩 재시작 없이 돕니다. 요청 하나마다 아주 조금씩 쌓이는 것이 있으면 그 기간 내내 불어납니다.
요청 하나마다 메모리를 1킬로바이트씩 돌려주지 않는 서버를 가정해 봅니다. 초당 요청이 100개면 쌓이는 양은 시험 길이에 따라 이렇게 달라집니다.
| 시험 길이 | 쌓인 메모리 |
|---|---|
| 10분 | 약 60메가바이트 |
| 1시간 | 약 360메가바이트 |
| 하루 | 약 8.6기가바이트 |
10분짜리 시험에서 60메가바이트는 평소 사용량의 출렁임에 묻혀 안 보입니다. 하루가 지나면 8기가바이트를 넘습니다. 프로세스에 준 메모리 한도를 넘기면 프로세스가 죽습니다.
이런 문제는 배포하고 며칠 뒤 한밤중에 서버가 쓰러지면서 처음 드러나기도 합니다. 소크 테스트는 그런 고장을 운영에 내보내기 전에 시험 환경에서 먼저 일으켜 봅니다. 오래 걸어 두면 작은 기울기가 눈에 보이는 크기로 자랍니다.
오래 걸어야 드러나는 고장
오래 걸어야만 보이는 고장은 대개 무언가가 쌓이는 모양입니다. 쓰고 나서 돌려줘야 할 자원을 돌려주지 않는 것을 누수라고 부릅니다. 누수는 한 번에는 티가 안 나고 모여야 보입니다.
| 쌓이는 것 | 오래 지나면 보이는 모습 |
|---|---|
| 메모리 누수 — 다 쓴 객체를 어딘가에서 계속 붙잡고 있다 | 메모리가 꾸준히 올라 결국 프로세스가 죽는다 |
| 연결 누수 — 데이터베이스 연결을 커넥션 풀에서 빌리고 안 돌려준다 | 풀에 남은 연결이 바닥나 요청이 기다리다 실패한다 |
| 파일 누수 — 연 파일이나 소켓을 안 닫는다 | 프로세스가 열 수 있는 개수의 상한에 닿아 새 연결을 못 받는다 |
| 지우는 규칙이 없는 캐시나 목록 | 항목이 늘기만 해서 메모리가 계속 오른다 |
| 로그 파일과 임시 파일 | 디스크가 차서 파일 쓰기가 실패한다 |
표의 다섯은 모양이 같습니다. 한 번에 새는 양은 작습니다. 시간이 곱해져야 커집니다. 그래서 이 시험에서는 거는 부하의 크기보다 거는 시간의 길이가 결과를 가릅니다.
둘째 줄의 커넥션 풀은 미리 열어 둔 데이터베이스 연결을 여러 요청에 돌려 가며 빌려주는 묶음입니다. 연결을 여는 비용을 요청마다 치르지 않으려고 둡니다.
셋째 줄의 개수는 파일 디스크립터로 셉니다. 운영체제는 프로세스가 연 파일과 소켓마다 번호를 하나씩 붙여 줍니다. 한 프로세스가 가질 수 있는 번호 수에는 상한이 있습니다.
쌓임이 프로세스를 죽이는 모습으로만 드러나지는 않습니다. 응답이 서서히 느려지는 모습으로도 나타납니다.
가비지 컬렉션은 안 쓰는 메모리를 언어의 실행 환경이 알아서 치우는 일입니다. 붙잡힌 객체가 늘면 치울 때마다 훑을 것이 많아집니다. 치우는 동안 요청 처리가 멈추는 시간도 따라서 길어질 수 있습니다.
시간이 지나야 찾아오는 사건도 있습니다. 하루 한 번 도는 배치 처리, 정해진 주기마다 로그 파일을 갈아 끼우는 로그 로테이션, 몇 시간마다 만료되는 인증 토큰이 그렇습니다. 시험이 그 시각을 한 번도 안 지나면 그때 무슨 일이 생기는지 모른 채 끝납니다.
메모리가 그리는 두 모양
시간에 따라 메모리 사용량을 그려 보면 누수가 모양으로 드러납니다. 아래는 예로 든 두 서버를 여덟 시간 동안 잰 모습입니다.
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]
한 선은 톱니처럼 오르내리다 제자리로 돌아옵니다. 치우는 일이 제때 되는 서버입니다. 다른 선도 오르내리지만 바닥이 한 칸씩 올라갑니다. 무언가를 붙잡고 놓지 않는 서버입니다.
그래서 봐야 할 것은 꼭대기가 아니라 바닥입니다. 치운 직후에도 남는 양이 시간에 따라 오르면 누수를 의심합니다. 짧은 시험에서는 톱니 한두 개만 보여서 이 두 모양을 가를 수 없습니다.
재는 값과 판정
소크 테스트의 합격선은 한 값이 아니라 추세입니다. 시험 내내 같은 간격으로 값을 적어 둡니다. 그리고 처음 한 시간과 마지막 한 시간을 나란히 놓습니다.
둘이 같은 모양이면 통과입니다. 부하는 처음부터 끝까지 같습니다. 그런데도 한쪽으로 꾸준히 흐르는 값이 있으면 그 값이 단서입니다.
응답 시간을 볼 때는 평균 대신 가장 오래 걸린 쪽 몇 퍼센트를 봅니다. 요청 100개를 걸린 시간이 짧은 것부터 세웠을 때 99번째 값을 99번째 백분위수라고 합니다. 가끔 길게 멈추는 문제는 평균에는 잘 안 보입니다. 이런 오래 걸린 쪽 값에 먼저 보입니다.
| 값 | 무엇을 보나 |
|---|---|
| 메모리 사용량 | 치운 직후의 바닥이 오르나 |
| 열린 연결 수와 파일 수 | 부하는 같은데 개수가 늘어나나 |
| 디스크 사용량 | 줄지 않고 차오르기만 하나 |
| 응답 시간의 오래 걸린 쪽 | 뒤로 갈수록 더 느려지나 |
| 오류율 | 뒤로 갈수록 실패가 늘어나나 |
표의 값은 두 군데서 나옵니다. 응답 시간과 오류율은 부하를 거는 도구가 셉니다. 메모리와 연결 수와 디스크는 서버를 지켜보는 모니터링이 셉니다. 둘을 같은 시간 축에 놓아야 느려진 때와 차오른 때를 맞춰 볼 수 있습니다.
부하의 크기와 시험 길이
거는 부하는 예상 최대가 아니라 평소 수준입니다. 예상 범위를 넘겨 무너질 때까지 미는 시험은 스트레스 테스트라고 합니다. 그렇게 밀면 다른 고장이 먼저 터집니다. 그러면 천천히 쌓이는 것을 볼 틈이 없습니다.
평소 수준을 고르는 까닭이 하나 더 있습니다. 운영에서 서버가 가장 오래 머무는 상태가 평소 수준입니다. 그 상태를 길게 재현하는 것이 이 시험의 목적입니다.
부하는 처음부터 한 번에 걸지 않습니다. 몇 분에 걸쳐 목표까지 올립니다. 이 오르는 구간을 램프업이라고 합니다. 그 뒤로는 시험이 끝날 때까지 같은 부하를 유지합니다.
길이는 쌓이는 속도가 정합니다. 새는 속도가 느릴수록 오래 걸어야 눈에 띄는 크기가 됩니다. 흔히 몇 시간에서 며칠을 잡습니다.
길이를 정할 때 기준은 둘입니다. 운영에서 재시작 없이 도는 기간을 흉내 낼 만큼 겁니다. 그리고 하루 한 번 도는 정기 작업을 한 번 이상 지날 만큼 겁니다.
한 번 도는 순서
시험 한 번이 도는 순서를 그림으로 보면 이렇습니다.
flowchart TD
A["평소 수준까지 부하를 올린다"] --> B["긴 시간 유지하며 값을 적는다"]
B --> C["처음과 끝을 견준다"]
C --> D{"꾸준히 오르는 값이 있나"}
D -->|"없다"| E["통과"]
D -->|"있다"| F["그 자원을 붙잡는 코드를 찾아 고친다"]
F --> A
오르는 값을 찾았을 때 할 일은 그 자원을 누가 쥐고 있는지 좇는 것입니다. 메모리라면 어떤 객체가 쌓였는지를 봅니다. 연결이라면 어느 코드가 빌리고 안 돌려줬는지를 봅니다.
고친 뒤에는 같은 길이로 다시 걸어야 합니다. 쌓이는 문제는 짧게 돌려서는 고쳐졌는지도 안 보이기 때문입니다.
대가와 쓰지 않는 때
가장 큰 대가는 시간입니다. 결과가 몇 시간에서 며칠 뒤에 나오니 코드를 고칠 때마다 돌릴 수는 없습니다. 그래서 큰 배포를 앞두고 돌리거나 밤과 주말마다 정기로 돌리는 식으로 씁니다.
그동안 시험 환경도 붙잡힙니다. 운영과 닮은 환경을 며칠씩 켜 두는 비용이 듭니다. 시험 환경이 운영과 덜 닮을수록 쌓이는 속도도 운영과 달라집니다.
몇 분 돌고 끝나는 배치나 명령줄 도구에는 쌓일 시간이 없습니다. 이런 프로그램에는 이 시험의 값이 작습니다. 오래 떠 있는 서버일수록 값이 커집니다.
서버를 정해진 주기로 재시작해서 쌓인 것을 비우는 운영도 있습니다. 이 방법은 쌓인 것을 비울 뿐 새는 곳을 막지는 않습니다. 재시작 주기보다 빨리 차오르면 그 사이에 쓰러집니다.
이웃한 시험과 가르는 선
부하를 걸어 성능을 보는 시험은 여럿입니다. 거는 방법도 비슷합니다. 가르는 기준은 셋입니다. 부하를 얼마나 거나, 얼마나 오래 거나, 무엇을 알아내려 하나입니다.
| 시험 | 얼마나 거나 | 얼마나 오래 | 무엇을 알아내나 |
|---|---|---|---|
| 부하 테스트 | 예상 범위 안 | 잠시 유지 | 그 범위를 감당하나 |
| 스트레스 테스트 | 예상 범위 밖 | 무너질 때까지 | 어디서 어떻게 무너지나 |
| 스파이크 테스트 | 짧게 확 올렸다 내림 | 아주 짧게 | 급격한 변화를 견디나 |
| 소크 테스트 | 평소 수준 | 몇 시간에서 며칠 | 시간이 갈수록 쌓이는 것이 있나 |
표에서 보듯 소크 테스트만 시험의 길이가 핵심입니다. 나머지 셋은 부하의 크기나 변화를 봅니다.
관련 항목
소크 테스트와 목적이 갈리는 성능 시험
부하 테스트 · 스트레스 테스트 · 스파이크 테스트 · 스모크 테스트 · 성능 테스트 · 벤치마크 · 용량 테스트 · 회귀 테스트
오래 걸어야 드러나는 고장
메모리 누수 · 자원 누수 · 커넥션 누수 · 커넥션 풀 고갈 · 파일 디스크립터 고갈 · 메모리 단편화 · 스레드 누수 · 메모리 부족
시간에 따라 추세를 읽는 지표
응답 시간 · 백분위수 · 꼬리 지연 · 오류율 · 처리량 · 메모리 사용량 · 가비지 컬렉션 · 사용률
오래 도는 부하를 만들어 거는 도구와 설정값
k6 · Locust · JMeter · 부하 생성기 · 가상 사용자 · 램프업 · 임계값 · 초당 요청 수
쌓이는 원인을 좇을 때 쓰는 도구
모니터링 · 메트릭 · 힙 덤프 · 프로파일링 · 대시보드 · 경보 · 관측성
시험이 한 번은 지나야 할 정기 작업
배치 처리 · 로그 로테이션 · 크론 · 토큰 만료 · 인증서 갱신
시험 결과를 받아 쓰는 운영 작업
다른 이름: soak testing · soak test · 내구 테스트 · endurance testing · 소크 시험