Langfuse, 그리고 ClickHouse: LLM 옵저버빌리티 데이터 스택 해부
Langfuse, 그리고 ClickHouse: LLM 옵저버빌리티 데이터 스택 해부

Langfuse, 그리고 ClickHouse: LLM 옵저버빌리티 데이터 스택 해부

ClickHouse 분류
3rd Party
Type
Introduction
작성자

Ken

LLM/에이전트 애플리케이션의 트레이스를 어디에, 어떻게 저장할 것인가. 이 글에서는 오픈소스 LLM 옵저버빌리티 플랫폼 Langfuse를 소개하고, 2026년 1월 ClickHouse의 Langfuse 인수가 갖는 기술적 의미를 짚는다. 마지막으로 ClickHouse 운영자 관점에서 Langfuse repository의 테이블 구조를 해부하고, 데이터가 폭증할 때 어디를 튜닝해야 하는지를 — Langfuse 팀이 v3에서 v4로 가며 직접 부딪힌 사례를 근거로 — 제언한다.

1. Langfuse 소개

Langfuse는 LLM 애플리케이션과 에이전트를 빌드·테스트·모니터링하기 위한 오픈소스 플랫폼이다. 2023년 Marc Klingen, Maximilian Deichmann, Clemens Rawert가 창업했고 Y Combinator를 거쳤다. 2025년 말 기준 GitHub 20K+ stars, 월 26M+ SDK 설치, 6M+ Docker pull을 기록했으며 Fortune 50 중 19개사, Fortune 500 중 63개사가 사용한다.

핵심 제품 영역은 네 가지다.

영역
설명
Observability (Tracing)
LLM 호출·툴 실행·에이전트 스텝을 트레이스/옵저베이션 단위로 추적. 입력·출력·비용·지연시간을 실시간 기록
Prompt Management
프롬프트 버전 관리, 라벨링, SDK/서버 캐싱을 통한 저지연 배포
Evaluations
프로덕션 출력 품질을 평가(LLM-as-a-judge, 휴먼 어노테이션, 코드 평가기 등)
Metrics / Dashboards
비용·지연·품질 지표를 시계열로 집계해 대시보드로 렌더링

기술적으로 중요한 두 가지 성질이 있다. 첫째, OpenTelemetry 네이티브다. 2025년 1월 OTel 엔드포인트를 출시하고 여름에 1st-party SDK를 전부 OTel 프로토콜로 이관했다. 현재 Langfuse Cloud 옵저베이션의 약 60%가 OTel 엔드포인트를 통해 들어온다. 둘째, 코어가 MIT 라이선스 오픈소스이며, Cloud(완전 관리형)와 self-hosting(프로덕션 스케일) 양쪽으로 배포할 수 있다. 규제 산업 — 금융·헬스케어 등 데이터를 외부로 내보낼 수 없는 조직 — 이 self-hosting을 선택하는 경우가 많다.

2. Langfuse와 ClickHouse — 아키텍처와 인수

2.1 인수: 데이터 플랫폼이 AI 피드백 루프를 흡수하다

2026년 1월 16일, ClickHouse는 $400M Series D(Dragoneer 주도, 기업가치 $15B)를 발표하면서 Langfuse 인수네이티브 Postgres 서비스를 동시에 공개했다. 이는 ClickHouse가 HyperDX(ClickStack 옵저버빌리티), PeerDB 등에 이어 진행한 일련의 인수 플레이북의 연장선이다.

인수의 핵심 논리는 단순하다. 전통적 옵저버빌리티가 시스템 헬스/성능 메트릭을 본다면, LLM 옵저버빌리티는 비결정적 AI 시스템의 출력이 정확하고 안전하며 의도에 부합하는가를 본다. AI 에이전트가 프로덕션에 깊숙이 박히면서 모든 LLM 호출을 구조화된 쿼리 가능한 레코드로 전환하는 것이 엔터프라이즈 도입의 전제 조건이 되었고, ClickHouse는 바로 그 "쿼리 가능한 레코드"를 담는 고처리량·저지연 엔진을 제공한다. Langfuse는 MIT 라이선스와 self-hosting을 유지하기로 했다.

기술적 정합성도 강했다. Langfuse는 인수 협의 이전부터 이미 v3 내부 스토리지로 ClickHouse를 쓰고 있었다. Postgres가 수백만 행 규모에서 병목에 부딪히자 2024년 12월 트레이싱 데이터를 ClickHouse로 이관했기 때문이다.

2.2 왜 Postgres에서 ClickHouse로 갔나

Langfuse는 처음 Postgres 위에 만들어졌지만, 옵저버빌리티 데이터 특성상 Postgres가 최적이 아님을 알고 있었다. 이유는 세 가지로 정리된다.

  • 컬럼형 레이아웃: OLAP 데이터베이스는 분석 쿼리(예: 시간대별 LLM 비용)에 필요한 컬럼만 스캔한다. 행 기반 Postgres 대비 압도적으로 적은 I/O.
  • 멀티노드 인서트 스케일링: 인입량 급증을 수평 확장으로 흡수해야 했다.
  • 오픈소스 라이선스: self-hosting 제품이므로 OSS 라이선스로 어디서나 배포 가능한 DB가 필요했다.

ClickHouse가 세 조건을 모두 만족했다.

2.3 v3 아키텍처: 큐 기반 비동기 인입

Langfuse v3는 두 개의 애플리케이션 컨테이너와 네 개의 스토리지 컴포넌트로 구성된다.

각 컴포넌트의 역할은 다음과 같다.

컴포넌트
역할
Langfuse Web
UI와 공개 API 서빙. 인입 배치를 받아 즉시 S3에 적재
Langfuse Worker
큐에서 이벤트를 꺼내 토큰화·파싱 등 CPU 무거운 처리 후 ClickHouse로 flush
PostgreSQL
트랜잭션 워크로드(프로젝트·유저·설정 등)의 메인 DB
ClickHouse
트레이싱 데이터(traces/observations/scores)의 OLAP 스토리지. 대시보드·메트릭·테이블 렌더링
Redis/Valkey
이벤트 큐(BullMQ)와 API 키·프롬프트 read-through 캐시
S3/Blob
원본 이벤트, 멀티모달 입력, 대용량 export의 영구 저장소이자 네이티브 백업

핵심 설계 — 큐 기반 트레이스 인입. 모든 트레이스는 Web 컨테이너가 배치로 받아 즉시 S3에 쓴다. Redis에는 S3 참조만 들어간다. 이후 Worker가 S3에서 트레이스를 픽업해 ClickHouse로 인입한다. 요청 부하가 급증해도 DB 제약으로 인한 타임아웃/에러가 발생하지 않게 인입과 처리를 분리한 구조다.

v3에서 /api/public/ingestion 엔드포인트는 비동기가 되었다. 이벤트를 받아 큐에 넣고 207을 반환하므로, 인입 직후 즉시 조회되지 않을 수 있다(eventual consistency).

3. Langfuse Enterprise 기능

Langfuse는 2025년 6월 이후 모든 "제품 기능"을 MIT로 공개했다. 트레이싱, 프롬프트 관리, 평가, 플레이그라운드, 대시보드는 OSS에서 사용량·시트 제한 없이 동작한다. self-hosting과 Cloud가 동일한 코드베이스·스키마를 쓰므로 확장성에도 버전 간 차이가 없다.

엔터프라이즈 라이선스(LANGFUSE_EE_LICENSE_KEY)가 필요한 것은 컴플라이언스/거버넌스 성격의 주변 기능이다. EE 코드는 repository의 /ee 디렉터리에 격리되어 license 체크로 게이팅된다.

EE 기능
내용
Project-level RBAC Roles
조직 단위 RBAC(OWNER/ADMIN/MEMBER/VIEWER/NONE)는 OSS. 프로젝트 단위 세분화 역할은 EE
SSO Enforcement
SSO(SAML/OIDC) 연동 자체는 OSS. 조직에 SSO를 강제하는 것은 EE
Org Management API & SCIM
유저 프로비저닝·역할 할당·프로젝트 셋업 자동화(SCIM)
Instance Management API
인스턴스/조직을 프로그래밍 방식으로 관리하는 REST API
Audit Logs
컴플라이언스용 활동 추적 로그
Data Retention Policies
설정 가능한 데이터 라이프사이클 자동 관리
Server-Side Data Masking
서버측 데이터 마스킹
Protected Prompt Labels
보호된 프롬프트 라벨
UI Customization
코브랜딩, 커스텀 링크, 모듈 가시성, LLM API 기본값
Organization Creators
조직 생성 권한 제한

라이선스/플랜 구조를 정리하면:

  • OSS (MIT): 전 제품 기능, 사용량 무제한. /ee 외 전부 MIT
  • Enterprise (self-host): 위 EE 모듈 + 라이선스 키
  • Cloud: Core($29/mo) → Pro($199/mo, 3년 보존 + SOC2/ISO27001) → Teams(+SSO) → Enterprise(SCIM + audit logs + SLA)
  • Enterprise (커스텀 계약): EE 전체 + 고급 SAML/SCIM SSO, 확장 audit-log 보존, 우선 지원·SLA, 법무 red-line, 전문 서비스
한국 규제 환경 관점. 금융권(전자금융감독규정)·ISMS-P 대응 시 실질적으로 필요한 항목은 Server-Side Data Masking(PII/민감정보 마스킹), Audit Logs(접근·변경 추적), Data Retention Policies(보존기간 강제), SCIM/SSO Enforcement(중앙 IdP 연동·강제), Project-level RBAC(망분리 환경에서 프로젝트별 최소권한)이다. self-hosting + EE 조합이면 데이터를 자체 인프라(또는 BYOC) 내에 두면서 이 요건들을 충족할 수 있다.

4. ClickHouse 관점에서 본 Langfuse repository와 튜닝 제언

여기서부터가 핵심이다. Langfuse를 self-host로 운영하는 ClickHouse 엔지니어 입장에서, repository의 트레이싱 테이블이 어떻게 설계되어 있고 데이터가 증가할 때 어디가 먼저 아파오는지를 본다. 근거는 Langfuse 핸드북, self-hosting 스케일링 문서, 그리고 v3→v4 전환기("Simplifying Langfuse for Scale")에서 팀이 직접 공개한 사례다.

4.1 스키마 구조: 모든 트레이싱 테이블 = ReplacingMergeTree

ClickHouse에는 네 개의 주요 테이블이 있다: traces, observations, scores, event_log. 모두 ReplacingMergeTree 엔진을 쓴다. 동일 정렬 키 + 더 높은 버전 컬럼으로 새 행을 써서 백그라운드 머지 시 자동 중복 제거 → 즉 업데이트를 행 교체로 표현하기 위함이다. traces/observations/scores 모두 업데이트가 허용되므로 이 엔진이 중심이다.

실제 traces 테이블 DDL의 핵심 부분(self-host 마이그레이션 기준):

설계 포인트를 ClickHouse 관점에서 짚으면:

  • 정렬 키 선두가 project_id + 시간. ClickHouse는 정렬 키 기준으로 데이터를 prune하므로, 쿼리에 시간 필터가 없으면 프로젝트 전체 파트를 스캔한다. 이게 느린 UI/API의 1순위 원인이다.
  • metadataMap(LowCardinality(String), String) — 키 카디널리티가 낮은 메타데이터에 최적. 여기에 mapKeys/mapValuesbloom_filter 스킵 인덱스를 걸어 메타데이터 필터를 가속한다.
  • input/outputNullable(String) CODEC(ZSTD(3)) — 대용량 텍스트라 ZSTD 압축. 단, 컬럼형 + Wide Part에서는 이 큰 컬럼을 끌어오는 비용이 크다(후술).
  • idbloom_filter(0.001) — 고카디널리티 String 단건 조회 가속.
  • event_ts가 ReplacingMergeTree 버전 컬럼, is_deleted로 소프트 삭제 표현.

4.2 ReplacingMergeTree의 본질적 비용: read-time dedup

ReplacingMergeTree의 중복 제거는 eventual이며 언제 일어날지 보장되지 않는다. 인입 직후 수 분~수 시간 동안 중복이 남아 있어 짧은 구간 대시보드가 부정확해진다. 그래서 Langfuse는 읽기 시점에 추가 중복 제거를 한다. 세 가지 방식을 상황에 맞게 섞어 쓴다.

여기서 결정적 인사이트가 나온다. OpenTelemetry로만 인입하는 프로젝트는 옵저베이션이 단일 불변(immutable) 행으로 들어오므로 FINAL이 불필요하다. 레거시 SDK는 start/end/update 이벤트를 따로 보내 같은 옵저베이션의 여러 버전을 만들지만, OTel span은 불변이기 때문이다.

4.3 인서트 경로

ClickHouse는 큰 인서트에서 이득을 본다(인서트마다 초기 파트 생성 → 나중에 머지). Langfuse는 두 단계 배칭을 쓴다.

  1. Worker가 모든 ClickHouse write를 ClickHouseWriter인메모리 버퍼에 모았다가 설정된 배치 크기/간격으로 flush.
  2. ClickHouse 측 **async_inserts*로 서버에서 한 번 더 누적 후 ACK·디스크 기록.

4.4 데이터 증가에 따른 튜닝 포인트 — 제언

운영 규모가 커질수록 다음 순서로 손대는 것을 권한다. 앞쪽일수록 비용 대비 효과가 크다.

① 시간 필터 규율 (가장 먼저, 가장 효과 큼)

트레이싱 데이터는 project_id + 시간으로 인덱싱된다. 모든 무거운 쿼리에 시간 필터를 강제하면 데이터 pruning이 작동한다. Langfuse가 V2 → V2(신규) Observations/Metrics API를 다시 설계하며 시간 필터를 필수로 만든 이유가 이것이다. 구 GET /api/public/traces는 시간 필터가 없으면 풀스캔을 유발했다. 사내 대시보드/쿼리도 같은 규율을 적용할 것.

② OTel 전용 프로젝트에서 FINAL 제거

  • 혼합 배포: LANGFUSE_SKIP_FINAL_FOR_OTEL_PROJECTS=true → OTel로 인입하는 프로젝트를 Redis에 24h TTL로 마킹, 이후 옵저베이션 읽기에서 FINAL을 건너뜀(레거시로 돌아가면 안전하게 원복).
  • 전 프로젝트 OTel 확신 시: LANGFUSE_API_CLICKHOUSE_DISABLE_OBSERVATIONS_FINAL=true로 Redis 조회 없이 전역 비활성(레거시 인입이 하나라도 있으면 금지 — stale/중복 위험).

③ 인입 경로의 ClickHouse read 제거

Worker는 기본적으로 기존 이벤트를 ClickHouse에서 읽어 병합한다 → ClickHouse 부하 증가, 처리량 제한. v3 이전에서 마이그레이션하지 않은 배포라면 전체 이력이 S3에 있으므로 선택적이다. LANGFUSE_SKIP_INGESTION_CLICKHOUSE_READ_MIN_PROJECT_CREATE_DATE를 첫 프로젝트 생성 이전 날짜(예: 2025-01-01)로 설정해 read를 스킵.

④ 인입과 UI의 컴퓨트 분리

  • 애플리케이션 레벨: langfuse-web인입 처리용UI 처리용 두 디플로이로 분리하고 /api/public/ingestion*, /media*, /otel* 트래픽을 인입용으로 라우팅.
  • DB 레벨(ClickHouse Cloud / BYOC 한정): CLICKHOUSE_READ_ONLY_URL로 UI·공개 API read를 **별도 컴퓨트 그룹(warehouse)**으로 보낸다. 인입 인서트·백그라운드 머지와 read가 같은 CPU/메모리를 두고 경합하지 않게 격리. 이것이 SharedMergeTree 기반 compute-compute separation의 직접 활용 사례다.

⑤ 디스크 증가 제어

  • TTL: traces, observations, scores, event_log에 TTL 적용(또는 Data Retention 정책, EE).
  • system log 테이블이 디스크를 잠식한다. 기본 ClickHouse는 trace_log, text_log, opentelemetry_span_log, asynchronous_metric_log, metric_log, latency_log에 TTL이 없고 쿼리 프로파일러가 system.trace_log에 계속 쓴다. Langfuse는 이들을 안 읽으므로 제거하거나 짧은 TTL + 프로파일러 비활성을 권장(query_log, part_log, error_log는 디버깅용으로 유지).
<!-- /etc/clickhouse-server/config.d/ 에 마운트 -->
<clickhouse>
    <trace_log remove="1"/>
    <text_log remove="1"/>
    <opentelemetry_span_log remove="1"/>
    <asynchronous_metric_log remove="1"/>
    <metric_log remove="1"/>
    <latency_log remove="1"/>
</clickhouse>

가장 큰 테이블 식별 쿼리:

SELECT table, formatReadableSize(size) AS size, rows FROM (
    SELECT table, database, sum(bytes) AS size, sum(rows) AS rows
    FROM system.parts
    WHERE active
    GROUP BY table, database
    ORDER BY size DESC
)

⑥ 삭제/업데이트의 머지 압력 완화 (ClickHouse 25.7+)

삭제는 mutation을 유발하고(_row_exists 컬럼 추가 → Wide Part는 컬럼 재작성, Compact Part는 파트 재작성), 대량 삭제 시 백그라운드 머지를 폭증시킨다.

  • CLICKHOUSE_LIGHTWEIGHT_DELETE_MODE를 기본 alter_update에서 lightweight_update로 → ALTER TABLE ... DELETE mutation 대신 lightweight delete.
  • CLICKHOUSE_USE_LIGHTWEIGHT_UPDATE=true → 트레이싱 테이블 업데이트를 네이티브 UPDATE로.
  • 데이터 보존 작업이 타임아웃나면 LANGFUSE_CLICKHOUSE_DELETION_TIMEOUT_MS(기본 600000ms) 상향.

⑦ 수직 확장과 컴퓨트/스토리지 분리

ClickHouse는 수직 확장 설계다. 느린 UI는 메모리를 키우면 대개 개선된다(대형 배포 16GiB+ 권장). 단, 디스크 증가가 운영 병목이 되면 ClickHouse Cloud/BYOC의 SharedMergeTree로 스토리지를 컴퓨트와 독립적으로 키우는 편이 self-managed OSS에서 수동 샤드 플래닝/볼륨 확장하는 것보다 낫다.

4.5 한 발 더: v4 "observations-first" 비정규화에서 배우는 것

Langfuse가 v3에서 부딪힌 한계와 v4(2026-03 Cloud 베타, self-host 예정)의 해법은, 그대로 대규모 ClickHouse 스키마 설계의 교과서다.

문제(v3). ① "이 유저의 총비용은?" 같은 질문이 수십억 행 traces ⨝ observations 조인을 요구 → 대규모에서 응답시간 천장. ② ReplacingMergeTree read-time dedup이 모든 이력 쿼리에 숨은 세금. ③ 업데이트 diff를 S3에 저장·머지하는 비용. 1년간 데이터 19배, 노드 15배로 키웠는데도 일부 장기 구간 쿼리가 OOM으로 죽었다.

해법(v4). 불변(immutable) 레코드 기반의 단일 wide 테이블로 비정규화. 조인과 read-time dedup을 동시에 제거. trace_id는 독립 엔티티가 아니라 session_id/user_id 같은 상관관계 핸들이 된다. (옵저버빌리티 업계가 수렴 중인 "Wide Events" 패턴 — Uber의 ClickHouse 로깅 재설계와 같은 결론.) 결과: 대형 프로젝트 대시보드 로드 최소 10x, 초기 테이블 로드 초 → 밀리초.

그 과정에서 드러난 ClickHouse 튜닝 교훈(그대로 적용 가능):

증상
원인
처방
스캔이 비효율
파티션당 파트 ~1,000개 (정상은 150~200)
파트 수 억제, 인서트 배칭 강화
인덱스가 과밀
granule 최대 크기가 기본 10MiB로 제한
행 수는 8192 유지, granule 최대 크기 10MiB → 64MiB로 상향 → 인덱스 희소화, lookup·pruning 개선
머지가 진행 안 됨
행이 수 MB(거대 String/Map)라 파트가 ~100GiB에서 정체(머지 한계 150GiB 근처)
중복 데이터 제거로 행 크기↓ → 파트당 레코드↑, 머지 재개
대용량 페이로드가 코어 쿼리까지 무겁게 함
input/output/metadata가 매 스캔에 끌려옴
derived 테이블 패턴: MV로 대용량 필드 truncate한 코어 테이블 분리. 대시보드/목록은 코어, 단건 상세만 full
인서트 파이프라인이 느려짐
MV 추가가 인서트 병렬화를 끔
parallel_view_processing=1
노드 간 데이터 전파 지연(최대 25분)
빈번한 TTL 파티션 삭제 → Keeper에 dead part 폭증
TTL 파티션 삭제 빈도 관리, 업스트림 패치
마지막 줄이 특히 중요하다. 3분 단위 파티션 + TTL 드롭 같은 공격적 라이프사이클은 Keeper 메타데이터를 오염시켜 파트 전파를 지연시킨다. self-host에서 짧은 파티션 + TTL을 남발하면 같은 함정에 빠질 수 있다.

5. 정리

Langfuse의 데이터 스택은 "큐 기반 비동기 인입 + S3 원본 보관 + ClickHouse OLAP"이라는, 고처리량 옵저버빌리티의 정석 구조다. ClickHouse 운영자 입장에서 Langfuse를 스케일링할 때 기억할 것은 단순하다 — 시간으로 prune하라, OTel이면 FINAL을 버려라, 읽기와 쓰기 컴퓨트를 분리하라, system log를 굶겨라, 그리고 거대한 페이로드는 derived 테이블로 떼어내라. Langfuse 팀이 19배 성장 동안 직접 부딪히며 정리한 이 교훈들은 LLM 트레이싱뿐 아니라 일반 와이드-이벤트 옵저버빌리티 설계에도 그대로 적용된다.

참고 자료