Chapter 02

Langfuse 架构拆解:七个组件与数据流

看懂了架构,后面的 SDK 接入、性能调优、K8s 部署就不再是"照着文档抄命令",而是能有判断地做选择。

一张图看全貌

┌──────────────┐ ┌─────────────────────────────────────┐ │ Your App │ ──────>│ Langfuse SDK (Python/JS/OTel) │ │ (LLM 调用点) │ └──────────────┬──────────────────────┘ └──────────────┘ │ POST /api/public/ingestion ▼ ┌────────────────────────────────────┐ │ Langfuse Web (Next.js) │ ← 用户浏览器 │ - 接 SDK 流量 (API routes) │ │ - 渲染 UI (Traces/Prompts/...) │ └──────┬──────────────┬──────────────┘ │ │ 队列/缓存 │ │ 元数据 ▼ ▼ ┌─────────┐ ┌────────────┐ │ Redis │ │ Postgres │ ← OLTP │ (Queue) │ │ users/orgs │ 配置/权限 └────┬────┘ │ prompts │ prompt 版本 │ └────────────┘ dataset 元 │ ▼ ┌───────────────────┐ │ Langfuse Worker │ │ - 消费队列 │ │ - 写 ClickHouse │ │ - 跑 eval job │ └──────┬────────────┘ │ ▼ ┌─────────────┐ ┌──────────┐ │ ClickHouse │ │ S3/MinIO │ │ (OLAP) │ │ blob │ │ traces/obs │ │ 大 body │ │ scores │ │ media │ └─────────────┘ └──────────┘

七个组件挨个讲

1. Langfuse Web(Next.js App)

这是用户唯一看到的组件——浏览器打开的 UI、以及 SDK POST 数据进来的接收端,都是它。一个进程同时承担"前端 + API Gateway"两个角色。

关键细节
Web 收到 ingestion 请求后 直接写 ClickHouse,而是扔到 Redis 队列交给 Worker。这样 SDK 永远感知不到 ClickHouse 繁忙或重启——对 SDK 来说,只要 Redis 能收,就算成功。

2. Langfuse Worker

后台 worker,独立部署,单独扩容。做两件事:

  1. 消费 ingestion 队列:把 SDK 批量上报的 trace/observation/score 批量写入 ClickHouse
  2. 执行异步作业:跑在线 eval(LLM-as-Judge)、dataset run、batch export

扩容经验:Web 和 Worker 可以不同步扩。写入高峰扩 Worker,UI 查询高峰扩 Web。

3. ClickHouse(OLAP 主存储)

Langfuse 的"账本"。所有 trace / observation / score / event 全部落在 ClickHouse 里。为什么不是 Postgres?

为什么 ClickHouse

  • 观测数据 append-heavy,OLAP 列存压缩率高(比 Postgres 省 10×)
  • 聚合查询(按模型/用户/时间段求 token 和 cost)是日常,ClickHouse 秒级
  • 原生支持 TTL、分区、Projection,留存周期管理方便
  • 千万级写入 + 亿级扫描都轻松

代价

  • 单点/HA 部署比 Postgres 复杂
  • 更新/删除贵(Langfuse 设计是 append-only,没问题)
  • 慢查询排查要懂 EXPLAIN 和索引结构
  • ZooKeeper / Keeper 依赖(Replicated 表需要)

4. Postgres(OLTP 元数据)

关系型数据,不适合放 ClickHouse 的那部分:

日常写入量小,Postgres 14+ 单实例扛得住,HA 上 RDS / CloudSQL / Patroni 任一方案。

5. Redis(队列 + 缓存)

两用途:

生产推荐 Redis 7+ 主从或 Cluster,AOF 开启防丢数据(虽然队列丢了理论上 SDK 会重试)。

6. S3 / MinIO(大 body 对象存储)

LLM 调用的 input / output 可能几十 KB 甚至 MB(想想你喂进去的长 RAG 上下文)。全塞 ClickHouse 会膨胀,Langfuse 把大 body 抽出来扔对象存储,ClickHouse 只存引用:

ClickHouse observations 表:
  id=obs_123
  input_ref=s3://langfuse-blobs/projects/proj-1/obs/obs_123/input
  output_ref=s3://langfuse-blobs/projects/proj-1/obs/obs_123/output
  token_in=1234, token_out=567, model=gpt-4o, ...

S3 object:
  s3://langfuse-blobs/.../input  (原始 prompt, gzipped)

这让 ClickHouse 表保持轻,聚合查询快;代价是详情页要额外访问 S3。内网 MinIO / 云上 S3 / R2 都可以——Langfuse 用 S3 协议,改 endpoint 就能切。

媒体也走 S3
Langfuse 3.0 后支持多模态 trace:图片、音频、PDF 作为 attachment 写进 trace。这些 blob 100% 走 S3,不进 ClickHouse,不进 Postgres。

7. 可选:ZooKeeper / Keeper

只有当你用 ClickHouse Replicated 表做多副本时需要。单机起步阶段不用装,后面第 8 章讲 K8s 生产部署时再展开。

完整的数据流:一次 LLM 调用的生命周期

  1. App 发起 LLM 调用,SDK trace() / generation() 捕获 input/output/model/token
  2. SDK 在后台线程 批量(默认 1 秒或 100 条)POST 到 /api/public/ingestion
  3. Web 接收,快速校验 API Key + project,push 到 Redis ingestion 队列,立即返回 202
  4. Worker 从队列消费,把 trace/observation 写入 ClickHouse;大 body 另存 S3,写引用
  5. 若该 project 配了在线 evaluator,Worker 额外派发 eval job:调裁判 LLM → 产生 score,回写 ClickHouse
  6. 用户打开 UI,Web SSR 从 ClickHouse 聚合、从 Postgres 取元数据,从 S3 取大 body 详情

整条路径里,SDK 永远只接触 Web;Postgres 不在热写路径;ClickHouse 永远在 Worker 后面。扩容与故障边界都被清晰隔离。

读写路径对比

操作热路径组件目标存储延迟
SDK 写 traceWeb → RedisRedis 队列< 20ms
Trace 落库Worker → ClickHouseClickHouse~1-5s(批量)
UI 看 trace 列表Web → ClickHouseClickHouse~100-500ms
UI 看 trace 详情Web → ClickHouse + S3ClickHouse + S3~200-800ms
读 Prompt(SDK)SDK 缓存 → Web → Redis → PostgresPostgres(冷)< 5ms(热)
运行在线 evalWorker → LLM ProviderClickHouse 回写 score秒-十秒

可以省掉哪些组件?

完整七件套不是必须的,按规模可以降级:

别省的东西
S3 / MinIO 在任何"有真实 prompt 的"环境都省不了——不走 S3 就意味着大 body 进 ClickHouse,表膨胀到一定程度,查询慢到不能用。早装比晚装便宜。

和 OpenTelemetry 的关系

Langfuse 从 2024 底开始原生支持 OpenTelemetry OTLP。这意味着:

第 4 章我们会演示三种接入方式并排比较。现在只需要记住:Langfuse 既是自己的 SDK,又是 OTLP 兼容后端

本章小结