Skip to content

필사 모드: Langfuse가 트레이스를 ClickHouse에 두는 이유 — 저장 계층의 역할 분담

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

들어가며 — 저장소가 하나가 아니다

Langfuse를 처음 자체 호스팅해 보면 대부분 같은 지점에서 멈칫합니다. 데이터베이스가 하나가 아니기 때문입니다. Postgres도, ClickHouse도, Redis도, S3 호환 오브젝트 스토리지까지 필요합니다. 애플리케이션 하나에 저장소 네 개는 과해 보입니다.

그런데 1편의 데이터 모델을 떠올리면 이유가 보입니다. 조직과 프로젝트 설정은 건수가 적고 정합성이 중요합니다. 트레이스는 하루 수백만 건이 들어오고 한 달치를 훑어 집계해야 합니다. 프롬프트 원문은 한 건이 수 메가바이트가 될 수 있습니다. 이 셋을 같은 엔진에 밀어 넣으면 어느 쪽도 잘 안 됩니다.

구성과 설정 이름은 2026-08-15에 공식 문서에서 확인했습니다. Langfuse는 버전에 따라 아키텍처가 달라지므로 사용 중인 버전의 문서를 다시 확인하세요. 이 글은 자체 호스팅 v4 문서를 기준으로 하며, ClickHouse가 트레이스 저장소가 된 것은 v3부터입니다. v2 시절 구조를 설명하는 글을 보고 있다면 여기서부터 다릅니다.

네 개의 저장소와 두 개의 컨테이너

자체 호스팅 개요 문서가 나열하는 구성 요소는 여섯 개입니다. 애플리케이션 컨테이너 두 개와 저장소 네 개입니다.

구성 요소문서가 밝히는 역할
Langfuse WebLangfuse UI와 API를 제공하는 주 웹 애플리케이션
Langfuse Worker이벤트를 비동기로 처리하는 워커
PostgreSQL트랜잭션 워크로드를 위한 주 데이터베이스
ClickHousetrace, observation, score를 저장하는 고성능 OLAP 데이터베이스
Redis/Valkey큐와 캐시에 쓰이는 인메모리 데이터 구조 저장소
S3/오브젝트 스토리지수신 이벤트, 멀티모달 입력, 대용량 내보내기를 보관하는 객체 저장소

여기에 플레이그라운드와 평가용 LLM API 게이트웨이가 선택 항목으로 붙습니다. 컨테이너 문서에 따르면 웹은 langfuse/langfuse:4 이미지로 3000번 포트를, 워커는 langfuse/langfuse-worker:4 이미지로 3030번 포트를 씁니다.

각 문서가 부여한 역할을 이어 붙이면 이런 그림이 됩니다.

                      ┌───────────────────────────────┐
   SDK / OTLP  ─────▶ │  langfuse-web        :3000    │
                      │  콘솔과 API                    │
                      └──────┬──────────────────┬─────┘
                             │                  │
              원본 이벤트 보관 │                  │ 큐에 적재
                             ▼                  ▼
                  ┌────────────────────┐   ┌──────────────┐
                  │ S3 / 오브젝트       │   │ Redis/Valkey │
                  │  원본 이벤트         │   │  큐와 캐시     │
                  │  멀티모달 입력       │   └──────┬───────┘
                  │  배치 내보내기       │          │
                  └────────────────────┘          ▼
                                        ┌───────────────────────┐
                                        │ langfuse-worker :3030 │
                                        │ 비동기 처리와 삽입      │
                                        └──────┬────────┬───────┘
                                               │        │
                                               ▼        ▼
                              ┌────────────────────┐  ┌──────────────────┐
                              │ ClickHouse         │  │ PostgreSQL       │
                              │  traces            │  │  조직과 프로젝트   │
                              │  observations      │  │  데이터셋         │
                              │  scores            │  │  암호화된 API 키   │
                              └────────────────────┘  └──────────────────┘

수집 경로 — 이벤트가 지나가는 순서

이 구조를 이해하는 열쇠는 캐시 문서의 한 문장입니다. Redis는 API에서 새 이벤트를 빠르게 받아들이고 그 처리와 삽입을 뒤로 미루기 위해 쓰이며, 그래서 요청 급증을 우아하게 넘길 수 있다고 되어 있습니다.

즉 SDK가 보낸 이벤트는 도착 즉시 ClickHouse로 들어가지 않습니다. 웹이 받아 큐에 넣고, 워커가 꺼내 처리한 뒤 삽입합니다. 여기서 두 가지 결과가 나옵니다.

  • 화면에 트레이스가 뜨기까지 약간의 지연이 있는 것은 정상입니다. 문제는 지연이 계속 커지는 경우이고, 그때 볼 것은 워커의 처리량입니다.
  • 워커가 죽어 있어도 API는 계속 200을 반환합니다. 데이터는 큐에 쌓입니다. 워커 컨테이너를 감시하지 않으면 조용히 밀립니다.

오브젝트 스토리지 문서는 원본 이벤트를 보관하는 이유를 재시도 작업, 재생과 재해 복구, 선택적인 레코드 조립이라고 밝힙니다. 처리가 실패해도 원본이 남으니 다시 시도할 수 있다는 뜻이고, 대신 버킷 용량을 계속 먹습니다. 이 부분은 5편에서 다시 봅니다.

Redis 쪽에는 반드시 지켜야 할 설정이 하나 있습니다. 문서는 모든 Redis 또는 Valkey 인스턴스에서 maxmemory-policynoeviction으로 두어야 큐 작업이 축출되지 않는다고 명시합니다. 기본값 그대로면 메모리 압박이 올 때 큐에 있던 이벤트가 조용히 사라집니다.

왜 ClickHouse인가 — 컬럼 지향과 트레이스 질의

ClickHouse 문서는 ClickHouse를 Langfuse 안에서 trace, observation, score 엔티티를 담당하는 주 OLAP 저장소로 설명하며, 높은 쓰기 처리량과 빠른 분석 질의에 최적화되어 있다고 밝힙니다.

왜 그 조합이 성립하는지는 ClickHouse 쪽 설명을 보면 분명합니다. ClickHouse 성능 문서가 드는 근거는 네 가지입니다.

  • 컬럼 지향 저장입니다. 같은 타입과 분포를 가진 값이 함께 놓이기 때문에 압축에 특히 잘 맞습니다.
  • 기본 키 인덱스가 테이블 데이터의 정렬 순서를 정의합니다. 잘 고른 기본 키는 필터를 전체 컬럼 스캔이 아니라 빠른 이진 탐색으로 처리하게 합니다.
  • 벡터화 실행입니다. 질의 계획 연산자가 중간 결과를 한 행씩이 아니라 묶음으로 넘기므로 CPU 캐시 활용이 좋아지고 SIMD 명령을 쓸 수 있습니다.
  • MergeTree의 백그라운드 병합입니다. 추가 데이터 변환을 질의 시점이 아니라 병합 과정에서 처리해 사용자 질의를 크게 빠르게 만듭니다.

이 네 가지를 1편의 저장 형태 이야기와 겹쳐 보면 그림이 완성됩니다. Langfuse는 개념적으로 하나의 observation 테이블을 두고, 각 행에 observation 데이터와 함께 trace 수준 속성의 사본을 담습니다.

관계형 감각으로 보면 낭비입니다. user_id가 같은 trace의 observation 스무 개에 스무 번 복사되니까요. 그런데 컬럼 지향에서는 이야기가 달라집니다. 같은 값이 반복되는 컬럼은 압축이 극단적으로 잘 되고, user_id로 필터링하는 질의는 그 컬럼 하나만 읽으면 됩니다. 조인이 없으니 셔플도 없습니다. 비정규화의 비용이 이 엔진에서는 거의 사라지고, 대신 질의 경로가 단순해집니다.

ClickHouse가 담는 것 — traces, observations, scores

문서가 언급하는 주요 테이블은 traces, observations, scores 세 개입니다. 여기에 오브젝트 스토리지에 올라간 파일을 추적하는 blob_storage_file_log 테이블이 따로 있다고 오브젝트 스토리지 문서가 밝힙니다.

접근 패턴에 대해서도 문서가 명시적입니다. 트레이싱 데이터는 월 단위로 파티셔닝되며, 접근 패턴은 프로젝트와 시간 필터를 중심으로 합니다. 그래서 프로젝트와 기간을 좁힌 질의는 파티션 가지치기의 이득을 그대로 받고, 기간을 열어 둔 채 전체를 훑는 질의는 파티션을 전부 건드립니다. 대시보드 기본 기간을 넓게 잡아 두면 이 비용을 매번 냅니다.

사용자 권한도 문서에 정확히 나옵니다.

-- Langfuse 가 ClickHouse 사용자에게 요구하는 권한
GRANT INSERT, SELECT, ALTER UPDATE, ALTER DELETE,
      ALTER DROP INDEX, CREATE, DROP TABLE
  ON langfuse.*
  TO langfuse;

ALTER UPDATEALTER DELETE가 들어 있는 점을 눈여겨보세요. 읽기 전용 분석 저장소가 아니라 보존 정책에 따른 삭제와 갱신이 일어나는 저장소라는 뜻이고, 5편의 보존 기간 이야기와 직결됩니다.

클러스터 구성에는 제약이 있습니다. 문서는 Langfuse가 현재 다중 샤드 클러스터를 지원하지 않으며 샤드 수가 1이어야 한다고 명시하고, 프로덕션에서는 복제본 최소 3개를 권장합니다. 확장 방향이 샤딩이 아니라 복제와 수직 확장이라는 뜻입니다. 읽기 부하를 분리하고 싶다면 CLICKHOUSE_READ_ONLY_URL이 있습니다. 문서는 이를 UI와 공개 API 질의에 쓰는 읽기 전용 엔드포인트로 설명합니다.

Postgres와 Redis가 맡는 몫

Postgres 문서가 밝히는 저장 대상은 사용자, 조직, 프로젝트, 데이터셋, 암호화된 API 키, 설정입니다. 건수가 적고 정합성이 중요하며 트랜잭션이 필요한 것들이고, 트레이스는 여기 없습니다.

Redis는 앞에서 본 큐 역할에 더해 캐시를 맡습니다. 문서가 명시하는 캐시 대상은 API 키와 프롬프트이며 각각 기본 300초의 수명을 가집니다. API 키는 평문으로 저장되지 않고 해시 또는 암호화된 형태만 캐시됩니다.

여기서 자체 호스팅을 자주 넘어뜨리는 조건이 하나 나옵니다. 자체 호스팅 개요 문서는 모든 인프라 구성 요소가 UTC 시간대로 동작해야 하며, UTC가 아닌 설정은 질의가 잘못된 결과나 빈 결과를 반환하게 만든다고 명시합니다. Postgres 문서와 ClickHouse 문서 양쪽에 같은 경고가 있습니다.

큰 payload는 어디로 가나

LLM 트레이싱이 일반적인 분산 트레이싱과 결정적으로 다른 지점이 여기입니다. 스팬 하나에 붙는 데이터가 큽니다. 긴 컨텍스트를 넣은 프롬프트, 검색된 문서 스무 개의 원문, 이미지 입력이 전부 한 observation에 매달립니다.

오브젝트 스토리지 문서는 이 저장소의 용도를 원본 이벤트, 멀티모달 콘텐츠, 배치 내보내기 세 가지로 정리합니다. 멀티모달 쪽에는 크기 상한이 설정으로 있습니다. LANGFUSE_S3_MEDIA_MAX_CONTENT_LENGTH의 기본값은 1,000,000,000바이트, 즉 1GB입니다. 내려받기 URL의 유효 기간은 LANGFUSE_S3_MEDIA_DOWNLOAD_URL_EXPIRY_SECONDS가 기본 3600초로 정합니다.

공식 지원 대상은 Amazon S3, Google Cloud Storage, Azure Blob Storage, MinIO/AIStor, Cloudflare R2이고, OCI Object Storage와 Tigris를 비롯한 S3 호환 서비스는 커뮤니티 지원입니다. 필요한 최소 권한은 버킷과 객체 양쪽에 대한 s3:PutObject, s3:ListBucket, s3:GetObject이며, 보존 정책까지 쓰려면 s3:DeleteObject가 추가로 필요합니다.

스키마는 안정적인 계약이 아니다

ClickHouse에 데이터가 있으면 자연스럽게 드는 생각이 있습니다. 그냥 직접 질의하면 되지 않나. 문서는 여기에 명확한 경고를 답니다. ClickHouse 스키마는 안정적인 API 계약이 아닙니다. 메이저 업그레이드, 백그라운드 마이그레이션, 성능 개선 작업이 테이블과 컬럼, 중복 제거 동작, 조인 방식을 바꿀 수 있으므로, 직접 질의를 만들었다면 Langfuse를 올릴 때마다 다시 검증해야 합니다.

그래서 판단은 이렇게 갈립니다. 애플리케이션이 의존하는 지표는 공개 API로 가져오고, ClickHouse 직접 질의는 일회성 조사나 사내 분석처럼 깨져도 사람이 고칠 수 있는 곳에만 씁니다. 6편에서 다룰 메트릭 API가 앞쪽 자리입니다.

실제 컬럼 정의가 필요하다면 추측하지 말고 마이그레이션 파일을 읽는 편이 정확합니다. ClickHouse 문서는 수동 마이그레이션 절차를 설명하면서 저장소의 ./packages/shared/clickhouse/migrations/ 경로를 가리킵니다. 아래는 스키마를 확인하는 방법을 보여 주는 예시입니다.

-- 예시: 실제 컬럼 정의를 눈으로 확인한다
SHOW TABLES FROM langfuse;
SHOW CREATE TABLE langfuse.observations;

-- 파티션이 실제로 어떻게 잘려 있는지 본다
SELECT partition, sum(rows) AS rows, formatReadableSize(sum(bytes_on_disk)) AS size
FROM system.parts
WHERE database = 'langfuse' AND table = 'observations' AND active
GROUP BY partition
ORDER BY partition DESC;

두 번째 질의는 스키마를 추측하지 않아도 안전합니다. system.parts는 Langfuse가 아니라 ClickHouse가 제공하는 시스템 테이블이기 때문입니다. 파티션별 행 수와 디스크 사용량이 나오므로 어느 달이 비용을 먹는지 바로 보입니다.

버전이 갈리는 지점

이 시리즈에서 버전 표기가 가장 중요한 절입니다. 저장 계층 요구사항이 메이저 버전에 따라 다릅니다.

구성 요소Langfuse v3Langfuse v4
ClickHouse24.3 이상25.12 이상, 26.4 권장
PostgreSQL12 이상최소 15, 16 권장
Redis7 이상7.2 권장
Valkey8 이상 공식 지원

ClickHouse 요구 버전이 올라간 이유도 문서에 있습니다. v4가 쓰는 경량 업데이트, JSON 타입, 전문 검색 기능 때문입니다. 이 값들은 바뀔 수 있으니 배포 전에 사용 중인 버전의 문서에서 다시 확인하세요.

버전 정책 문서 기준으로 v2는 수명 종료, v3는 폐기 예정, v4는 정식 출시입니다. v3에서 v4로 넘어오며 기존 배치 수집이 OpenTelemetry로 교체되고, 구형 읽기 API가 제거되었으며, Observations API v2와 메트릭 API v2가 도입되었습니다. 인터넷에서 찾은 Langfuse 아키텍처 글은 어느 버전을 말하는지부터 확인해야 합니다.

마치며 — 저장 계층을 알면 진단이 빨라진다

역할 분담을 정리하면 트레이스 본체는 ClickHouse, 조직과 설정은 Postgres, 큐와 캐시는 Redis, 원본 이벤트와 큰 payload는 오브젝트 스토리지입니다. 이 지도가 있으면 장애 때 어디를 볼지가 달라집니다.

  • 화면에 트레이스가 늦게 뜬다 → 워커와 Redis 큐
  • 로그인은 되는데 트레이스가 비어 있다 → ClickHouse 연결이나 마이그레이션
  • 트레이스는 보이는데 첨부가 안 열린다 → 오브젝트 스토리지 권한이나 프리사인 URL 만료
  • 질의가 빈 결과를 준다 → 시간대 설정

지금 할 수 있는 점검은 파티션별 크기를 뽑아 보는 것입니다. 어느 달이 얼마나 차지하는지 모르면 보존 정책을 정할 근거가 없습니다.

직접 해보기

  • DuckDB 데이터 분석 놀이터 — 같은 데이터를 행 지향과 컬럼 지향으로 다뤄 보면 압축과 스캔 비용의 차이가 체감됩니다.
  • PostgreSQL 놀이터 — 트랜잭션 데이터와 분석 데이터를 한 엔진에 넣었을 때 무엇이 먼저 무너지는지 실험해 보세요.

관측 데이터를 ClickHouse에 넣는 설계 자체가 궁금하다면 관측 데이터를 ClickHouse에 넣는다는 것에서 정렬 키와 TTL 설계를 더 자세히 다룹니다.

시리즈

참고 자료

현재 단락 (1/109)

Langfuse를 처음 자체 호스팅해 보면 대부분 같은 지점에서 멈칫합니다. 데이터베이스가 하나가 아니기 때문입니다. Postgres도, ClickHouse도, Redis도, S3...

작성 글자: 0원문 글자: 7,718작성 단락: 0/109