LabHub

OTCA — 오픈텔레메트리 인증 어소시에이트 · 샘플링과 운영 · 이론

테일 샘플링은 공짜가 아니다 — 세 가지 비용

LabHub 에서 이어서 보기

한 줄 요약

헤드 샘플링은 아직 아무 일도 일어나지 않은 시점에 결정하므로 드문 사건을 그 드문 만큼 정확히 버립니다. 테일 샘플링은 결과를 보고 결정하지만 세 가지 비용을 냅니다 — 네트워크는 줄지 않고, 메모리를 크게 먹고, trace ID 기반 라우팅을 요구합니다. 헤드에서 버린 것은 테일에서 되살릴 수 없습니다.

왜 이게 필요했나

전량 저장은 대부분의 조직에서 비용이 맞지 않습니다. 문제는 무엇을 버리느냐입니다. 헤드 샘플링 1% 로 시간당 5건의 오류를 조사한다고 해 봅시다.

보존되는 오류 트레이스 = 5 × 0.01 = 시간당 0.05건1건을 보려면 평균 20시간 대기

이것이 헤드 샘플링의 실질적 결말입니다. 조사가 필요할 때 그 트레이스는 없고, 있는 트레이스는 전부 정상 요청이라 볼 이유가 없습니다.

어떻게 동작하나

테일 샘플링은 트레이스가 완료될 때까지 스팬을 버퍼에 모아 두었다가 결과를 보고 결정합니다. 오류가 있으면 남기고, 느리면 남기고, 나머지는 소량만 남깁니다. 정책은 각각 "남길 조건"이며, 여러 정책 중 하나라도 매칭되면 보존됩니다. 그래서 헬스체크를 제외하려면 매칭을 뒤집는 invert_match 를 씁니다.

| 항목 | 헤드 샘플링 | 테일 샘플링 |
| --- | --- | --- |
| 결정 시점 | 루트 스팬 생성 시 | decision_wait 경과 후 |
| 오류·느린 요청 보존 | 확률적으로만 | 규칙으로 100% |
| 에이전트·네트워크 비용 | 비율만큼 감소 | 감소 없음 |
| 백엔드 저장 비용 | 감소 | 감소 |
| 컬렉터 메모리 | 무시 가능 | 초당 트레이스 × 대기 시간만큼 버퍼 |
| 운영 요구 사항 | 없음 | trace ID 기반 라우팅 필수 |

비용 1 — 네트워크는 줄지 않습니다. 판정하려면 모든 스팬이 컬렉터에 도착해야 하므로, 절약되는 것은 백엔드 저장과 인덱싱뿐입니다. 컬렉터 자체의 CPU 와 네트워크는 오히려 늘어납니다.

비용 2 — 메모리. decision_wait 동안 모든 스팬을 들고 있어야 합니다. 계산식은 단순합니다.

초당 트레이스 수 × decision_wait(초) × 트레이스당 스팬 수 × 스팬 크기 = 버퍼 메모리예: 10,000 trace/s × 30s × 12 스팬 × 1.2KB  = 4,320,000 KB = 약 4,219 MB (여유 2배면 약 8,438 MB)

num_traces 는 동시에 메모리에 유지하는 트레이스 수의 상한이고, 초과하면 가장 오래된 트레이스가 강제로 판정됩니다. 필요한 값도 같은 곱셈에서 나옵니다 — 초당 트레이스 수 × decision_wait. 10,000 trace/s 에 30초 대기라면 300,000 이 필요합니다.

비용 3 — 라우팅. 같은 트레이스의 모든 스팬이 같은 컬렉터 인스턴스에 도착해야 합니다. 컬렉터를 여러 대로 늘리고 일반 로드밸런서를 앞에 두면 한 트레이스의 스팬이 여러 인스턴스로 흩어져 각자 조각을 보고 판정하고, 결과는 무작위로 잘린 트레이스입니다. 해법은 앞단에 routing_key: traceID 를 쓰는 loadbalancing 익스포터 계층을 두는 것입니다.

같은 이유로 DaemonSet 에서는 테일 샘플링이 불가능합니다. 노드마다 컬렉터가 뜨는 구조에서는 한 트레이스의 스팬이 여러 노드로 흩어져 도착하므로 각 에이전트가 트레이스의 일부만 보게 됩니다. 테일 샘플링은 Deployment(게이트웨이) 계층에서만 씁니다.

현장에서 만나는 모습

가장 비싸게 배운 사고가 num_traces 를 50,000 으로 둔 채 트래픽이 늘어난 경우였습니다. 피크가 10,000 trace/s 였고 decision_wait 이 30초였으니 필요한 값은 300,000 이었습니다. 상한이 6분의 1이었으므로 오래된 트레이스가 계속 강제 판정되었고, 메모리 압박이 겹치면서 컬렉터가 OOM 으로 재시작을 반복해 5분치 데이터를 잃었습니다. 지표는 이상을 거의 보고하지 않았습니다 — 데이터는 "정상적으로" 전송되고 있었고, 백엔드에서 특정 트레이스만 없는 모양이었기 때문입니다.

교훈은 두 개입니다. 첫째, tail_sampling 을 켜기 전에 곱셈을 먼저 합니다. 둘째, 데이터가 사라지는데 지표가 조용하면 파이프라인에서 tail_sampling 을 잠시 빼고 재현되는지 봅니다. 이것이 가장 빠른 구분법입니다.

실무 조합은 대체로 이렇습니다. 트래픽이 아주 많은 서비스는 헤드에서 10~50% 로 미리 줄이고, 그 위에 테일을 얹어 오류와 느린 요청을 건집니다. 헤드에서 이미 버린 것은 테일에서 되살릴 수 없으므로 헤드 비율은 감당 가능한 한 높게 잡는 것이 원칙입니다.

다음 실습에서 할 것

/root/otca-sampling/ 아래에 테일 샘플링 정책 다섯 개를 씁니다. 오류와 지연을 100% 남기는 결과 기반 정책, invert_match 로 헬스체크를 제외하는 정책, VIP 테넌트와 지연을 and 로 묶은 조합 정책, 그리고 나머지를 위한 확률 정책. 그다음 앞단 loadbalancing 익스포터를 설정하고, 버퍼 메모리를 직접 계산해 파일로 남기고, 마지막으로 그 값을 그대로 담은 OpenTelemetryCollector CR 을 씁니다.