LabHub
배우기 러닝패스 코스

부하 테스트 · 좌표 누락 · 이론

가장 나쁜 순간이 결과에서 사라진다

LabHub 에서 이어서 보기

한 줄 요약

닫힌 루프 부하 발생기는 서버가 멈춰 있는 동안 요청을 아예 보내지 않는다. 보내지 않은 요청은 지연 분포에 남지 않고, 그래서 가장 나쁜 순간이 결과에서 통째로 사라진다. 이것을 좌표 누락(coordinated omission)이라고 부른다.

왜 이게 필요했나

결제 API 앞에서 이런 보고서를 받은 적이 있다. "1분 동안 12,000건을 보냈고 p99 는 60밀리초였습니다. 목표 500밀리초를 크게 밑돕니다." 그런데 같은 시간 서버 로그에는 2초짜리 정지가 한 번 찍혀 있었다. 두 문장이 같은 자료에서 나왔다.

이상한 일이 아니다. 부하 발생기가 동시성 1로 돌고 있었다면, 서버가 2초를 붙잡고 있는 동안 그 발생기는 응답을 기다리느라 아무것도 보내지 않았다. 2초가 끝나고 돌아온 응답 하나만 2초로 기록된다. 표본 200개 중 1개다. 200개에서 1개는 99.5백분위라서, p99 자리에는 그 옆의 멀쩡한 50밀리초가 앉는다.

진짜 사용자는 그렇게 행동하지 않는다. 사용자는 서버가 멈췄다고 요청을 미루지 않는다. 2초 동안에도 계속 들어오고, 그 요청들은 전부 줄 뒤에 쌓여 2초에 가까운 시간을 기다린다. 즉 실제로 나쁜 경험을 한 요청 수십 개가 측정에서 빠진 채로 보고서가 만들어진 것이다.

이 결함이 무서운 이유는 실패하는 방향이 늘 같기 때문이다. 좌표 누락은 결과를 무작위로 흔드는 것이 아니라 항상 좋아 보이는 쪽으로만 틀리게 만든다. 그래서 아무도 의심하지 않고, 출시 심사를 통과하고, 장애가 난 다음에야 "시험에서는 괜찮았는데" 라는 말이 나온다.

어떻게 동작하나

부하를 거는 방식은 크게 두 가지다.

| 모델 | 다음 요청을 보내는 시점 | 서버가 느려지면 |
| --- | --- | --- |
| 닫힌 루프(closed) | 앞 요청의 응답을 받은 뒤 | 보내는 양이 저절로 줄어든다 |
| 열린 모델(open) | 미리 정한 시각이 되면 | 보내는 양은 그대로, 줄이 길어진다 |

닫힌 루프는 동시 사용자 수를 흉내 낼 때 맞는 모델이다. 문제는 그 성질이 측정에도 그대로 스며든다는 것이다. 서버가 느려지면 부하도 같이 줄어드니, 발생기와 서버가 짜고서(coordinated) 나쁜 구간을 함께 건너뛴다. 이름이 여기서 왔다.

고치는 방법은 두 갈래다. 첫째는 처음부터 열린 모델로 재는 것이다. 요청 i 의 의도한 발사 시각을 미리 고정해 두고, 지연을 응답 시각 − 의도한 발사 시각 으로 잰다. 이 값을 보정 지연(corrected latency)이라고 한다. 발사가 늦어졌으면 늦어진 만큼이 지연에 그대로 더해진다.

둘째는 이미 재 놓은 닫힌 루프 표본을 사후에 메우는 것이다. HdrHistogram 의 recordValueWithExpectedInterval 이 하는 일이 이것이다 — 표본 하나의 값이 기대 간격보다 크면, 그 사이에 보냈어야 했는데 보내지 못한 요청들을 기대 간격씩 빼 가며 만들어 넣는다. 지연 2.0초짜리 표본 하나는 기대 간격 80밀리초에서 1.92초, 1.84초, ... 하는 식으로 25개의 표본을 더 만들어 낸다. wrk2 가 wrk 를 갈라 나온 이유도 이 보정을 넣기 위해서였다.

두 방법의 결과는 같은 쪽을 가리킨다. 그래서 어느 쪽이든 하나는 반드시 해야 한다. 다만 사후 보정은 기대 간격을 알아야 쓸 수 있다는 한계가 있다. 목표 비율을 정해 놓고 건 시험이라면 기대 간격은 그 비율의 역수지만, 비율 없이 동시성만 정해 놓고 건 시험에는 애초에 기대 간격이라는 것이 없다. 그런 시험 결과는 사후에 고칠 방법이 없고, 다시 재는 수밖에 없다.

현장에서 만나는 모습

가장 흔한 모습은 "최댓값만 크고 p99 는 작은" 결과다. 이 조합을 보면 먼저 좌표 누락을 의심해야 한다. 최댓값이 p99 의 서른 배라는 것은 나쁜 구간의 표본이 한두 개밖에 안 잡혔다는 뜻이고, 그건 대개 그 구간에 요청을 못 보냈기 때문이다.

두 번째는 "동시성을 올렸는데 p99 가 오히려 좋아졌다" 는 결과다. 동시성이 높으면 멈춘 구간에도 다른 워커의 요청이 몇 개는 들어가 줄을 서므로 표본이 조금 생기긴 하지만, 여전히 실제 도착률보다는 훨씬 적다. 숫자가 좋아진 것이 아니라 덜 틀렸을 뿐이다.

세 번째는 도구를 바꿨더니 숫자가 나빠졌다는 신고다. 닫힌 루프 도구에서 열린 모델 도구로 옮기면 p99 가 몇 배로 뛴다. 그때 "새 도구가 부정확하다" 고 결론 내리고 되돌리는 일이 실제로 있었다. 되돌린 쪽이 틀렸다 — 새 숫자가 처음으로 맞는 숫자였다.

마지막으로, 보정을 하고 나면 SLA 판정이 뒤집히는 일이 자주 생긴다. 보정 전에는 통과, 보정 후에는 미달이다. 이때 필요한 것은 임계값을 손보는 것이 아니라 어느 숫자로 약속했는지를 다시 합의하는 것이다. 시험 보고서에 보정 여부를 적어 두지 않으면, 반년 뒤 아무도 그 숫자가 어느 쪽이었는지 모른다.

다음 실습에서 할 것

150번째 요청에서만 2초를 붙잡는 파이썬 서버를 띄우고, 같은 서버를 두 번 잰다. 한 번은 hey 로 닫힌 루프, 한 번은 80밀리초 간격 200건이 미리 적힌 고정 일정으로 열린 모델이다. 두 원본 파일에서 p50/p99/최댓값을 직접 다시 계산해 표로 놓고, 닫힌 루프 표본에 HdrHistogram 식 보정을 손으로 구현해 세 번째 분포를 만든다. 마지막으로 SLA 임계값 하나를 걸어 보정 전후로 판정이 뒤집히는 것을 확인하고, 다시는 이 함정에 빠지지 않도록 시험 설계 규칙을 파일로 남긴다.