요약
- 서버별로 "지금 처리 중인 요청이 몇 개인가"를 재는 지표가 없어서
응답시간 합 ÷ 시간으로 대신 구하는 경우가 있다. - 이 값은
요청 수 × 평균 응답시간 ÷ 시간과 같다. 로드밸런서가 요청을 똑같이 나눠서 요청 수가 같으면, 이 값은 평균 응답시간에 그냥 비례한다. - 그래서 "처리 중인 요청도 3배, 평균 응답시간도 3배"라고 쓰면 증거가 두 개가 아니라 하나다.
본문
어쩌다 이걸 쓰게 되나
서버가 지금 몇 개의 요청을 붙들고 있는지 직접 재는 지표는 잘 없다. 그래서 리틀의 법칙으로 대신 구한다.
처리 중인 요청 수 = 응답시간 합 ÷ 창 길이
= 요청 수 × 평균 응답시간 ÷ 창 길이
식을 풀어 쓰면 바로 보인다. 요청 수가 고정이면 앞의 값은 평균 응답시간에 상수를 곱한 것뿐이다.
실제로 두 서버를 비교해봤다. 로드밸런서가 요청을 똑같이 나눠주고 있던 상태다.
| 응답시간 비 | 처리 중인 요청 수 비 | |
|---|---|---|
| 사례 A | 5.47 | 5.47 |
| 사례 B | 2.65 | 2.65 |
소수점 둘째 자리까지 같다. 다른 창에서는 3.1109 대 3.1105였다. 같은 수다.
"밀려서 느려졌다"의 근거로 쓸 수 없다
리틀의 법칙은 큐가 있든 없든 항상 맞는 식이다. 느린 DB를 그냥 기다리기만 해도 처리 중인 요청 수는 늘어난다. 요청이 밀려서 늘어난 것과 구분이 안 된다.
순서를 거꾸로 읽기도 쉽다. 실제로는 "서버가 느리니까 처리 중인 요청이 많아진 것"인데, 이 숫자를 보고 "요청이 쌓여서 서버가 느려졌다"고 읽게 된다.
그럼 뭘 봐야 하나
응답시간에서 계산하지 않은 값을 찾는다.
- OS가 재는 CPU 시간. 응답시간과 상관없이 따로 측정되므로, 이 순위가 응답시간 순위와 같으면 진짜 차이가 있다는 증거가 된다.
- 로드밸런서가 잰 시간과 앱이 잰 시간의 차이. ALB의
TargetResponseTime은 요청을 보낸 순간부터 응답이 올 때까지를 잰다. 요청이 소켓이나 프로세스 앞에서 기다렸다면 그 시간이 여기 들어간다. 앱이 잰 시간과 차이가 작으면(관찰한 사례에서는 171.0ms 대 163ms, 8ms 차이) 기다린 구간이 없다는 뜻이다. - 분포 모양. 요청이 밀려서 생긴 문제면 꼬리만 길어진다. p50이 1.80배, p99가 2.21배처럼 비슷하게 밀렸다면 큐가 아니라 처리 속도 자체가 느린 것이다.
남는 교훈
지표 두 개를 나란히 놓기 전에, 하나가 다른 하나를 계산해서 만든 것은 아닌지 본다. 합계나 평균을 조합해 만든 값은 원래 지표와 따로 놀지 않는 경우가 많다.
관련 노트
- 버퍼풀 메모리 축소로 인한 read latency 하락은 착시이다
- 스냅샷에서 복원한 RDS의 ReadLatency 수십 ms는 IOPS 부족이 아니라 S3 lazy loading이다
- 평균 응답 속도보다 p99가 중요한 이유