Split View: Langfuse가 트레이스를 ClickHouse에 두는 이유 — 저장 계층의 역할 분담
Langfuse가 트레이스를 ClickHouse에 두는 이유 — 저장 계층의 역할 분담
- 들어가며 — 저장소가 하나가 아니다
- 네 개의 저장소와 두 개의 컨테이너
- 수집 경로 — 이벤트가 지나가는 순서
- 왜 ClickHouse인가 — 컬럼 지향과 트레이스 질의
- ClickHouse가 담는 것 — traces, observations, scores
- Postgres와 Redis가 맡는 몫
- 큰 payload는 어디로 가나
- 스키마는 안정적인 계약이 아니다
- 버전이 갈리는 지점
- 마치며 — 저장 계층을 알면 진단이 빨라진다
- 직접 해보기
- 시리즈
- 참고 자료
들어가며 — 저장소가 하나가 아니다
Langfuse를 처음 자체 호스팅해 보면 대부분 같은 지점에서 멈칫합니다. 데이터베이스가 하나가 아니기 때문입니다. Postgres도, ClickHouse도, Redis도, S3 호환 오브젝트 스토리지까지 필요합니다. 애플리케이션 하나에 저장소 네 개는 과해 보입니다.
그런데 1편의 데이터 모델을 떠올리면 이유가 보입니다. 조직과 프로젝트 설정은 건수가 적고 정합성이 중요합니다. 트레이스는 하루 수백만 건이 들어오고 한 달치를 훑어 집계해야 합니다. 프롬프트 원문은 한 건이 수 메가바이트가 될 수 있습니다. 이 셋을 같은 엔진에 밀어 넣으면 어느 쪽도 잘 안 됩니다.
구성과 설정 이름은 2026-08-15에 공식 문서에서 확인했습니다. Langfuse는 버전에 따라 아키텍처가 달라지므로 사용 중인 버전의 문서를 다시 확인하세요. 이 글은 자체 호스팅 v4 문서를 기준으로 하며, ClickHouse가 트레이스 저장소가 된 것은 v3부터입니다. v2 시절 구조를 설명하는 글을 보고 있다면 여기서부터 다릅니다.
네 개의 저장소와 두 개의 컨테이너
자체 호스팅 개요 문서가 나열하는 구성 요소는 여섯 개입니다. 애플리케이션 컨테이너 두 개와 저장소 네 개입니다.
| 구성 요소 | 문서가 밝히는 역할 |
|---|---|
| Langfuse Web | Langfuse UI와 API를 제공하는 주 웹 애플리케이션 |
| Langfuse Worker | 이벤트를 비동기로 처리하는 워커 |
| PostgreSQL | 트랜잭션 워크로드를 위한 주 데이터베이스 |
| ClickHouse | trace, 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-policy를 noeviction으로 두어야 큐 작업이 축출되지 않는다고 명시합니다. 기본값 그대로면 메모리 압박이 올 때 큐에 있던 이벤트가 조용히 사라집니다.
왜 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 UPDATE와 ALTER 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 v3 | Langfuse v4 |
|---|---|---|
| ClickHouse | 24.3 이상 | 25.12 이상, 26.4 권장 |
| PostgreSQL | 12 이상 | 최소 15, 16 권장 |
| Redis | 7 이상 | 7.2 권장 |
| Valkey | — | 8 이상 공식 지원 |
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 설계를 더 자세히 다룹니다.
시리즈
- 이전 글: Langfuse SDK 계측
- 다음 글: Langfuse 자체 호스팅 — 배포와 첫 기동
참고 자료
- Langfuse 자체 호스팅 개요: https://langfuse.com/self-hosting
- Langfuse ClickHouse: https://langfuse.com/self-hosting/infrastructure/clickhouse
- Langfuse Postgres: https://langfuse.com/self-hosting/infrastructure/postgres
- Langfuse 캐시와 큐: https://langfuse.com/self-hosting/infrastructure/cache
- Langfuse 오브젝트 스토리지: https://langfuse.com/self-hosting/infrastructure/blobstorage
- Langfuse 컨테이너: https://langfuse.com/self-hosting/infrastructure/containers
- ClickHouse가 빠른 이유: https://clickhouse.com/docs/concepts/why-clickhouse-is-so-fast
Why Langfuse Puts Traces in ClickHouse — How the Storage Layer Splits the Work
- Opening — There Is Not One Datastore
- Four Datastores and Two Containers
- The Ingestion Path — The Order an Event Travels
- Why ClickHouse — Columnar Storage and Trace Queries
- What ClickHouse Holds — traces, observations, scores
- What Postgres and Redis Carry
- Where Large Payloads Go
- The Schema Is Not a Stable Contract
- Where the Versions Diverge
- Closing — Knowing the Storage Layer Speeds Up Diagnosis
- Try It Yourself
- Series
- References
Opening — There Is Not One Datastore
Most people stall at the same point the first time they self-host Langfuse: there is not one database. You need Postgres, ClickHouse, Redis, and an S3-compatible object store. Four datastores for one application looks excessive.
Recall the data model from the first post and the reason appears. Organization and project settings are low in volume and high in consistency requirements. Traces arrive by the millions per day and have to be aggregated across a month. A single raw prompt can run to several megabytes. Push all three into the same engine and none of them work well.
Component and configuration names here were verified against the official documentation on 2026-08-15. Langfuse's architecture differs by version, so re-check the docs for the version you are running. This post works from the self-hosting v4 documentation, and ClickHouse became the trace store starting with v3. If you are reading an article that describes the v2 structure, it diverges from here.
Four Datastores and Two Containers
The self-hosting overview lists six components: two application containers and four datastores.
| Component | Role as the documentation states it |
|---|---|
| Langfuse Web | The main web application serving the Langfuse UI and APIs |
| Langfuse Worker | A worker that asynchronously processes events |
| PostgreSQL | The main database for transactional workloads |
| ClickHouse | High-performance OLAP database which stores traces, observations, and scores |
| Redis/Valkey | A fast in-memory data structure store used for queue and cache operations |
| S3/Blob Storage | Object storage to persist incoming events, multi-modal inputs, and large exports |
An LLM API gateway attaches as an optional component for the playground and for evals. According to the containers documentation, web runs the langfuse/langfuse:4 image on port 3000 and worker runs langfuse/langfuse-worker:4 on port 3030.
Stitch the roles each page assigns together and you get this picture.
┌───────────────────────────────┐
SDK / OTLP ─────▶ │ langfuse-web :3000 │
│ console and API │
└──────┬──────────────────┬─────┘
│ │
persist raw │ │ enqueue
events ▼ ▼
┌────────────────────┐ ┌──────────────┐
│ S3 / object store │ │ Redis/Valkey │
│ raw events │ │ queue+cache │
│ multi-modal input │ └──────┬───────┘
│ batch exports │ │
└────────────────────┘ ▼
┌───────────────────────┐
│ langfuse-worker :3030 │
│ async processing │
└──────┬────────┬───────┘
│ │
▼ ▼
┌────────────────────┐ ┌──────────────────┐
│ ClickHouse │ │ PostgreSQL │
│ traces │ │ orgs, projects │
│ observations │ │ datasets │
│ scores │ │ encrypted keys │
└────────────────────┘ └──────────────────┘
The Ingestion Path — The Order an Event Travels
The key to this structure is one sentence in the cache documentation: Redis is used to accept new events quickly on the API and defer their processing and insertion, which is what lets the system handle request peaks gracefully.
So an event the SDK sends does not land in ClickHouse on arrival. Web receives it and enqueues it; the worker dequeues, processes, and inserts. Two consequences follow.
- A small delay before a trace shows up in the UI is normal. The problem is a delay that keeps growing, and what you look at then is worker throughput.
- If the worker is down, the API still returns 200. Data piles up in the queue. Without monitoring on the worker container, it backs up silently.
The object storage documentation gives the reasons for persisting raw events as retry jobs, replay and disaster recovery, and optional record assembly. Processing can fail and the original is still there to retry — at the cost of steadily consuming bucket capacity. The fifth post returns to that.
On the Redis side there is one setting you must get right. The documentation states that maxmemory-policy must be set to noeviction on all Redis or Valkey instances so that queue jobs are not evicted. Leave it at the default and queued events quietly vanish under memory pressure.
Why ClickHouse — Columnar Storage and Trace Queries
The ClickHouse documentation describes ClickHouse as the main OLAP storage solution within Langfuse for Trace, Observation, and Score entities, optimized for high write throughput and fast analytical queries.
Why that pairing works is clearest in ClickHouse's own explanation. The performance documentation gives four reasons.
- Column-oriented storage. Values of the same type and data distribution sit together, which suits compression particularly well.
- Primary key indexes define the sort order of the table data. A well-chosen primary key lets filters be evaluated with fast binary searches instead of full-column scans.
- Vectorized execution. Query plan operators pass intermediate rows in batches rather than one at a time, which improves CPU cache utilization and allows SIMD instructions.
- MergeTree background merges. Additional data transformations happen during the background merge rather than at query time, making user queries significantly faster.
Overlay those four on the storage shape from the first post and the picture completes. Langfuse conceptually keeps one observations table, and each row holds the observation data plus a copy of the trace-level attributes.
To a relational instinct that is waste — user_id copied twenty times across the twenty observations of one trace. In a columnar store the story changes. A column where the same value repeats compresses extremely well, and a query filtering on user_id reads only that one column. No join means no shuffle. The cost of denormalization all but disappears on this engine, and what you get back is a simpler query path.
What ClickHouse Holds — traces, observations, scores
The main tables the documentation names are traces, observations, and scores. The object storage documentation adds a separate blob_storage_file_log table that tracks files uploaded to blob storage.
The documentation is explicit about access patterns too: tracing data is partitioned monthly, and access patterns center on project and time filters. So a query narrowed by project and period gets the full benefit of partition pruning, while a query that leaves the period open and scans everything touches every partition. Set a wide default range on a dashboard and you pay that cost on every load.
User permissions are spelled out precisely as well.
-- Grants Langfuse requires on its ClickHouse user
GRANT INSERT, SELECT, ALTER UPDATE, ALTER DELETE,
ALTER DROP INDEX, CREATE, DROP TABLE
ON langfuse.*
TO langfuse;
Note that ALTER UPDATE and ALTER DELETE are in there. This is not a read-only analytics store; it is a store where retention-driven deletes and updates happen, which connects directly to the retention discussion in the fifth post.
Cluster topology carries a constraint. The documentation states that Langfuse does not currently support multi-shard clusters and that the shard count must be 1, while recommending a minimum of three replicas in production. In other words, you scale by replication and vertically, not by sharding. If you want to separate read load, CLICKHOUSE_READ_ONLY_URL exists; the documentation describes it as an optional read-only endpoint used for UI and public-API queries.
What Postgres and Redis Carry
The Postgres documentation names users, organizations, projects, datasets, encrypted API keys, and settings. Low volume, consistency-critical, transactional. Traces are not here.
Redis takes caching on top of the queue role above. The documentation names API keys and prompts as the cached objects, each with a default time to live of 300 seconds. API keys are never stored in plain text; only hashed or encrypted forms are cached.
Here is the condition that trips self-hosters most often. The self-hosting overview states that all infrastructure components must operate with the UTC timezone, and that non-UTC configurations will cause queries to return incorrect or empty results. The same warning appears in both the Postgres and ClickHouse pages.
Where Large Payloads Go
This is where LLM tracing diverges decisively from ordinary distributed tracing: the data hanging off one span is large. A prompt with a long context, the raw text of twenty retrieved documents, and image inputs all attach to a single observation.
The object storage documentation groups this store's purposes into three: raw events, multi-modal content, and batch exports. On the multi-modal side there is a configurable size ceiling. LANGFUSE_S3_MEDIA_MAX_CONTENT_LENGTH defaults to 1,000,000,000 bytes, that is 1 GB. Download URL validity is set by LANGFUSE_S3_MEDIA_DOWNLOAD_URL_EXPIRY_SECONDS, default 3600 seconds.
Officially supported providers are Amazon S3, Google Cloud Storage, Azure Blob Storage, MinIO/AIStor, and Cloudflare R2, with OCI Object Storage, Tigris, and other S3-compatible services under community support. The minimum permissions are s3:PutObject, s3:ListBucket, and s3:GetObject on both the bucket and its objects, plus s3:DeleteObject if you want data retention.
The Schema Is Not a Stable Contract
Once the data is in ClickHouse a natural thought follows: why not just query it directly? The documentation attaches a clear warning. The ClickHouse schema is not a stable API contract. Major upgrades, background migrations, and performance work can alter tables, columns, deduplication behavior, and join patterns, so custom direct queries have to be re-validated with every Langfuse upgrade.
The judgment splits accordingly. Metrics your application depends on come from the public API; direct ClickHouse queries belong to one-off investigations and internal analysis where a human can fix breakage. The metrics API covered in the sixth post is the front half of that.
If you need the actual column definitions, read the migration files rather than guessing. The ClickHouse documentation points at the repository path ./packages/shared/clickhouse/migrations/ while describing the manual migration procedure. The following shows how to check the schema and is illustrative.
-- Illustrative: confirm the real column definitions with your own eyes
SHOW TABLES FROM langfuse;
SHOW CREATE TABLE langfuse.observations;
-- See how the partitions are actually cut
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;
The second query is safe without guessing at the schema, because system.parts is a system table ClickHouse provides rather than something Langfuse defines. It returns rows and disk usage per partition, so you can see immediately which month is eating the cost.
Where the Versions Diverge
This is the section where version labelling matters most in this series, because the storage-layer requirements differ by major version.
| Component | Langfuse v3 | Langfuse v4 |
|---|---|---|
| ClickHouse | 24.3 or newer | 25.12 or newer, 26.4 recommended |
| PostgreSQL | 12 or newer | Minimum 15, 16 recommended |
| Redis | 7 or newer | 7.2 recommended |
| Valkey | — | 8 or newer officially supported |
The documentation gives the reason the ClickHouse requirement rose: v4 relies on lightweight updates, the JSON type, and full-text search. These values can change, so re-check the docs for the version you are running before you deploy.
Per the versioning documentation, v2 is end of life, v3 is deprecated, and v4 is generally available. Moving from v3 to v4 replaced legacy batch ingestion with OpenTelemetry, removed the legacy read APIs, and introduced the Observations API v2 and the Metrics API v2. Whenever you find a Langfuse architecture article online, check which version it describes first.
Closing — Knowing the Storage Layer Speeds Up Diagnosis
To summarize the split: trace bodies in ClickHouse, organizations and settings in Postgres, queue and cache in Redis, raw events and large payloads in object storage. With that map, where you look during an incident changes.
- Traces show up late in the UI → the worker and the Redis queue
- Login works but traces are empty → the ClickHouse connection or migrations
- Traces render but attachments do not open → object storage permissions or presigned URL expiry
- Queries return empty results → the timezone setting
The check you can run today is pulling per-partition sizes. Without knowing how much each month occupies, there is no basis for setting a retention policy.
Try It Yourself
- DuckDB Data Playground — handling the same data row-wise and column-wise makes the difference in compression and scan cost tangible.
- PostgreSQL Playground — experiment with putting transactional and analytical data in one engine and see what buckles first.
If the design of observability data in ClickHouse itself interests you, Putting Observability Data Into ClickHouse goes deeper on sort keys and TTL design.
Series
References
- Langfuse self-hosting overview: https://langfuse.com/self-hosting
- Langfuse ClickHouse: https://langfuse.com/self-hosting/infrastructure/clickhouse
- Langfuse Postgres: https://langfuse.com/self-hosting/infrastructure/postgres
- Langfuse cache and queue: https://langfuse.com/self-hosting/infrastructure/cache
- Langfuse blob storage: https://langfuse.com/self-hosting/infrastructure/blobstorage
- Langfuse containers: https://langfuse.com/self-hosting/infrastructure/containers
- Why ClickHouse is so fast: https://clickhouse.com/docs/concepts/why-clickhouse-is-so-fast