관측성 파이프라인, 무엇부터 봐야 할까?

관측성 파이프라인의 설계는 세 가지 축 위에서 움직인다: **신호 수집 방식(무계측 vs 계측)**과 카디널리티 폭발 제어, 그리고 세 신호 간 상관 전략. 이 셋이 총 비용과 커버리지를 결정한다.

관측성 신호는 생성되는 순간부터 지수적으로 비용이 증가한다. 특히 고차원 레이블을 가진 메트릭과 구조화되지 않은 로그의 조합은 저장소 비용을 빠르게 밀어올린다. 어디서 샘플링할지, 어느 신호를 버릴지, 커널에서 수집할 것인지 애플리케이션 계층에서 할 것인지가 이 모든 결정의 출발점이다.

메트릭, 로그, 트레이스를 각각 수집하면 비용이 세 배 드나?

아니다. 신호 유형별로 저장·질의 비용 구조가 완전히 다르므로, 선택적 수집이 더 효율적일 수 있다. 메트릭은 타임시리즈 데이터베이스(TSDB)에서 압축률이 높고(시계열 인코딩으로 10:1 이상), 로그는 전문 검색 엔진(Elasticsearch 같은 전역 색인)에서 해시 기반 저장이, 트레이스는 스팬 단위 분산 저장으로 최적화된다.

문제는 카디널리티다. 동일 메트릭을 service, endpoint, method, status 레이블로 분해하면 카디널리티는 곱해진다. 예를 들어 서비스 10개 × 엔드포인트 100개 × HTTP 메서드 5개 × 상태코드 10개 = 50,000개 시계열이 된다. 각 시계열을 5분 단위로 저장하면 1일에 576개 데이터 포인트가 누적되고, 보존 기간 30일 기준 약 1.44억 개 포인트가 된다.

로그는 비정규화된 구조여서 다르다. 같은 정보를 로그 줄에 포함시키더라도 저장 비용은 메트릭의 선형 증가가 아니라 발생 빈도 의존적이다. 트레이스는 전체 요청의 일부만 샘플링하므로 통제 가능하다.

판단 기준: 실시간 집계와 알림이 필요한 영역은 메트릭으로. 사후 분석과 검색이 주인 영역은 로그로. 병목 지점의 세부 경로 분석만 트레이스로 수집한다.

메트릭, 로그, 트레이스 간에 맥락을 유지할 수 있나?

세 신호를 연결하는 것은 선택적이고, 대부분의 경우 **공통 레이블(trace ID, request ID, 타임스탬프)**로 느슨하게 연결된다. 메트릭에 trace ID를 붙이는 것은 카디널리티를 폭발시키므로 권장되지 않으며, 대신 로그와 트레이스에 공통 식별자를 심고, 메트릭은 aggregation 수준에서 정의한다.

예를 들어 Jaeger 트레이스에 포함된 trace ID를 JSON 구조 로그에도 포함시키면, 동일 요청의 로그 줄을 검색할 수 있다. 메트릭은 request_duration_seconds 히스토그램으로 정의하되, 분위수(p50, p99)를 계산하는 식으로 트레이스 샘플과 대조한다.

이 연결은 저장소 아키텍처에도 영향을 미친다. 로그와 트레이스를 같은 객체 저장소(S3, GCS)에 저장하고 메타데이터(trace ID, 타임스탬프)로 색인하면 쿼리 엔진이 여러 신호를 크로스 조회할 수 있다. 다만 쿼리 레이턴시는 수백 밀리초 단위가 된다.

판단 기준: 메트릭으로 문제를 감지하고, 로그와 트레이스로 근인을 파악하는 두 단계 워크플로우가 표준. 메트릭 안에 로그나 트레이스 데이터를 포함시키지 말 것.

어디서 샘플링해야 신호 품질을 잃지 않나?

샘플링은 수집 초기(collector 진입 전)에 할지, 저장 직전에 할지, 질의 시점에 할지에 따라 결과가 크게 달라진다.

수집 초기 샘플링은 네트워크·저장소 비용을 가장 많이 줄이지만, 드물게 발생하는 느린 요청(p99 지연)을 놓치기 쉽다. OpenTelemetry의 head-based sampling(요청 진입 시점에 샘플 여부 결정)이 이 방식이다. 비용은 낮지만 대표성 편향(bias)이 생긴다.

저장 직전 샘플링은 전체 신호를 메모리에 버퍼링한 후 필터링하므로, 느린 요청만 100% 샘플링하거나 에러만 선택적으로 보관할 수 있다. tail-based sampling이라 부르며, 동적 임계값 기반 필터링이 가능하다. 비용은 중간 수준이지만 구현 복잡도가 높다.

질의 시점 샘플링은 저장된 데이터 중 일부만 읽는 방식으로, 저장소에는 전수를 보관했다가 쿼리 시 1% 샘플만 로드한다. 비용 최소화는 안 되지만 사후 분석에서는 충분히 대표적이다.

2026년 기준, 대규모 시스템은 세 방식을 혼합한다. 메트릭은 100% 수집하되 카디널리티를 사전에 제약하고, 트레이스는 head-based(2~5%)와 tail-based(에러/느린 요청 100%)를 조합하며, 로그는 저장소 직전 필터링(레벨별 선택)으로 정리한다.

판단 기준: 메트릭은 선택적 레이블로 카디널리티 제어. 트레이스는 tail-based 샘플링으로 이상치 확보. 로그는 저장소 진입 전 필터링.

eBPF 무계측 수집이 애플리케이션 계측을 대체할 수 있나?

아니다. eBPF는 커널에서 네트워크·시스템 콜 수준의 신호를 무계측으로 캡처하므로, 애플리케이션 계층 정보는 전혀 볼 수 없다.

eBPF의 강점은 계측 오버헤드 제로배포 없는 적용이다. 이미 실행 중인 프로세스에 eBPF 프로그램을 attach하면 TCP 연결, DNS 쿼리, 시스템 콜 레이턴시를 실시간으로 수집한다. 데이터베이스 드라이버나 HTTP 클라이언트를 수정할 필요가 없다.

단점은 애플리케이션 의미론이 없다는 것이다. eBPF는 GET /api/users 엔드포인트를 TCP 연결(port 80) 수준으로만 본다. 비즈니스 로직(user_id=123)이나 애플리케이션 지연(데이터베이스 쿼리 vs 캐시 조회)은 구분하지 못한다. 또한 암호화된 HTTPS 트래픽(TLS)을 해석할 수 없으므로, HTTP 헤더나 payload 분석은 애플리케이션 계층의 계측에 의존한다.

실제로는 두 계층의 조합이 표준이 되고 있다. eBPF는 시스템 수준 baseline(네트워크 RTT, 시스템 콜 지연)을 잡고, 애플리케이션 계측은 비즈니스 경계에서의 이벤트(요청 시작/종료, 캐시 hit/miss)를 기록한다. 둘 다 동일한 trace ID로 연결하면, 시스템 지연을 애플리케이션 로직 어디 부분에서 발생했는지 매핑할 수 있다.

eBPF 수집의 비용은 다르다. 커널 메모리에 버퍼링되므로 사용자 공간 전송 비용이 낮지만, 복잡한 필터링(정규표현식, 비즈니스 로직)은 할 수 없다. 저장소 비용은 수집 볼륨에 따라 결정되는데, 전체 네트워크 패킷을 캡처하면 급격히 증가한다.

판단 기준: eBPF는 시스템 baseline과 배포 제약 환경에서만 사용. 비즈니스 이벤트와 세부 경로는 애플리케이션 계측 필수.

카디널리티 폭발은 어떻게 사전에 막나?

카디널리티는 설계 단계에서 제어해야 한다. 런타임에는 통제 불가능에 가깝다.

일반적인 원인은 고유성이 높은 값을 레이블로 포함시키는 것이다. 예: user_id=12345 (백만 개 사용자 = 백만 레이블 조합), request_id=abc123xyz (초당 수천 요청 = 매일 수억 고유값), ip_address=192.168.1.100 (네트워크 규모 선형). 이들은 메트릭 레이블에 포함되면 안 된다.

대신 구분 기준을 집약된 차원으로 재정의한다:

  • user_iduser_cohort (신규/활성/휴면, 5~10가지)
  • request_id → 제거 (추적은 로그와 트레이스에서)
  • ip_addressregion, datacenter (수십 가지)

TSDB는 설정 수준에서 새로운 레이블 조합 생성을 제한할 수 있다. Prometheus의 metric_relabel_configs를 이용해 저장 전 레이블을 필터/삭제하거나, Grafana Loki는 structured 로그를 수집하되 색인할 레이블을 명시적으로 제한한다(default 3~5개).

메트릭 설계 원칙:

  • 차원은 10개 이하. 권장 5개 이내.
  • 각 차원의 고유값은 최대 1,000개. 대부분은 100개 이하.
  • high-cardinality 값(ID, hash)은 레이블 금지. 로그나 트레이스에서만.

저장소별 카디널리티 한계:

  • Prometheus: ~100만 시계열(권장 500만 이하로 운영)
  • InfluxDB: 무제한(비용 기반)
  • TimescaleDB: 스키마 정의 필수(일반적으로 메모리 부하 기준)

판단 기준: 메트릭 설계 초기부터 차원 수와 각 차원의 최대 고유값을 명시하고, 저장 전 자동 필터링을 구성한다.

사이드카 방식과 커널 수집, 아키텍처상 무엇이 다른가?

사이드카 방식(예: Fluent Bit, OpenTelemetry Collector 컨테이너)은 애플리케이션 옆에서 로그/메트릭을 수집해 중앙으로 전송한다. 애플리케이션은 표준 출력(stdout)이나 메트릭 엔드포인트(/metrics)만 노출하고, 사이드카가 이를 파싱·보강·필터링한다.

커널 수집(eBPF 프로브, systemtap)은 커널 공간에서 직접 신호를 가져온다. 계측 부하가 없고, 배포 변경 불필요.

아키텍처적 트레이드오프:

측면 사이드카 커널
계측 부하 애플리케이션에 영향(고부하 시 최악 1~5% 오버헤드) 거의 없음
배포 사이드카 배포 필요 기존 프로세스에 attach, 배포 불필요
비즈니스 맥락 애플리케이션 로그/메트릭이므로 풍부 시스템 이벤트만(비즈니스 의미 없음)
필터링/보강 사이드카에서 복잡한 변환 가능(regex, 룩업) 커널에서 제한적(성능 때문)
보존 기간 중앙 저장소(days~months) 커널 메모리(초~분 단위)

Kubernetes 환경에서 사이드카는 daemonset(노드당 1개) 또는 사이드카 패턴(pod마다 1개)으로 배포된다. daemonset이 더 효율적이지만(리소스 중복 최소), 노드 수준 신호만 수집 가능하다.

커널 수집은 eBPF 프로그램을 호스트에 로드하므로, 컨테이너 내부 프로세스를 감시하려면 호스트 권한(privileged mode)이 필요하다. 이는 보안 정책상 제약이 될 수 있다.

판단 기준: 비즈니스 이벤트 중심 환경 → 사이드카. 배포 제약이 있거나 시스템 기준이 필요 → 커널 수집. 대규모 시스템은 둘 다 병행.

저장소와 보존 기간을 어떻게 설계하나?

관측성 데이터는 신호 유형별로 저장소와 보존 기간이 다르다.

메트릭: 시계열 데이터베이스(Prometheus, InfluxDB, TimescaleDB). 고 압축(10:1 이상), 낮은 쓰기 부하. 권장 보존: 15~30일(near real-time 알림). 장기 보관은 별도 저장소(S3 + 다운샘플링)로 이동.

로그: 검색 엔진(Elasticsearch, OpenSearch) 또는 객체 저장소(S3 + Parquet, S3 + JSON). 검색 엔진은 빠른 쿼리(초 단위)와 유연한 필터링. 비용 높음(인덱싱 오버헤드). 객체 저장소는 비용 저(GB당 $0.02 이하)지만 쿼리 레이턴시 높음(수초). 권장 보존: 790일(운영용 12주, 규정상 요구사항 충족 위해 장기).

트레이스: 분산 추적 데이터베이스(Jaeger, Tempo, Zipkin) 또는 객체 저장소. 샘플링되므로 볼륨은 상대적으로 낮음. 권장 보존: 3~7일(실시간 디버깅), 또는 1개월(감사 목적).

저장소 비용은 수집 볼륨으로 결정된다. 메트릭 1백만 시계열을 1시간 해상도로 저장하면:

  • 1일: 24백만 데이터 포인트 (compressed ~100MB)
  • 30일: 720백만 포인트 (~3GB, TSDB 환경)

로그 1GB/day를 90일 보관하면:

  • 객체 저장소: ~$1.8 (90GB × $0.02)
  • Elasticsearch: $50100 (인덱싱, 샤딩, 레플리카 고려)

트레이스는 샘플링에 따라. 1000 RPS에서 2% 샘플링 (20 RPS) × 평균 10 스팬 × 1KB/스팬 = 200KB/s = 17GB/day. 7일 보관: 119GB (비용 ~$2.4/day).

멀티 티어 아키텍처가 표준:

  1. Hot tier (1~7일): 고속 저장소(SSD 기반 TSDB 또는 ES).
  2. Warm tier (7~90일): 원가 최적 저장소(S3 + Athena, BigQuery).
  3. Cold tier (90일+): 아카이브(S3 Glacier 등, 쿼리 불가).

흔한 설계 실수: 처음부터 '다 수집'하는 함정

많은 팀이 "나중에 필요할 수 있으니"라며 모든 신호를 무제한으로 수집하려다가, 저장소 비용 급증으로 중단하는 패턴을 반복한다.

실제 문제는 여럿:

  1. 카디널리티 제약 없는 설계: 초반에 누군가 pod_name, container_id, node_ip 같은 고유값 레이블을 포함시키면, 자동으로 시계열이 폭증한다. 이미 쌓인 데이터는 어쩔 수 없고, 새 메트릭부터만 제약을 걸어도 기존 데이터 비용은 계속 발생한다.

  2. 샘플링 정책 부재: 트레이스를 전수 수집하거나, 불필요한 LOW 레벨 로그를 저장소에 써넣는 식. 저장소 비용이 선형 증가하는데, 쿼리 성능은 나아지지 않는다.

  3. 신호 통합 혼동: 메트릭에 trace ID를 넣거나, 로그에 모든 시스템 메트릭을 JSON 필드로 포함시키는 방식. 저장소 설계 목표와 맞지 않아 성능과 비용 모두 악화.

  4. 보존 정책 불명확: "언제까지 보관할 건데?" 불명확하면, 관리자는 안전하게 365일 유지하다가 어느 날 그만둔다. 그 사이 누적 비용은 천문학.

대신 설계 초기에 정해야 할 것:

  • 메트릭 차원 5개 이내, 각 차원 고유값 최대치.
  • 트레이스 샘플링 목표 (예: p99 지연 포함, 전체 2%).
  • 로그 레벨별 저장소(ERROR/WARN → hot 7일, INFO → warm 30일, DEBUG → 불저장).
  • 신호별 보존 기간과 티어 이동 정책.
  • 매월 비용 기준과 초과 시 대응.

핵심 정리

  • 신호 유형별 저장 방식이 다르다: 메트릭은 TSDB(압축률 높음), 로그는 검색 엔진 또는 객체 저장소, 트레이스는 샘플링 기반 분산 저장. 세 가지를 같은 방식으로 취급하면 비용과 성능 모두 악화.

  • 카디널리티는 런타임에 제어 불가능: 설계 단계에서 메트릭 차원(5개 이내), 각 차원 고유값 최대치(1,000 이하)를 명시하고, 저장소 진입 전 자동 필터링을 구성. 사후 조정은 매우 어렵다.

  • 샘플링은 계층별로 다르다: 메트릭은 카디널리티 제약으로, 트레이스는 head-based(2~5%)와 tail-based(이상치 100%)의 조합으로, 로그는 저장소 직전 필터링으로 비용을 통제.

  • eBPF는 시스템 기준, 애플리케이션 계측은 비즈니스 맥락: 둘 다 필요하며 상호보완. eBPF만으로는 비즈니스 이벤트를 추적할 수 없고, 애플리케이션 계측만으로는 시스템 수준 기준을 얻기 어렵다.

  • 메트릭·로그·트레이스 연결은 선택적: 공통 레이블(trace ID, 타임스탠프)로 느슨하게 연결. 메트릭에 고차원 ID를 포함시키면 카디널리티 폭발.

  • 멀티 티어 보존이 표준: hot(17일, TSDB 또는 ES)→ warm(790일, 객체 저장소)→ cold(90일+, 아카이브). 비용 최적화와 쿼리 성능의 균형.

  • 초기 설계 결정이 가장 중요: "나중에 필요할 수 있으니" 전수 수집은 비용 폭증의 주요 원인. 비즈니스 요구사항에 따라 신호별·레벨별 보존 정책을 명시해야 한다.

자주 묻는 질문

메트릭을 얼마나 자주 저장해야 하나?

메트릭 스크레이프 간격은 15초~1분이 표준. 고 빈도(1초 단위)는 저장소 부하를 4배 이상 증가시키고, 저 빈도(5분)는 short-lived 이벤트를 놓친다. 실시간 알림이 목표면 1분, 장기 트렌드 분석만 하면 5분으로도 충분.

로그와 메트릭 중 뭘 먼저 구축해야 하나?

메트릭부터. 메트릭으로 문제를 감지하고, 로그와 트레이스로 근인을 파악하는 두 단계 워크플로우가 표준. 메트릭 없이 로그만 보관하면 언제 문제가 생겼는지 알아야 로그를 검색할 수 있으므로 비효율적.

트레이스 샘플링을 언제까지 늘려도 되나?

전체 요청의 5% 이상 샘플링은 저장소 비용 대비 추가 인사이트가 미미하다. 대신 tail-based 샘플링으로 느린 요청(p99+), 에러 요청을 100% 캡처하면, 1~2% head-based 샘플과 조합해도 비용과 커버리지의 균형을 맞출 수 있다.

eBPF로 수집한 데이터도 trace ID로 추적할 수 있나?

기술적으로 가능하지만 제약이 있다. eBPF는 커널에서 네트워크 패킷을 보므로, 암호화된 HTTPS 내부의 trace ID 헤더를 볼 수 없다. 대신 connection ID(socket 단위)로 추적하고, 애플리케이션 계층에서 수집한 trace ID와 타임스탬프로 correlate한다. 실제로는 별도의 매핑 단계가 필요.

저장소를 바꾸면서 기존 데이터는 어떻게 마이그레이션하나?

일반적으로는 하지 않는다. 새 저장소로 전환할 때는 전환 이후 신규 데이터만 수집하고, 기존 데이터는 old 저장소에서 읽기 전용으로 보관한다. 중요한 데이터(감사 로그)만 선택적으로 내보낸다(export). 전체 마이그레이션은 비용이 매우 높다.

카디널리티 제한을 어떻게 자동으로 적용하나?

TSDB 레벨에서는 metric_relabel_configs(Prometheus) 또는 cardinality_limit 옵션(일부 InfluxDB)으로 저장 전 필터링. 로그는 수집 에이전트(Fluent Bit의 grep filter, Vector의 remap)에서 조건부 삭제. 트레이스는 collector 레벨(OpenTelemetry Collector의 attribute processor)에서 high-cardinality 속성 제거. 모두 저장소 진입 전에 적용하는 것이 비용 절감의 핵심.

비용 증가를 빨리 감지하려면?

저장소별로 일일 용량 증가율 모니터링(메트릭 시계열 수, 로그 바이트, 트레이스 스팬 수)과 cost per event 지표를 추적. 메트릭은 시계열 수가 선형 증가하면 카디널리티 누수, 트레이스는 스팬 수 급증이 샘플링 누수. 주간 리뷰 체계로 임계값을 정하고 초과 시 알림.