트레이스 커버리지를 지키려면 샘플링 지점을 어디에 둬야 하나?

결론부터: 샘플링 지점을 바꾼다고 문제가 사라지지 않는다. head-based는 저장 비용을 가장 먼저 줄이지만 나중에 중요하다고 판명될 트레이스를 사전에 걸러낸다. tail-based는 전체 맥락을 보고 보존 여부를 정하지만 collector 버퍼 비용이 들고, 스팬 단위로 판단하면 부분 트레이스가 남는다. eBPF 훅 기반 상시 수집은 샘플링률을 낮춰도 커널 훅 실행 비용 자체는 거의 줄지 않는다.

이 글의 핵심

  • head-based 확률 샘플링은 트레이스 초기에 결정되므로, 이후 실패·지연으로 드러날 트레이스가 처음부터 탈락할 수 있다 출처.
  • tail-based는 오류·지연 트레이스 보존에 유리하지만, 활동(스팬) 단위로 결정하면 "오류 스팬 보존"과 "오류 트레이스 완전 보존"은 다른 결과를 낳는다.
  • TU Delft의 Beyla 측정에서 100% 샘플링과 10% 샘플링의 CPU 사용량은 약 1.5백만 대 1.6백만으로 거의 같았다 — 샘플링은 저장량을 줄이지 수집 비용을 줄이지 않는다 출처.
  • 흐름 수가 100개에서 100,000개로 늘면 hash map 처리량이 30.5% 줄어든다. 카디널리티 문제는 샘플링 전략과 별개 축이다.
  • 2026년 기준 실무 구성은 두 샘플링 방식과 eBPF 상시 수집을 조합하되, 각각 "무엇을 버리는지"를 분리해서 설계해야 한다.

두 접근을 나란히 놓으면 이렇다. 하나는 애플리케이션·에이전트 단에서 트레이스 시작 시점에 보존 여부를 확률적으로 정하는 head-based 샘플링이고, 다른 하나는 트레이스가 끝날 때까지 기다렸다가 오류·지연 조건을 보고 보존하는 tail-based 샘플링이다. 그 아래에는 커널 훅이 모든 이벤트에서 실행된 뒤 전송 단계에서만 걸러지는 eBPF 상시 수집 계층이 따로 있다. 셋은 "언제 버리는가"가 다르고, 그 차이가 그대로 커버리지 손실의 성격을 결정한다.

결정 시점이 다르면 버려지는 트레이스도 달라지나?

그렇다. head-based는 트레이스가 시작되는 순간 확률적으로 샘플 여부를 정하므로, 그 트레이스가 나중에 5xx를 반환하거나 지연이 길어질지는 결정 시점에서 알 수 없다. OpenTelemetry .NET 문서는 head-based 확률 샘플링이 트레이스 초기에 결정되는 반면, tail-based 샘플링은 트레이스가 끝난 뒤 전체 맥락을 보고 결정한다고 명시한다 출처. 즉 head-based만 쓰면 결과적으로 중요했을 트레이스가 처음부터 통계에서 빠질 수 있다.

반대로 tail-based는 판정을 미루는 대신 판정 전까지 스팬을 보관해야 한다. 이 보관 구간의 메모리·대기시간·확장성이 collector 설계의 핵심 변수가 된다. 두 방식 모두 "커버리지 손실 없음"을 보장하지 않으며, 손실이 생기는 시점만 다를 뿐이다.

샘플링률을 낮추면 수집 비용도 비례해서 줄어드나?

아니다. 적어도 uprobe 기반 eBPF 수집에서는 그렇지 않다. TU Delft의 Beyla 측정에서 100% 샘플링과 10% 샘플링의 CPU 사용량은 각각 약 1.5백만과 1.6백만으로 거의 같았다. 함수가 호출될 때마다 커널 훅이 먼저 실행되고 그 다음에 샘플을 버리는 구조이기 때문에, 샘플링률을 낮춰도 프로브 실행 비용은 그대로 남는다 출처.

빈 훅 자체의 비용은 종류별로 차이가 크다. Cloudflare의 ebpf_exporter 벤치마크(Linux 6.5-rc1)는 tracepoint 약 15ns, fentry 약 24ns, kprobe 약 137ns로 측정했고, 비교 기준 getpid() 호출은 약 117ns였다. 훅 자체는 가볍지만, 초당 이벤트 수·맵 갱신·스택 수집·전송·심볼화가 누적되면 이야기가 달라진다. 실제로 작은 I/O와 높은 동시성 조건을 재현한 CHEOPS 2026 워크숍 논문에서는 eBPF 트레이싱 오버헤드가 최대 41%까지 측정됐다. 이 수치는 요청 크기·동시성·훅 위치가 동일하게 재현된 경우에만 유효하다.

고카디널리티 키를 넣으면 샘플링과 무관하게 비용이 느나?

그렇다. CoNEXT 2025 측정에서는 흐름 수가 100개에서 100,000개로 늘어날 때 hash map 처리량이 30.5% 감소했다. pod, container, service, route, status_code, user_id를 전부 맵 키로 넣는 설계는 수집률뿐 아니라 커널 맵 갱신·메모리·질의 비용까지 함께 키운다 출처. 이 비용은 head-based든 tail-based든 샘플링 전략과 무관하게 발생한다.

함수 전체를 추적하는 설계도 같은 계열 문제다. NetDev 연구에서는 전체 함수 추적 조건에서 처리량이 46% 감소한 사례가 보고됐다. 다만 이 수치는 해당 연구의 구현·부하 조건에 한정된 값이며, 제품 공통값으로 일반화할 근거는 없다. 카디널리티와 훅 범위는 샘플링률과 분리해서 별도로 관리해야 하는 축이다.

오류 트레이스만 보존하면 워터폴은 안전한가?

아니다. 호출 트리의 한 구간에서 오류가 발생해 그 활동만 tail-based로 보존되고, 성공한 상위·하위 활동은 head-based 단계에서 이미 탈락했다면 전체 워터폴은 부분 트레이스로 남는다. OpenTelemetry .NET 문서는 "모든 오류 스팬 보존"과 "완전한 오류 트레이스 보존"이 같은 정책이 아니라고 명시한다 출처. 정상 활동은 일부만 남기고 실패 활동은 모두 보존하는 하이브리드 전략조차, 활동 단위로 결정하면 결과가 조각난 트레이스로 남을 수 있다.

실무 설계는 보통 오류 외에도 HTTP 5xx, 긴 지연시간, 특정 서비스·경로, 배포 직후 구간을 별도 보존 조건으로 둔다. 다만 이 조합의 2025~2026년 독립 재현 벤치마크 수치는 확인되지 않았으므로, 이는 공식 구현 문서에서 확인되는 설계 방향으로만 받아들여야 한다.

수치로 보면 두 접근은 얼마나 차이 나나?

항목 head-based 샘플링 tail-based 샘플링 eBPF 상시 수집(uprobe)
결정 시점 트레이스 시작 시 트레이스 종료 후 매 호출마다 훅 실행
저장 비용 가장 먼저 감소 판정 후 감소 샘플링은 전송량만 감소
수집/프로브 CPU 영향 적음 collector 버퍼 비용 증가 100%→10% 샘플링해도 1.5M→1.6M로 거의 불변
대표 리스크 중요 트레이스 사전 탈락 부분 트레이스, 메모리 비용 작은 I/O·고동시성에서 최대 41% 오버헤드

10,000 RPS Go 서비스 사례에서는 eBPF 메트릭과 전체 OpenTelemetry 트레이싱 사이 CPU 차이가 크게 보고됐다. Coroot 측정에서는 CPU가 약 2코어에서 2.7코어로 늘고 BatchSpanProcessor가 전체 CPU의 약 10%를 썼다. 별도 InfoQ 사례는 eBPF 메트릭 수집이 0.3코어 미만, 전체 OpenTelemetry 트레이싱이 약 2.7코어였다고 소개하지만, 두 수치가 동일 애플리케이션·하드웨어·버전에서 직접 비교된 것인지는 검색 결과만으로 확인되지 않는다 출처.

head-based에서 tail-based로, 혹은 eBPF 상시 수집으로 옮기면 뭐가 드나?

실제로 드는 비용은 세 갈래다. 첫째, tail-based로 옮기면 collector가 트레이스 종료까지 스팬을 보관해야 하므로 메모리와 처리 대기시간이 늘어난다. 둘째, eBPF 상시 수집을 추가하면 샘플링률과 무관하게 커널 훅·맵 갱신 비용이 고정적으로 붙는다 — 이는 전송 단계가 아니라 수집 단계 비용이라 샘플링 조정으로는 줄지 않는다. 셋째, 애플리케이션 계측(OpenTelemetry)과 eBPF 신호를 함께 쓰는 구성에서는 export 단계가 비용의 상당 부분을 차지한다. 2025년 ICPE 연구를 소개한 자료에서는 OpenTelemetry v1.25.1의 중앙값 처리량 감소가 38.6%, Elastic APM v6.22.0이 49.4%였고, 가장 큰 비용 요인으로 트레이스 데이터 export가 지목됐다 출처. 다만 이 수치는 원 논문 세부 재현 조건이 충분히 확인되지 않아 보조 근거로만 써야 한다. 어느 쪽으로 전환하든 커널 버전, 훅 타입, 수집기 버전, 요청률·동시성·I/O 크기, map 최대 크기, export 배치 크기를 함께 기록하지 않으면 비교 자체가 무의미해진다.

결국 무엇을 기준으로 골라야 하나?

정답은 없고 조건만 있다. 저장·전송 비용이 병목이고 수집 측 CPU 여유가 있다면 head-based 확률 샘플링으로 충분할 수 있다. 오류·지연 분석의 정확도가 우선이고 collector 메모리·지연을 감당할 수 있다면 tail-based가 맞다. 커널 훅 실행 비용 자체가 문제라면 샘플링률을 낮추는 대신 훅 수를 줄이거나 커널에서 집계 후 주기적으로 내보내는 방식을 검토해야 한다. 고카디널리티 라벨이 걸려 있다면 샘플링 전략과 별개로 키 설계부터 다시 봐야 한다. 세 축(결정 시점, 프로브 비용, 카디널리티) 중 어느 것이 실제 병목인지 먼저 측정하지 않으면, 샘플링 전략을 바꾸는 작업 자체가 헛수고가 될 수 있다.

핵심 정리

  • head-based는 저장 비용을 가장 먼저 줄이지만 결정이 이르기 때문에 중요 트레이스가 사전 탈락할 수 있다.
  • tail-based는 전체 맥락을 보고 보존하지만 collector 버퍼 비용이 들고, 스팬 단위 판정은 부분 트레이스를 남길 수 있다.
  • uprobe 기반 eBPF 수집은 샘플링률을 10%로 낮춰도 CPU 비용이 거의 줄지 않는다(1.5M vs 1.6M, Beyla 측정).
  • 카디널리티 증가(100→100,000 흐름)는 샘플링과 무관하게 map 처리량을 30.5% 떨어뜨린다.
  • "오류 스팬 보존"과 "오류 트레이스 완전 보존"은 다른 정책이며, 혼동하면 분석 단계에서 워터폴이 끊겨 보인다.

자주 묻는 질문

head-based와 tail-based를 동시에 쓸 수 있나?

가능하다. OpenTelemetry .NET 예시는 정상 활동은 일부만 head-based로 남기고 실패 활동은 tail-based 조건으로 전부 보존하는 하이브리드 구성을 보여준다. 다만 활동(스팬) 단위로 결정하면 결과 트레이스가 부분적으로 남을 수 있다는 점은 동일하게 적용된다.

eBPF 샘플링률을 낮추는 게 아예 의미 없나?

의미는 있지만 줄이는 대상이 다르다. 샘플링은 Kafka·OTLP·시계열 저장소로 보내는 저장·전송량을 줄인다. 하지만 프로브가 모든 이벤트에서 실행되는 구조라면 커널 훅·맵 갱신 비용은 그대로 남으므로, 수집 CPU를 줄이려면 훅 자체를 줄이거나 커널 집계 방식을 바꿔야 한다.

고카디널리티 라벨을 줄이면 질의 비용만 개선되나?

아니다. 흐름 수가 늘면 map 처리량 자체가 떨어진다는 CoNEXT 2025 측정이 보여주듯, 카디널리티는 저장·질의뿐 아니라 커널 맵 갱신과 메모리에도 직접 영향을 준다. 질의 비용만 보고 라벨 설계를 미루면 수집 단계에서 먼저 병목이 생길 수 있다.

전체 함수 추적 대신 뭘 먼저 줄여야 하나?

네트워크·시스템 호출 일부만 추적하는 쪽이 전체 함수 추적보다 가볍다는 보고가 있다(관련 연구에서 전체 함수 추적 시 처리량 46% 감소 사례). 비즈니스 로직 내부의 상세 구간은 eBPF만으로는 포착하기 어렵기 때문에, 전체 함수 추적을 늘리기보다 애플리케이션 계측(OpenTelemetry 스팬)과 역할을 분담하는 편이 일반적이다.

더 알아보기

관측성 파이프라인 설계: 메트릭·로그·트레이스 수집의 실제 — 이 주제의 종합 가이드