트레이스 샘플링 - 헤드와 테일 샘플링의 선택 기준
분산 추적을 켜면 곧 비용 문제를 만난다. 요청 하나가 서비스 다섯 개를 지나면 스팬이 수십 개 생기고, 초당 수천 요청이면 저장해야 할 양이 로그를 넘어선다. 그래서 샘플링을 켜는데, 무엇을 버릴지 정하는 방식에 따라 정작 필요한 트레이스가 남기도 하고 사라지기도 한다.
샘플링이 필요한 이유
트레이스의 비용은 세 곳에 있다.
- 애플리케이션: 스팬 생성과 컨텍스트 전파. 크지 않지만 0은 아니다.
- 네트워크: 수집기로 보내는 양.
- 저장소: 보존 기간 × 양. 대개 여기가 가장 크다.
전부 저장하는 선택지가 있고, 규모가 작으면 그것이 가장 단순하다. 샘플링은 규모가 커진 뒤의 문제다.
헤드 샘플링: 시작할 때 정한다
요청이 시작되는 지점에서 “이 트레이스를 남길지”를 정하고, 그 결정을 전파한다(W3C traceparent의 sampled 플래그). 하위 서비스는 그 결정을 따른다.
- 장점: 결정이 즉시 내려지므로 버릴 스팬은 아예 만들지 않는다. 비용이 가장 적고 구현이 단순하다.
- 단점: 무엇이 흥미로운지 모르는 시점에 정한다. 1%를 남긴다면 드물게 나는 오류 트레이스가 남을 확률도 1%다.
확률 기반이 기본이고(TraceIdRatioBased), 그 위에 규칙을 얹는 변형이 흔하다. 예를 들어 /health는 0%, 결제 경로는 100%, 나머지는 1%처럼 경로별로 다르게 둔다. 엔드포인트별 차등이 헤드 샘플링을 실용적으로 만드는 핵심이다.
테일 샘플링: 끝나고 나서 정한다
수집기가 트레이스의 모든 스팬을 일단 받아 버퍼에 모으고, 완료된 뒤 조건을 보고 남길지 정한다.
- 오류가 있으면 남긴다.
- 지연이 임계값을 넘으면 남긴다.
- 특정 속성(중요 고객, 결제 경로)이면 남긴다.
- 나머지는 확률적으로.
필요한 것을 정확히 남길 수 있다는 것이 유일하고 결정적인 장점이다. 드문 오류를 조사할 때 “그 트레이스가 샘플링에서 빠졌다”가 사라진다.
대가가 크다.
- 모든 스팬을 일단 전송하고 버퍼링해야 한다. 애플리케이션과 수집기 사이의 네트워크 비용은 줄지 않는다.
- 수집기가 상태를 갖는다. 트레이스 단위로 모아야 하므로, 같은 트레이스의 스팬이 같은 수집기 인스턴스로 가야 한다. 로드 밸런싱을 트레이스 ID 기준으로 해야 하고, 수집기가 확장의 병목이 된다.
- 완료 판정이 어렵다. 트레이스가 끝났는지 알 방법이 없으므로 타임아웃으로 결정한다. 그 시간만큼 메모리를 쓰고, 늦게 오는 스팬은 놓친다.
고르는 기준
| 상황 | 선택 |
|---|---|
| 규모가 작다 | 샘플링 없음. 가장 단순하다 |
| 비용만 줄이면 된다 | 헤드 샘플링 + 경로별 차등 |
| 드문 오류 조사가 목적이다 | 테일 샘플링 |
| 둘 다 | 헤드에서 1차로 줄이고 테일에서 선별 |
실무에서 가장 흔한 것은 마지막이다. 헤드에서 명백히 불필요한 것(헬스 체크, 정적 리소스)을 걷어내고, 나머지를 테일에서 선별한다.
샘플링과 무관하게 메트릭은 전부 집계해야 한다. 트레이스를 1% 남기더라도 오류율과 지연 분포는 100% 기준으로 계산해야 한다. OpenTelemetry의 span metrics처럼 샘플링 전에 메트릭을 뽑는 구성이 이 때문에 쓰인다. 샘플링된 트레이스로 비율을 계산하면 틀린다.
이 설명이 깨지는 곳
- 샘플링 결정은 전파되어야 한다. 중간 서비스가 컨텍스트를 잃으면 트레이스가 쪼개진다. 비동기 경계(스레드 풀, 메시지 큐)에서 자주 끊긴다.
- 트레이스가 없어도 로그는 있다.
trace_id를 로그에 남겨 두면 샘플링에서 빠진 요청도 로그로 추적할 수 있다. 둘을 함께 설계해야 하는 이유다(메트릭·로그·트레이스). - 1%가 얼마인지는 트래픽에 달렸다. 초당 10건이면 1%는 6분에 4건이고, 그것으로는 아무것도 조사할 수 없다. 낮은 트래픽 서비스는 샘플링하지 않는 편이 낫다.
- 속성에 개인정보를 넣지 않는다. 트레이스는 보존 기간이 길고 여러 시스템을 지난다.
무엇을 재면 확인되는가
- 샘플링 비율을 바꿔 가며 “특정 오류의 트레이스를 찾을 확률”을 센다. 조사 가능성이 숫자로 나온다.
- 테일 샘플링 수집기의 메모리 사용량을 트레이스 수와 버퍼 타임아웃의 함수로 본다.
- 샘플링된 트레이스로 계산한 오류율과 전량 메트릭의 오류율을 비교한다. 차이가 나면 전자를 지표로 쓰고 있었다는 뜻이다.
실무와의 접점
monticker의 OpenTelemetry 도입에서 sampling.probability: 1.0을 개발 환경 값으로 두고 “운영에서는 0.1 이하로 줄여야 한다”고 적었다. 지금 보면 그 문장에 빠진 것이 “무엇을 남길 것인가” 다. 비율만 줄이면 조사에 필요한 것도 같은 비율로 사라진다. 운영으로 가져간다면 헤드에서 헬스 체크를 걷어내고, 오류와 느린 요청은 테일에서 남기는 구성이 필요하다.
정리
- 트레이스 비용은 대부분 저장소에 있고, 샘플링은 규모가 커진 뒤의 문제다.
- 헤드 샘플링은 시작 시점에 정해 전파한다. 싸지만 무엇이 흥미로운지 모른 채 정한다.
- 테일 샘플링은 완료 후 조건으로 정한다. 필요한 것을 남기지만 수집기가 상태를 갖고 병목이 된다.
- 경로별 차등이 헤드 샘플링을 실용적으로 만든다.
- 메트릭은 샘플링과 무관하게 전량 집계해야 한다. 샘플된 트레이스로 비율을 계산하면 틀린다.
- 트래픽이 낮으면 샘플링하지 않는 편이 낫다.
댓글
아직 댓글이 없습니다