Skip to content
Published on

트레이스가 비용이 될 때 — Langfuse의 보존, 샘플링, 마스킹

공유하기
Authors

들어가며 — 양이 비용으로 바뀌는 순간

트레이싱은 도입 초기에 거의 공짜처럼 느껴집니다. 하루 요청이 수천 건일 때는 무엇을 다 남겨도 티가 나지 않습니다. 문제는 그 시기의 습관이 굳는다는 점입니다. 요청이 하루 수백만 건이 되고 사용자가 붙여 넣은 문서가 프롬프트에 통째로 들어가기 시작하면 저장 비용과 개인정보가 동시에 옵니다. 둘은 같은 문제의 두 얼굴입니다. 필요 이상으로 많이 남기고 있다는 것이니까요.

구성과 설정 이름은 2026-08-15에 공식 문서에서 확인했습니다. Langfuse는 버전에 따라 아키텍처가 달라지므로 사용 중인 버전의 문서를 다시 확인하세요. 요금제별 세부 사항처럼 시간에 따라 바뀌는 값은 특히 그렇습니다.

비용이 붙는 네 지점

3편에서 본 저장 계층을 비용 관점으로 다시 보면 지점이 넷으로 나뉩니다.

지점무엇이 쌓이나무엇으로 줄이나
ClickHousetrace, observation, score 행샘플링, 보존 정책
오브젝트 스토리지 원본 이벤트수신 이벤트 전체버킷 수명 주기 정책
오브젝트 스토리지 미디어이미지와 첨부크기 상한, 보존 정책
네트워크와 워커처리량 자체샘플링, 내보내기 필터

주목할 점은 샘플링이 위쪽 두 줄에 동시에 영향을 준다는 것입니다. 애플리케이션에서 아예 보내지 않으면 원본 이벤트도 생기지 않습니다. 반대로 보존 정책은 이미 저장된 것을 나중에 지우는 장치입니다. 순서상 샘플링이 먼저입니다.

샘플링 — trace 단위로 결정된다

샘플링 문서가 정의하는 설정은 두 가지 경로입니다. 환경 변수 LANGFUSE_SAMPLE_RATE, 그리고 생성자 인자인 파이썬의 sample_rate와 JS/TS의 sampleRate입니다. 값의 범위는 0에서 1이고 기본값은 1, 즉 전부 수집입니다.

from langfuse import Langfuse, get_client

Langfuse(sample_rate=0.5)   # 50퍼센트만 수집
langfuse = get_client()

핵심은 결정 단위입니다. 문서는 SDK가 trace 수준에서 샘플링한다고 명시합니다. trace가 선택되면 그 안의 모든 observation과 score가 함께 선택되고, 선택되지 않으면 그 trace의 observation도 score도 전송되지 않습니다.

왜 중요한지는 반대 경우를 생각해 보면 압니다. observation 단위로 샘플링하면 트리에 구멍이 뚫린 트레이스가 남고, 부모는 있는데 자식이 없는 상태에서 지연을 분해하면 숫자가 틀립니다. trace 단위 결정은 남는 것은 온전하고 없는 것은 아예 없다는 성질을 보장합니다.

JS/TS SDK는 OpenTelemetry의 표준 샘플러를 씁니다.

import { TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";

// 20퍼센트 수집
const sampler = new TraceIdRatioBasedSampler(0.2);

샘플링에서 빼야 하는 것

비율 샘플링에는 잘 알려진 함정이 있습니다. 드물게 일어나는 실패가 함께 사라진다는 점입니다. 하루에 열 번 나는 오류를 10퍼센트로 샘플링하면 하루 한 번만 남는데, 조사가 필요한 것은 정확히 그 열 번입니다. 그래서 현실적인 배치는 비율 하나로 끝내지 않습니다.

  • 정상 경로는 낮은 비율로 남깁니다. 지연과 비용의 분포를 보는 데는 표본으로 충분합니다.
  • 실패와 이상 경로는 전부 남깁니다. 코드에서 오류를 잡은 지점의 실행은 별도로 취급합니다.
  • 평가 대상 데이터셋은 샘플링과 무관하게 다룹니다.

또 하나의 축은 아예 보내지 않는 것입니다. 2편에서 본 should_export_span이 그 자리입니다. 이미 OpenTelemetry를 쓰는 애플리케이션이라면 LLM 관측에 필요 없는 HTTP와 데이터베이스 스팬까지 흘러 들어오는데, is_genai_span 같은 판정 함수로 걸러 내면 처리량이 그만큼 줄어듭니다.

마스킹 — 프롬프트에 개인정보가 들어갈 때

LLM 트레이싱의 특수성이 여기서 가장 크게 드러납니다. 일반적인 애플리케이션 트레이스는 식별자와 상태 코드를 남기지만, LLM 트레이스는 사용자가 입력한 문장 전체를 남깁니다. 그리고 사용자는 무엇이든 붙여 넣습니다.

마스킹 문서가 밝히는 파이썬 쪽 방식은 두 가지이고, 차이는 적용 시점입니다. 권장되는 mask_otel_spans는 내보내기 단계, 즉 Langfuse가 어떤 OpenTelemetry 스팬을 내보낼지 결정한 뒤에 스팬 속성을 다룹니다. 예전 방식인 mask는 SDK가 속성을 만드는 시점에 동기적으로 동작합니다.

from langfuse import Langfuse

def mask_otel_spans(*, params: MaskOtelSpansParams) -> Optional[MaskOtelSpansResult]:
    patches = {}
    for identifier, span in params.spans.items():
        patches[identifier] = OtelSpanPatch(
            delete_attributes=("gen_ai.prompt.0.content",),
            set_attributes={"masking.applied": True},
        )
    return MaskOtelSpansResult(span_patches=patches)

langfuse = Langfuse(mask_otel_spans=mask_otel_spans)

JS/TS는 LangfuseSpanProcessormask 함수를 넘기는 하나의 방식입니다. 문서는 이 함수가 observation의 입력, 출력, 메타데이터에 전송 전 적용된다고 설명합니다.

import { LangfuseSpanProcessor } from "@langfuse/otel";

const spanProcessor = new LangfuseSpanProcessor({
  mask: ({ data }) => {
    return data.replace(/\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g, "***MASKED***");
  },
});

반드시 이해해야 할 성질이 하나 있습니다. 마스킹은 애플리케이션 안에서, 데이터가 인프라를 떠나기 전에 일어납니다. 서버 쪽 기능이 아닙니다. 마스킹을 켜지 않은 서비스가 하나라도 있으면 그 서비스의 프롬프트는 원문 그대로 저장되므로, 배포 단위마다 확인해야 합니다.

정규식 마스킹은 카드 번호처럼 형태가 고정된 값에는 잘 듣지만 이름이나 주소에는 약합니다. 그래서 두 겹으로 갑니다. 형태가 있는 값은 마스킹으로 지우고, 애초에 남길 이유가 없는 필드는 2편의 capture_inputcapture_output을 꺼서 수집 자체를 막습니다.

보존 정책 — 기본값은 삭제하지 않음

데이터 보존 문서가 명시하는 기본 동작이 중요합니다. 보존 정책이 없으면 Langfuse는 이벤트 데이터를 자동으로 삭제하지 않으며, 자체 호스팅 인스턴스는 기본적으로 데이터를 무기한 보관합니다. 아무것도 설정하지 않으면 계속 쌓인다는 뜻입니다.

설정은 프로젝트 단위입니다. 프로젝트 설정 화면에서 소유자와 관리자가 조정하거나 프로젝트 API로 설정하며, 문서가 밝히는 최소 보존 기간은 3일입니다. 삭제 대상과 기준 시각도 문서에 정확히 나옵니다.

대상기준이 되는 시각 필드
tracetimestamp
observationstart_time
scoretimestamp
미디어 자산created_at

삭제는 야간 작업으로 수행되며, 문서는 삭제된 자산을 복구할 수 없다고 못 박습니다. 보존 기간을 줄이기 전에 오브젝트 스토리지 내보내기를 설정해 S3, GCS, Azure로 자동 동기화해 두라는 것이 문서의 안내입니다.

자체 호스팅에는 추가 조건이 있습니다. 보존 기능을 켜려면 Langfuse IAM 역할에 모든 버킷에 대한 s3:DeleteObject 권한이 필요하고, 버전 관리가 켜진 S3 버킷이라면 삭제 마커와 이전 버전을 수동으로 지우거나 수명 주기 규칙으로 처리해야 한다고 문서가 명시합니다. 이걸 놓치면 보존 정책을 켜 놓고도 버킷 용량이 줄지 않습니다.

클라우드는 요금제에 따라 기본 접근 기간이 다릅니다. 문서를 읽은 시점 기준으로 Hobby 30일, Core 90일, Pro와 Enterprise 3년이며, 이런 값은 바뀌므로 사용 중인 요금제 문서에서 확인하세요.

원본 이벤트 버킷의 수명 주기

ClickHouse 보존 정책만 걸어 두면 절반만 한 것입니다. 3편에서 봤듯 오브젝트 스토리지에는 수신 이벤트 원본이 그대로 쌓입니다. 오브젝트 스토리지 문서는 일정 일수가 지난 객체를 자동으로 만료시키는 버킷 수명 주기 정책을 설정하라고 권고하며, 참고 값으로 Langfuse 클라우드의 30일을 제시합니다.

기간을 정할 때 고려할 것은 문서가 밝힌 원본 이벤트의 용도입니다. 재시도 작업, 재생과 재해 복구, 선택적인 레코드 조립 중 무엇을 언제까지 할 수 있어야 하는지가 곧 보존 기간입니다.

문서의 조언이 하나 더 있습니다. Langfuse는 업로드된 파일을 ClickHouse의 blob_storage_file_log 테이블로 추적하는데, 이 테이블의 수명을 버킷 정책과 맞추면 처리 최적화에 도움이 됩니다. 한쪽만 줄이면 이미 사라진 객체를 가리키는 행이 계속 남습니다.

큰 payload 다루기

멀티모달 입력과 긴 컨텍스트는 별도의 축입니다. 설정 문서에 나오는 관련 값은 다음과 같습니다.

# 미디어 관련 상한 (괄호 안이 문서 기준 기본값)
LANGFUSE_S3_MEDIA_MAX_CONTENT_LENGTH=1000000000        # 기본 1,000,000,000 바이트
LANGFUSE_S3_MEDIA_DOWNLOAD_URL_EXPIRY_SECONDS=3600     # 기본 3600 초

# 배치 내보내기 한도
BATCH_EXPORT_PAGE_SIZE=500                              # 기본 500
BATCH_EXPORT_ROW_LIMIT=1500000                          # 기본 1,500,000 행

# S3 동시 처리
LANGFUSE_S3_CONCURRENT_WRITES=50                        # 기본 50
LANGFUSE_S3_CONCURRENT_READS=50                         # 기본 50

기본 1GB라는 상한은 사실상 제한이 없는 것에 가깝습니다. 실제 서비스에서는 훨씬 낮게 잡아 두는 편이 안전합니다. 실수로 대용량 파일이 트레이스에 붙는 경로가 생겼을 때 버킷이 아니라 업로드 단계에서 막히기 때문입니다.

payload 자체를 줄이는 방법도 있습니다. 검색 결과 스무 건의 원문을 전부 남길 필요는 대개 없습니다. 문서 식별자와 점수, 상위 몇 건의 요약만 남기면 조사에 필요한 정보는 유지되면서 크기는 크게 줄어듭니다. 설정이 아니라 계측 설계의 문제이고, 2편에서 정한 경계를 다시 보는 일입니다.

비용 축을 데이터로 보기

지금까지는 트레이싱 시스템 자체의 비용이었지만, 이 시스템이 관측하는 모델 호출 비용도 같은 데이터 안에 있습니다.

1편에서 본 대로 generation에는 usage_detailscost_details가 붙습니다. 토큰과 비용 추적 문서는 사용량이 수집되거나 추정되고 가격이 정의된 모델 정의와 매칭되면 수집 시점에 비용이 계산된다고 설명합니다. 자체 호스팅 모델이나 사내 모델은 프로젝트 설정의 모델 항목에서 정규식 패턴과 가격을 등록하며, 사용자가 정의한 모델이 Langfuse가 관리하는 모델보다 우선합니다.

모델 정의를 등록하지 않으면 그 호출들의 비용이 0으로 보인다는 뜻입니다. 비용 대시보드가 실제 청구서와 맞지 않는다면 가장 먼저 볼 곳이 여기입니다.

마치며 — 줄이는 순서

순서는 이렇습니다. 먼저 보내지 않을 것을 정합니다. 내보내기 필터와 수집 끄기입니다. 그다음 얼마나 보낼지, 즉 샘플링을 정합니다. 그다음 무엇을 가릴지, 즉 마스킹을 정합니다. 마지막으로 얼마나 오래 둘지를 정하며 보존 정책과 버킷 수명 주기를 함께 겁니다. 앞쪽일수록 비용과 위험을 동시에 줄이고, 뒤쪽일수록 이미 발생한 것을 정리하는 일입니다.

지금 할 수 있는 점검은 두 가지입니다. 보존 정책이 설정되어 있는지 프로젝트 설정에서 확인하고, 버킷 수명 주기 규칙이 걸려 있는지 스토리지 쪽에서 확인하세요. 둘 다 비어 있다면 지금 쌓이는 모든 것이 영구히 남는 중입니다.

직접 해보기

  • LLM API 비용 계산기 — 모델 호출 비용을 먼저 계산해 보면 트레이싱 저장 비용과 어느 쪽이 큰지 감이 잡힙니다.
  • SLO 에러 버짓 계산기 — 샘플링 비율을 정할 때 어느 정도 표본이면 목표를 판정할 수 있는지 함께 따져 보세요.

시리즈

참고 자료