Skip to content

Langfuse 当前 repo 完全导读 ​

基于当前工作区编写:/Users/zhangxian/projects/langfuse。 本教程的目标不是罗列文件,而是教你如何拆解这个 repo:先建立静态地图,再追运行链路,最后抓住契约和抽象。

目录 ​


0. 学习路线:如何读一个大型 repo ​

0.1 本教程的拆解方法 ​

读 Langfuse 这样的 repo,最容易犯的错误是从目录树第一行开始随机点文件。正确方法是把 repo 当成一个正在运行的系统看:

  1. 先问入口:请求、任务、事件从哪里进入系统?
  2. 再看边界:哪个 package 拥有这个入口?它能不能直接访问数据?
  3. 找契约:这个流程依赖哪些 Zod schema、TypeScript type、队列 payload、查询定义?
  4. 追运行链路:同步返回,还是进入 Redis/BullMQ 后由 worker 异步处理?
  5. 落到数据层:最终写 Postgres、ClickHouse、Redis、S3,还是调用外部系统?

这五个问题比“这个文件叫什么”更重要。文件名会变,边界和契约的方向更稳定。

0.2 读 repo 的两只眼睛 ​

视角回答的问题在 Langfuse 里看什么
静态地图代码住在哪,谁能依赖谁pnpm-workspace.yaml、package.json、web/、worker/、packages/shared/
动态链路运行时谁先调用谁tRPC、Public API、ingestion、BullMQ、ClickHouseWriter
契约地图哪些规则不能随便改Zod schema、FilterState、queue payload、domain model、query builder
数据地图为什么有多种存储Postgres、ClickHouse、Redis、S3 的职责分工
修改地图改一个功能会波及哪里web 入口、shared 契约、worker 消费者、Fern/API、测试

0.3 本教程使用的核心源码锚点 ​

主题入口文件
monorepo 包结构pnpm-workspace.yaml、package.json、turbo.json
架构原则.agents/ARCHITECTURE_PRINCIPLES.md
tRPC 根路由web/src/server/api/root.ts
tRPC procedure 和鉴权web/src/server/api/trpc.ts
Public API 包装器web/src/features/public-api/server/createAuthedProjectAPIRoute.ts、withMiddlewares.ts
队列契约packages/shared/src/server/queues.ts
ingestion 入队packages/shared/src/server/ingestion/processEventBatch.ts
worker 注册worker/src/app.ts
ingestion workerworker/src/queues/ingestionQueue.ts
ClickHouse 批量写入worker/src/services/ClickhouseWriter/index.ts
v4 events 表结构packages/shared/clickhouse/scripts/dev-tables.sh
筛选契约packages/shared/src/interfaces/filters.ts、web/src/features/search-bar/README.md
UI token 和颜色web/src/styles/globals.css

1. 项目定位:Langfuse 在解决什么问题 ​

1.1 一句话理解 ​

Langfuse 是一个开源 LLM engineering 平台,用来开发、监控、评估和调试 AI 应用。代码层面看,它不是一个单纯的 Next.js 应用,而是一个围绕高吞吐 telemetry、trace、observation、score、dataset、prompt、eval 的多入口数据平台。

1.2 教学类比:机场运行系统 ​

可以把 Langfuse 想成一个机场运行系统:

机场类比Langfuse 对应物解释
旅客和航班数据不断进入SDK、Public API、OTel ingestion外部应用持续上报 trace、span、score、dataset 结果
值机柜台和安检web/ API 入口鉴权、校验、限流、上下文设置
调度中心packages/shared 契约和服务定义所有入口和 worker 都必须遵守的规则
后台行李系统worker/大批量、可重试、耗时工作离开请求链路
航班历史数据库ClickHouse存高吞吐、宽表、可分析事件
会员和订单系统PostgreSQL存组织、项目、配置、权限、事务型数据

这个类比的关键是:前台入口不能自己发明规则,后台系统不能猜 payload,所有关键规则必须沉到 shared 契约里。

1.3 当前架构原则 ​

项目自己的架构原则强调高规模、探索式 observability 和宽事件模型。最重要的几条可以转成工程判断:

  • observation/event 是主要分析单位,trace 更像相关性句柄。
  • 高基数上下文应该被保留,方便用户后续任意切分和过滤。
  • 列式存储和查询路径要围绕时间窗口、字段选择、排序键、数据裁剪设计。
  • 列表和聚合视图应该读轻量投影,大字段只在详情页或必要场景读取。
  • 公共 API 要规模感知:时间窗口、字段选择、分页和避免全历史扫描。

这解释了为什么 repo 里同时存在 events_full 和 events_core,也解释了为什么查询构建器、FilterState、列映射这些契约非常重要。


2. 静态地图:monorepo 的组织方式 ​

2.1 顶层目录 ​

text
langfuse/
├── web/                     # Next.js app:UI、tRPC、Public REST API、ingestion HTTP 入口
├── worker/                  # Queue consumer 和后台处理
├── packages/shared/         # 共享领域模型、DB、队列契约、仓储、服务
├── ee/                      # 企业版能力,被 web 引用
├── generated/               # 生成的 API client,不手改
├── fern/                    # Public API 定义源
├── scripts/                 # repo 脚本
└── docs/                    # 本地补充文档和架构图

2.2 package 依赖方向 ​

Langfuse 的关键边界是依赖方向:

读图规则:

  • web 可以依赖 shared 和 ee。
  • worker 只能依赖 shared,不能反向依赖 web。
  • ee 可以依赖 shared。
  • shared 不能依赖 web、worker、ee。

这就是 repo 的“重力方向”:越底层越稳定,越上层越接近具体入口。

2.3 monorepo 工具链 ​

文件作用
package.json根脚本、Node 版本、pnpm 版本、Turbo 命令
pnpm-workspace.yamlworkspace 包:web、worker、packages/**、ee
turbo.jsonbuild、dev、lint、typecheck、test 的任务依赖和缓存策略

当前根 package 约束:

  • Node:24
  • pnpm:11.4.0
  • 根命令通过 Turbo 分发到各 package
  • db:generate 不缓存,因为 Prisma generate 写入 node_modules,不能只依赖日志回放

2.4 不同目录的阅读顺序 ​

你想理解先读再读
UI 如何调用后端web/src/server/api/root.ts具体 router 和 React 调用处
SDK/Public API 怎么进来web/src/pages/api/public/**features/public-api/server/** 和 features/public-api/types/**
trace/observation 怎么写入processEventBatch.tsworker/src/queues/ingestionQueue.ts、IngestionService、ClickhouseWriter
后台任务怎么跑worker/src/app.tsworker/src/queues/**、packages/shared/src/server/queues.ts
筛选和表格怎么连到 ClickHousesearch-bar/README.mdfilters.ts、event-query-builder.ts
数据模型怎么定义Prisma schema、ClickHouse migrationsshared domain/repository definitions

3. 五层架构:从入口到数据设施 ​

3.1 架构图 ​

draw.io XML 图已经放在:

architecture/langfuse-current-architecture.drawio

图中分成五层:

  1. 外部入口
  2. web/ 同步产品界面与 API 入口
  3. packages/shared 契约、领域模型、服务、仓储
  4. worker/ 异步执行与后台处理
  5. 数据与基础设施

3.2 五层职责 ​

层主要模块职责
外部入口Browser、SDK、Public API client、OTel、内部触发器发起 UI 查询、REST 请求、telemetry ingestion 或后台任务
web/Next.js UI、tRPC、Public REST API、ingestion API同步请求入口,负责鉴权、校验、限流、上下文
packages/shareddomain、filter、query、queue、repository、service契约中心,定义 web 和 worker 共同遵守的规则
worker/WorkerManager、queue processor、IngestionService、ClickhouseWriter异步处理耗时或可重试任务
数据与基础设施PostgreSQL、ClickHouse、Redis、S3、外部系统持久化、队列、缓存、对象存储、外部副作用

3.3 为什么 shared 是第三层,而不是工具包 ​

packages/shared 不是普通 utils 包。它承载的是系统契约:

  • domain schema:trace、observation、score、event 的运行时校验和 TS 类型
  • queue schema:BullMQ job 的名字、payload、类型映射
  • query contract:字段、列映射、ClickHouse 查询构建
  • filter contract:前端筛选、搜索栏语法、后端 SQL lowering 的共同语言
  • repository:复杂数据读取和转换
  • service:跨 web/worker 复用的后端能力

换句话说,shared 是“系统宪法”,不是“杂物间”。

3.4 颜色和图例怎么读 ​

架构图颜色不是装饰,它对应边界:

颜色含义
灰色外部入口和外部系统
靛蓝web/ 同步入口
琥珀shared 契约中心
绿色worker 异步处理
红色数据与基础设施

实线表示主运行链路。虚线表示条件路径、legacy 路径或双写路径。


4. 运行原理:五条主链路 ​

4.1 链路一:UI 通过 tRPC 读写数据 ​

用户在浏览器中操作 UI,通常走 tRPC。

源码锚点:

  • appRouter 在 web/src/server/api/root.ts 聚合所有 router。
  • createTRPCRouter、authenticatedProcedure、protectedProjectProcedure 在 web/src/server/api/trpc.ts。
  • protectedProjectProcedure 会要求输入包含 projectId,并校验用户是否属于该项目。

读代码时要看三件事:

  1. 这个 procedure 是 query 还是 mutation?
  2. 输入 schema 是什么,是否包含 projectId?
  3. 业务逻辑是否委托给 service/repository,而不是塞在 router 里?

4.2 链路二:Public REST API 供 SDK 和外部系统调用 ​

SDK 或外部系统调用 /api/public/**,通常走 Next.js API route。

源码锚点:

和 tRPC 的区别:

对比项tRPCPublic REST API
使用者Langfuse Web UISDK、外部集成、用户脚本
鉴权NextAuth session + project membershipAPI key Basic/Bearer + access level
类型来源tRPC + Zod 输入Public API types + Zod request/response
对外稳定性内部 API公共契约,需要同步 Fern 和 SDK

4.3 链路三:ingestion 从请求变成异步写入 ​

这是 Langfuse 最核心的数据链路。外部 SDK 上报 trace/observation/score 时,不应该让 HTTP 请求一直等 ClickHouse 合并和写入完成。因此 ingestion 采用“先收下、再异步处理”的模型。

源码锚点:

关键直觉:

  • HTTP 请求只完成“接收、校验、排队”,重活交给 worker。
  • S3 存原始事件正文,Redis 只放小 payload 和指针。
  • queue payload 由 shared 定义,producer 和 consumer 不允许各自猜字段。
  • ClickhouseWriter 是批量写入缓冲层,避免每个事件单独打 ClickHouse。

4.4 链路四:worker 不是一个任务,而是一组可开关消费者 ​

worker/src/app.ts 是 worker 侧的总装配入口。它不是只启动一个 consumer,而是根据 env 开关注册很多队列:

  • trace upsert/delete
  • ingestion / secondary ingestion
  • OTel ingestion / secondary OTel ingestion
  • eval execution / LLM-as-judge / code eval
  • batch export
  • batch action
  • project/dataset/score delete
  • monitor、notification、webhook、integration 等

读 worker 代码时,先从 WorkerManager.register(queueName, processor, options) 看:

  1. 这个 queue 是否有 env 开关?
  2. concurrency、limiter、lockDuration、maxStalledCount 怎么设?
  3. processor 的输入 payload 是否来自 packages/shared/src/server/queues.ts?
  4. processor 成功和失败分别代表什么?失败是否应该重试?

4.5 链路五:v4 events 查询从 FilterState 变成 ClickHouse SQL ​

v4 events 表把 observation/trace 相关属性 denormalize 到宽事件中。前端筛选、搜索栏、表格列和后端查询需要共享同一套语言。

源码锚点:

这条链路的关键不是 UI,而是契约一致性:搜索栏只是 FilterState 的编辑器,不能绕过 filter contract 自己发明一种后端不懂的查询语言。


5. 契约中心:为什么 shared 是关键 ​

5.1 契约的类型 ​

Langfuse 里常见的契约不是单一接口,而是一组组合规则:

契约文件例子解决的问题
domain schemapackages/shared/src/domain/**、repositories/definitions.ts运行时数据长什么样
queue payloadpackages/shared/src/server/queues.tsweb 入队和 worker 消费必须一致
filter statepackages/shared/src/interfaces/filters.tsUI 筛选和后端查询共享语言
query builderpackages/shared/src/server/queries/**避免散落手写 SQL
repositorypackages/shared/src/server/repositories/**复杂数据读取、转换、tenant filter
public API typeweb/src/features/public-api/types/**外部 API 的请求/响应稳定性
UI table contractweb/src/components/table/**列定义、排序、可见性、分页、行选择

5.2 Zod + TypeScript 的模式 ​

很多数据结构会同时具备:

  1. Zod schema,负责运行时校验。
  2. z.infer<> type,负责 TypeScript 静态类型。
  3. 转换函数,负责 DB record 和 domain model 之间的映射。

例子:

  • observationRecordReadSchema
  • traceRecordInsertSchema
  • scoreRecordInsertSchema
  • eventRecordBaseSchema
  • eventRecordInsertSchema

这种模式的价值是:数据从外部进入系统时能被校验,进入代码后又有 TS 类型跟踪。

5.3 queue contract 是 web 和 worker 的共同边界 ​

packages/shared/src/server/queues.ts 里有三个层次:

  1. payload schema,例如 IngestionEvent、OtelIngestionEvent、EvalExecutionEvent。
  2. queue name,例如 QueueName.IngestionQueue。
  3. job name 和类型映射,例如 QueueJobs.IngestionJob、TQueueJobTypes。

为什么这么设计?

  • producer 可以类型安全地 add job。
  • consumer 可以类型安全地读 job.data。
  • rolling deploy 时可以通过 optional 字段兼容旧 job。
  • 队列名和 payload 不会在 web/worker 两边漂移。

如果你要新增 worker job,先改 queues.ts,再写 processor,最后注册到 worker/src/app.ts。

5.4 FilterState 是筛选系统的中心 ​

FilterState 是扁平数组,元素是 discriminated union:

  • datetime
  • string
  • number
  • stringOptions
  • categoryOptions
  • arrayOptions
  • stringObject
  • numberObject
  • boolean
  • null
  • positionInTrace

搜索栏 grammar 不能表达的形状,要么保留为 skipped filter,要么报 commit-blocking diagnostic。不能静默丢弃。

这体现了一个大型 UI 契约的原则:新的交互方式要编辑已有事实源,而不是另造一份状态。

5.5 查询构建器的职责 ​

event-query-builder.ts 做了几件关键事:

  • 统一字段映射:例如 traceId 映射到 e.trace_id as "trace_id"。
  • 定义 field set:列表查询、详情查询、metadata、tools、I/O 等按需选择。
  • 自动加 project_id filter:避免租户数据串读。
  • 对 events 表排序做优化:按 project_id、toStartOfMinute(start_time) 等贴合主键。
  • 对 trace_id equality 加 xxHash32(trace_id) 优化。
  • 默认读 events_core,需要完整 I/O 时再读 events_full。

读查询代码时,不要只看 SQL 字符串,要看 builder 在保护什么约束。


6. 数据层:Postgres、ClickHouse、Redis、S3 怎么分工 ​

6.1 总览 ​

存储用途典型数据
PostgreSQL + Prisma事务型、关系型、配置型数据org、project、user、API key、dataset、prompt、eval config
ClickHouse高吞吐、宽表、分析查询traces、observations、scores、events_full、events_core
Redis + BullMQ队列、限流、缓存、锁ingestion job、eval job、rate limit、recently processed cache
S3 / Blob storage大对象和原始事件正文raw ingestion event、media、export 文件
外部系统副作用和集成LLM providers、webhook targets、Slack、Stripe、PostHog

6.2 为什么不能只用一个数据库 ​

PostgreSQL 擅长事务和关系。ClickHouse 擅长大规模分析和列式扫描。Langfuse 同时需要:

  • 管组织、项目、权限、配置、API key。
  • 高速写入大量 observation/event。
  • 按时间、metadata、score、model、usage/cost 做探索式查询。
  • 在列表页尽快返回轻量数据,在详情页再取完整 payload。

一个数据库很难同时把这些场景都做好,所以 repo 里清晰区分:

  • 配置和事务:Postgres。
  • 遥测分析:ClickHouse。
  • 异步任务和限流:Redis。
  • 大 payload 和原始日志:S3。

6.3 ClickHouse legacy 表和 v4 events ​

当前代码里同时存在 legacy 表和 v4 events 路径:

表角色
traces、observations、scoreslegacy ClickHouse 表
observations_batch_staging批量 staging,带 TTL
events_fullv4 全量保真事件表,包含完整 input/output/metadata
events_corev4 轻量查询投影,面向列表和常用查询

events_full 的设计重点:

  • 宽事件,一行携带 trace、span、model、usage、cost、metadata、tool、experiment 等上下文。
  • 使用 ReplacingMergeTree(event_ts, is_deleted)。
  • 主键和排序围绕 project_id、toStartOfMinute(start_time)、xxHash32(trace_id)。
  • 对 input/output/metadata 建索引和压缩。

events_core 的设计重点:

  • 从 events_full 生成轻量投影。
  • 列表和常用查询默认读它,避免扫描大字段。
  • 详情或完整导出才需要读 full fidelity 数据。

6.4 ClickhouseWriter 的缓冲模型 ​

ClickhouseWriter 是 worker 内部的单例写入器:

它处理的不只是“写入”:

  • 批量。
  • 定时 flush。
  • 网络重试。
  • 字符串长度错误时拆 batch。
  • 记录过大时截断。
  • Decimal64 overflow clamp。
  • 指标和日志。

所以 ingestion processor 不应该自己直接 insert ClickHouse,而是把 record 交给 writer。

6.5 数据隔离的硬规则 ​

几乎所有项目级数据访问都必须带 projectId / project_id。

  • Prisma 查询要按 projectId 过滤。
  • ClickHouse 查询要按 project_id 过滤。
  • query builder 会自动加 project filter,但手写查询时必须主动检查。
  • Public API auth scope 中必须拿到 projectId。
  • tRPC protectedProjectProcedure 会把 projectId 和 session membership 绑定起来。

这是 multi-tenant 系统的底线。


7. 前端与颜色系统:UI 是怎样组织的 ​

7.1 前端技术栈 ​

web/ 是 Next.js 应用,主要栈包括:

  • Next.js
  • React
  • tRPC
  • TanStack Query
  • TanStack Table
  • Tailwind CSS v4
  • Radix UI
  • Zustand
  • Recharts
  • Storybook / Vitest / Playwright

但理解前端时不要从依赖列表开始,应该从页面工作流和 feature 目录开始。

7.2 feature 组织方式 ​

web/src/features/** 下放很多业务模块,例如:

  • datasets
  • prompts
  • evals
  • public-api
  • search-bar
  • tracing-tables
  • score-analytics
  • blobstorage-integration
  • slack
  • telemetry
  • mcp

典型 feature 会包含:

text
feature/
├── components/        # React 组件
├── server/            # router、service、server action
├── types/             # 类型和 schema
├── hooks/             # 客户端 hook
└── README.md          # 复杂 feature 的本地契约说明

不是所有 feature 都完全一致,但大方向是“业务相关代码尽量聚在一起”。

7.3 表格抽象 ​

Langfuse 是 observability 产品,表格是核心 UI。DataTable 封装了很多通用能力:

  • column visibility
  • column order
  • column sizing
  • sorting
  • pagination
  • row selection
  • pinned columns
  • row height
  • peek view
  • loading/error/empty state

源码锚点:

读表格代码时,要区分:

  • 表格容器负责数据请求和 URL/filter 状态。
  • column definition 负责列展示和排序语义。
  • DataTable 负责通用交互和布局。

7.4 颜色系统 ​

颜色 token 定义在 web/src/styles/globals.css。

整体特点:

  • 默认浅色:白色背景、中性文字、浅灰边框。
  • 深色:深蓝灰背景、中等亮度前景色。
  • 主强调色:靛蓝/紫蓝系,例如 --primary-accent。
  • 状态色:红、黄、绿、蓝都有浅色和深色 token。
  • 图表色:多色序列,不是单一品牌色渐变。
  • Search bar 语法高亮有独立 token:field、value、number、keyword。
  • 圆角基础值是 0.5rem,偏实用工具界面。

可以把 UI 颜色理解成三层:

这和产品定位一致:Langfuse 是高密度分析工具,不是营销页。颜色服务于扫描、对比、状态识别和数据可视化。

7.5 Search Bar 是一个契约型 UI ​

Search Bar 的设计特别值得学,因为它展示了大型前端功能怎样不破坏已有系统:

  • 它不是新的筛选状态源。
  • URL filter state 仍然是事实源。
  • bar 只是受控编辑器。
  • facet sidebar 也是同一事实源的另一个编辑器。
  • commit 前必须 validate,并 lower 到 FilterState。
  • 不能表达的筛选不能静默丢弃。

这类 UI 的核心不是 contenteditable,而是“状态所有权”。


8. 开发路线:新增功能时该从哪里下手 ​

8.1 新增一个 UI 内部功能 ​

适合场景:只给 Langfuse Web UI 使用,不作为 Public API 暴露。

路线:

  1. 在对应 web/src/features/[feature] 或 web/src/server/api/routers 找现有 router。
  2. 定义 Zod input schema。
  3. 使用 protectedProjectProcedure 或其他合适 procedure。
  4. 把业务逻辑放到 service/repository,不要写满 router。
  5. 需要复杂 ClickHouse 读写时优先放 shared repository。
  6. 前端通过 api.[router].[procedure].useQuery/useMutation 调用。
  7. 写 targeted web test。

自检:

  • 输入里是否有 projectId?
  • 是否校验 project membership?
  • 是否复用 shared 契约?
  • 是否把 business logic 塞进 route 了?

8.2 新增 Public API endpoint ​

适合场景:SDK、外部用户、自动化脚本需要稳定调用。

路线:

  1. 在 web/src/pages/api/public/** 建 route。
  2. 使用 withMiddlewares 包住 HTTP method。
  3. 使用 createAuthedProjectAPIRoute 做鉴权、限流、Zod 校验。
  4. request/response schema 放到 web/src/features/public-api/types/**。
  5. 响应也要被 Zod 校验,避免返回未声明字段。
  6. 更新 Fern sources。
  7. 更新 generated client 或相关 SDK 类型。
  8. 写 server API 测试。

自检:

  • 这是不是公共契约?如果是,不能只改 web route。
  • response schema 是否 strict?
  • 错误是否使用 shared error 类型?
  • Fern/API docs 是否同步?

8.3 新增 worker 队列任务 ​

适合场景:耗时、可重试、批量、调用外部系统或不应阻塞 HTTP 请求的工作。

路线:

  1. 在 packages/shared/src/server/queues.ts 定义 payload schema。
  2. 添加 QueueName、QueueJobs、TQueueJobTypes 映射。
  3. 在合适 producer 里 add job。
  4. 在 worker/src/queues/** 写 processor。
  5. 在 worker/src/app.ts 用 WorkerManager.register 注册。
  6. 设置 concurrency、limiter、lockDuration、maxStalledCount。
  7. 写 worker vitest。

自检:

  • payload 是否可以 rolling deploy 兼容?
  • processor 失败是否真的应该 retry?
  • 是否需要 dead letter 或 recorded error?
  • 是否有 tenant/project filter?

8.4 修改 ingestion 或 v4 event 字段 ​

这是高风险路径,因为会影响 SDK ingestion、worker、ClickHouse、UI 查询和可能的 API。

路线:

  1. 先看 packages/shared/src/server/ingestion/types 和 domain/repository definitions。
  2. 修改 event record schema。
  3. 修改 ClickHouse migration 或 dev table。
  4. 修改 IngestionService 转换逻辑。
  5. 修改 ClickhouseWriter TableName 或 record type,如果新增表。
  6. 修改 query builder field mapping。
  7. 修改 UI column/filter/search registry。
  8. 增加 ingestion worker 测试和查询测试。

自检:

  • 字段是 full fidelity 还是 core projection 也需要?
  • 是否应该 denormalize?
  • 是否会扫描大字段?
  • 是否需要 migration/backfill?
  • legacy 和 v4 双写模式下行为是否一致?

8.5 修改 Search Bar 或筛选语法 ​

必须先读 web/src/features/search-bar/README.md。

路线:

  1. 明确 FilterState 是否能表达新语义。
  2. 如果不能表达,不要只改 parser。
  3. 同步更新 field registry、validate、adapter、reverse adapter、commit gate。
  4. 保持 validate 和 lower parity。
  5. 增加 property/invariant 测试。
  6. 检查 URL round-trip。

自检:

  • 是否会静默丢 filter?
  • sidebar 和 search bar 是否仍读写同一事实源?
  • negation 是否能 lower 到现有 inverse operator?
  • free text search 的 scope 是否仍一致?

9. 读代码练习:从任务倒推源码 ​

9.1 练习一:追踪一次 UI trace 列表查询 ​

目标:弄清楚 trace list 如何从 UI 变成 ClickHouse 查询。

步骤:

  1. 从 trace 页面或 tracing table 组件开始找 api.traces 或 events table 调用。
  2. 找到 tRPC router:web/src/server/api/root.ts 里 traces: traceRouter。
  3. 找到具体 procedure。
  4. 看 input schema 是否包含 projectId、filter、orderBy、pagination。
  5. 找到 shared repository 或 query builder。
  6. 确认 ClickHouse 查询是否带 project_id 和时间范围。

你要能回答:

  • 这个列表读 legacy 表还是 v4 events?
  • 哪些字段是列表页需要的?
  • input/output 是否被延迟读取?

9.2 练习二:追踪一次 SDK ingestion ​

目标:从 HTTP 请求追到 ClickHouse 写入。

步骤:

  1. 找 ingestion API route。
  2. 找它如何调用 processEventBatch。
  3. 在 processEventBatch 中看校验、分组、S3 上传和入队。
  4. 在 queues.ts 中看 payload 契约。
  5. 在 worker/src/app.ts 中看 ingestion queue 是否注册。
  6. 在 worker/src/queues/ingestionQueue.ts 中看 S3 读取、secondary queue、mergeAndWrite。
  7. 在 IngestionService 中看事件如何转为 record。
  8. 在 ClickhouseWriter 中看批量写入。

你要能回答:

  • 为什么 Redis job 不直接塞完整 event body?
  • S3 SlowDown 时为什么有 secondary queue?
  • 为什么 worker 要从 event body 取 canonical entity id?

9.3 练习三:新增一个 Public API 字段 ​

目标:理解公共契约的修改面。

步骤:

  1. 找对应 web/src/pages/api/public/** route。
  2. 找 response schema。
  3. 找 service/repository。
  4. 找 Fern API 定义。
  5. 找 generated client 或 SDK 类型影响。
  6. 写 API 测试校验 response schema。

你要能回答:

  • 这个字段来自 Postgres 还是 ClickHouse?
  • 是否会触发大字段读取?
  • 旧客户端看到这个字段是否安全?

9.4 练习四:判断一个变化应该放哪层 ​

变化应该先看
新增一个项目设置项Postgres schema、settings UI、tRPC router
新增一种后台清理任务queues.ts、worker/src/app.ts、worker processor
新增 observation 过滤字段FilterState、field registry、query builder、ClickHouse schema
新增表格列table column definition、query field set、repository
新增 SDK endpointPublic API route、types、Fern、SDK
新增 ingestion event 字段ingestion types、IngestionService、ClickHouse schema、query builder

10. 术语表与常见误区 ​

10.1 术语表 ​

术语在本 repo 中的意思
webNext.js app,包含 UI、tRPC、Public API、ingestion HTTP 入口
workerBullMQ consumer 和后台任务进程
sharedweb/worker/ee 共享的契约、领域、仓储、服务
tRPCWeb UI 内部类型安全 API
Public API面向 SDK 和外部用户的 REST API
protectedProjectProceduretRPC 的项目级 session/RBAC 中间件
createAuthedProjectAPIRoutePublic API 的 API key 鉴权和 Zod 校验包装器
FilterState表格筛选的统一状态契约
events_fullv4 full fidelity ClickHouse event 表
events_corev4 query-optimized event 投影
ClickhouseWriterworker 内部批量写 ClickHouse 的单例
WorkerManagerworker 队列注册和管理入口
FernPublic API 定义和生成物来源

10.2 常见误区 ​

误区一:看到 Next.js 就以为所有后端逻辑都在 web。 实际:web 是入口层。很多稳定业务契约和数据访问在 shared,耗时工作在 worker。

误区二:把 shared 当 utils 包。 实际:shared 是契约中心。随便改 shared 会影响 web、worker、ee。

误区三:Public API 和 tRPC 可以复用同一套随意返回对象。 实际:Public API 是外部契约,需要 Zod response schema、Fern 和 SDK 同步。

误区四:ingestion 是同步写 ClickHouse。 实际:请求侧上传 S3 并入队,worker 异步读取、合并、批量写。

误区五:Search Bar 有自己的筛选状态。 实际:URL FilterState 是事实源,Search Bar 和 sidebar 都是编辑器。

误区六:ClickHouse 查询只要能跑就行。 实际:必须考虑 project_id、时间窗口、排序键、字段选择、大字段延迟读取、query builder 复用。

10.3 最小上手命令 ​

bash
pnpm install
pnpm run infra:dev:up
pnpm run db:generate
pnpm run dev:web
pnpm run dev:worker

常用验证:

bash
pnpm run lint
pnpm run typecheck
pnpm --filter web run test
pnpm --filter worker run test
pnpm --filter @langfuse/shared run test

种子数据:

bash
pnpm run seed -- list

涉及 UI 的改动,优先使用 seed CLI 生成场景,然后在浏览器里检查真实页面。


最后总结 ​

读 Langfuse 的核心路线可以压缩成一句话:

外部请求进入 web,稳定规则沉到 shared,耗时任务交给 worker,事务数据进 Postgres,分析事件进 ClickHouse,大 payload 放 S3,队列和限流交给 Redis。

进一步拆开,就是四个判断:

  1. 这是同步产品/API 请求,还是异步后台任务?
  2. 这是内部 UI 契约,还是外部 Public API 契约?
  3. 这个数据是事务配置,还是高吞吐分析事件?
  4. 这个规则应该属于入口层,还是应该沉到 shared 供 web/worker 共同使用?

只要这四个判断清楚,修改 Langfuse 的大多数路径都能落到正确位置。