샘플링이 장애 요청만 버린다 — 꼬리 샘플링(tail sampling)
추적 비용을 줄이려고 1% 만 남겼더니, 장애 때 찾는 실패 요청은 두세 건밖에 안 남았어요. 흔한 건 남고 드문 건 사라지거든요. 요청이 끝난 뒤 결과를 보고 남길 것을 고르는 법을 담았어요.
추적 비용이 부담돼서 샘플링을 켰어요. 전체의 1% 만 저장하기로요. 비용이 확 줄었고 평소에는 아무 문제가 없었어요.
그런데 장애가 나서 문제의 요청을 찾으려니 없어요. 실패한 요청 몇백 건 중에 저장된 게 두세 건이고, 그마저도 원하는 구간이 아니에요.
샘플링은 무작위로 버리는데, 우리가 찾는 건 무작위가 아니에요. 정상 요청 백만 건 중 1% 는 만 건이지만, 실패 요청 백 건 중 1% 는 한 건이에요.
비율이 같아도 남는 양이 완전히 달라요. 흔한 것은 충분히 남고, 드문 것은 사라져요. 그런데 조사에서 필요한 건 언제나 드문 쪽입니다.
가장 필요할 때 가장 없어요. 평소에 잘 돌던 도구가 정작 장애 때만 텅 비어 있는 거예요.
왜 그런가
샘플링의 목적은 비용을 줄이는 거예요. 추적이든 로그든 전부 저장하면 원본 트래픽만큼의 데이터가 쌓이고, 저장·전송·검색 비용이 서비스 비용을 넘길 수도 있어요.
그래서 일부만 남기는데, 문제는 무엇을 남길지 결정하는 시점이에요.
가장 단순한 방식은 요청이 시작될 때 주사위를 굴리는 거예요. 1% 확률로 "이건 저장한다"고 정하고 나머지는 버려요. 구현이 쉽고 오버헤드가 거의 없어요.
그런데 이 시점에는 그 요청이 성공할지 실패할지, 느릴지 빠를지 아무도 몰라요. 그래서 흥미로운 요청과 지루한 요청이 똑같은 확률로 버려져요.
우리가 실제로 보고 싶은 건 전체의 아주 작은 부분이에요. 실패한 것, 아주 느린 것, 드문 경로를 탄 것. 그런데 이것들은 정의상 소수라, 무작위 샘플링에서 살아남을 확률이 낮아요.
여기에 하나 더 있어요. 앞 편에서 본 대로 추적 문맥은 서비스를 넘어 다니는데, 샘플링 결정도 같이 넘어가야 해요. 서비스마다 따로 주사위를 굴리면 앞부분만 저장되고 뒷부분은 버려진 반쪽 추적이 생겨요.
코드로 보면
결과를 보고 나서 정한다:
시작할 때 주사위 — 성공/실패를 모르는 시점이다
if random() < 0.01: 저장
· 끝나고 나서 정한다 — 결과를 알고 고른다
오류가 났다 → 100% 저장
느리다(상위 백분위) → 100% 저장
드문 경로다 → 높은 비율로 저장
평범한 성공 → 0.1% 만 저장
· 저부하 구간은 통째로 남긴다
초당 10건짜리 엔드포인트를 1% 로 깎으면 사실상 0 이다
엔드포인트별로 최소 보장 건수를 둔다
· 결정은 문맥에 실어 전파한다
서비스마다 따로 굴리면 반쪽 추적이 생긴다✕ 문제 지점 · ✓ 해법 · 그대로 돌려 보기 전에 자기 환경에 맞게 고쳐 주세요.
실전에서는
핵심은 결정을 뒤로 미루는 것이에요.
요청이 끝난 뒤에 판단하면 성공/실패, 소요 시간, 지나간 경로를 다 알고 고를 수 있어요. 오류는 전부 남기고, 느린 것도 전부 남기고, 평범한 성공만 아주 낮은 비율로 남기는 식이죠. 저장량은 비슷한데 쓸모는 훨씬 커집니다.
대가가 있어요. 결정할 때까지 데이터를 들고 있어야 하니 메모리를 더 쓰고, 여러 서비스에 걸친 추적이라면 조각들을 한곳에 모아 판단해야 해서 구조가 복잡해져요. 그래도 대부분의 경우 그 값을 합니다.
둘을 섞는 방식도 실용적이에요. 시작할 때 낮은 비율로 무작위 표본을 확보하고(전체 분포를 보려면 편향 없는 표본이 필요해요), 그와 별개로 오류와 느린 요청은 무조건 남기는 거죠.
여기서 중요한 구분이 있어요. 표본은 분포를 보는 데 쓰고, 전수는 개별 사건을 조사하는 데 써요. 앞 편에서 본 백분위 같은 지표는 전수 집계에서 나와야 하고, 추적은 표본이어도 괜찮아요. 그런데 추적 표본으로 백분위를 계산하면 편향된 값이 나옵니다.
저부하 엔드포인트도 챙겨야 해요. 초당 만 건짜리를 1% 로 깎으면 초당 백 건이 남아 충분한데, 초당 10건짜리를 1% 로 깎으면 100초에 한 건이에요. 사실상 관측이 없는 거죠. 비율이 아니라 초당 최소 몇 건을 보장하는 방식이 낫습니다.
로그도 같은 문제를 가져요. 반복되는 오류를 전부 남기면 소음이 되지만, 샘플링하면 처음 보는 오류를 놓칠 수 있어요. 처음 몇 건은 남기고 그 뒤로는 세기만 하는 방식이 균형점입니다.
처방
- 샘플링 결정을 요청이 끝난 뒤로 미루세요. 결과를 알고 고르는 게 핵심입니다.
- 오류와 느린 요청은 무조건 남기세요. 조사에 실제로 쓰이는 게 이것들입니다.
- 낮은 비율의 무작위 표본도 함께 유지하세요. 편향 없는 분포를 보려면 필요합니다.
- 비율이 아니라 초당 최소 건수를 보장하세요. 저부하 엔드포인트가 사라지는 걸 막습니다.
- 샘플링 결정을 문맥에 실어 전파하세요. 서비스마다 따로 굴리면 반쪽 추적이 생깁니다.
- 백분위 지표는 전수 집계에서 내세요. 추적 표본으로 계산하면 편향됩니다.
- 로그는 처음 몇 건만 남기고 나머지는 세기만 하세요. 소음과 유실 사이의 균형점입니다.
- 장애 회고에서 "그 요청의 추적이 있었나"를 물으세요. 없었으면 샘플링 정책이 잘못된 겁니다.
- 무엇을 오류로 볼지 넓게 정의하세요. 5xx 만 치면 4xx·빈 응답·겨우 성공한 요청이 다 빠집니다.
- 샘플링 대신 보관 기간 조정을 먼저 검토하세요. 전부 저장하되 며칠만 두는 편이 나을 때가 많습니다.
지금 확인해 보세요
필요한 것이 남아 있는가:
· 최근 오류 하나를 골라 추적을 찾아본다
없으면 샘플링 정책이 조사와 안 맞는 것이다
· 무엇을 '오류' 로 보는지 확인
5xx 만? 4xx 는? 내용이 빈 200 은? 겨우 성공한 것은?
· 저부하 엔드포인트의 표본 수
초당 10건을 1% 로 깎으면 100초에 한 건 — 사실상 없다
· 표본으로 계산한 지표에 가중치가 붙어 있는가
오류 100% + 성공 1% 로 실패율을 내면 크게 틀린다
· 샘플링 정책이 문서로 있는가
장애 중에 '이게 왜 없지' 를 알아내면 늦다핵심 한 줄무작위로 버리는데 찾는 것은 무작위가 아니다.