运行观测:设计
跟踪:决定 146。依据:所有者 2026-11-04“这也是我们的 hub 应该能记录和显示的内容,方便 debug”“抄一抄 langfuse 的设计,看看他都能监控到什么”;所有者同日批准本设计。控制台页面先出截图稿,再写代码。
调研对象是 Langfuse 4.15:官方文档,加上它开源的主表定义 events_full。抄的是设计思路,不搬代码。
1. 要回答的四个问题
管理员不上服务器,在控制台就能回答:
- 这次调用慢在哪?
- 没人用的时候,Hub 在干什么?
- 现在有多忙,还能撑多少人?
- 崩溃之前,它在处理什么?
今天这四个问题都答不了。调用日志只有总耗时,控制台请求和后台任务没有记录,代码里没有读过内存和事件循环,崩溃时正在处理的请求不留痕迹。
2. Langfuse 能监控什么
| 层面 | 记录和显示的内容 |
|---|---|
| 每一步 | 父子层级、开始和结束时间、类型、级别和状态说明、输入输出及其长度、元数据 |
| 用量和花费 | 每一步一张用量表 usage_details(名称到数字,名称可以自定),一张花费表 |
| 谁、在哪 | 用户、会话、环境、发布、版本、标签、上报的服务和 SDK |
| 聚合指标 | 延迟 p50 / p95 / p99、数量、花费、分数,按时间、用户、名称、版本切片 |
| 质量 | 分数、人工标注、模型评审 |
| 怎么看 | 过滤栏(level:ERROR latency:>2)、Pulse 时间条、表格转图表、追踪的树 / 时间线 / 图、会话回放、看板 |
| 告警 | 按聚合指标设阈值、窗口和严重级别,发通知 |
| 治理 | 采样、脱敏、保留期、审计日志 |
| 跨服务 | W3C Trace Context,MCP 用 _meta 字段把客户端和服务端的追踪连起来 |
它的存储设计有四点值得抄:
- 一张宽表:一行一个步骤,追踪级的属性(用户、会话、环境)抄到每一行上,查询不用连表。
- 用量是映射:加一种用量不用改表结构。
- 后台批量写入:记录不拖慢应用。
- 名字当接口:动词开头,不含动态值,看板和告警都按名字引用。
3. 它看不到的
Langfuse 是请求层面的追踪,不是资源监控。它看不到:
- 进程内存、CPU、事件循环;
- 连接池、沙箱这类有限资源的排队;
- 磁盘;
- 进程崩溃;
- 没有埋点的后台任务。
它的文档把基础设施监控交给 Datadog 这类工具,还把数据库查询算作追踪里的噪声。而 Hub 这次的问题恰恰在这些地方。所以 Hub 的做法是:追踪部分抄 Langfuse,资源和崩溃自己补。
4. 对照
| Langfuse | Hub |
|---|---|
| 追踪 trace | 一次 MCP 调用、一个控制台请求、一个同步请求、一轮后台任务、一条命令行命令 |
| 步骤 observation | 认证、读来源、解析文档、跑沙箱函数、重跑检查、写记录 |
| 类型 | 用 span、event、tool(沙箱函数)、retriever(读来源和知识)、evaluator(重跑检查)、guardrail(边界校验);不用 generation、agent、chain、embedding,Hub 不调用模型 |
| 会话 session | MCP 会话,一个终端一个;现有的 session_id |
| 用户 user | 账号 |
| 用量 usage_details | 资源用量,见第 5 节 |
| 花费 cost_details | 不抄,Hub 没有 token 费用 |
| 环境、发布 | 部署的环境类别和发布编号;比较部署前后是快了还是慢了 |
| 级别和状态说明 | 错误码和说明 |
| 输入输出 | 不存,只存长度。参数仍按 B08 的脱敏规则进调用日志 |
| Pulse、过滤栏、时间线 | 抄,放在记录页 |
| 看板 | 只做固定的运行状况页,不做自定义看板 |
| 告警 | 抄阈值和窗口;先显示在控制台,通知渠道等 #126 |
| MCP 追踪 | Hub 已读 traceparent 请求头,加读 _meta |
不抄:提示词管理、模型和 token、数据集与实验、模型评审、标注队列、纠正、公开分享。这些属于模型一侧,Hub 不调用模型;验证结论已经在运行记录里。
5. 记录什么
追踪和步骤
一张宽表,每行一个步骤:
| 字段 | 说明 |
|---|---|
trace_id、span_id、parent_span_id |
W3C 格式。根步骤和调用日志用同一个追踪号 |
name、type |
名字动词开头,不含动态值,例如 read-source、parse-document、run-check、maintain-runs |
started_at、ended_at |
开始和结束 |
level、status_message |
级别和说明 |
origin |
入口:MCP、控制台、同步、后台、命令行 |
user_id、session_id、run_id |
抄到每一行 |
client_name、client_version |
客户端报的名字和版本,只作声明 |
release、environment |
发布和环境 |
usage |
用量映射 |
数据库查询不单独成步骤,只累加到所在步骤的用量里。超过 1 秒的查询记成 event。
用量
| 名称 | 含义 |
|---|---|
db.queries、db.rows、db.bytes、db.ms |
查询次数、读出行数和字节、数据库耗时 |
db.wait_ms |
等连接池的时间 |
sandbox.runs、sandbox.wait_ms、sandbox.ms |
沙箱运行次数、排队时间、运行时间 |
source.reads、source.bytes、source.ms |
读来源的次数、字节、耗时 |
answer.bytes |
回答的大小 |
self.ms |
总耗时减去上面各项 |
self.ms 不写成 CPU 时间。同一线程上交错的异步请求分不准,主线程忙不忙看下面“资源”一节的事件循环指标。db.rows、db.bytes 和 answer.bytes 放在一起,读了多少和答了多少一眼可比。
后台任务
每轮后台任务都是一条追踪,用量记法相同:运行维护、引用索引、知识同步、控制台实时轮询、备份。
资源
每 10 秒采样一次,按每种资源的利用率、排队和错误来记:
| 资源 | 利用率 | 排队 | 错误 |
|---|---|---|---|
| 主线程 | 进程 CPU、事件循环利用率 | 事件循环延迟 p99 | — |
| 内存 | 堆已用 / 上限、常驻内存 | 垃圾回收暂停 | 崩溃 |
| 沙箱 | 运行中 / 上限 | 排队数 | 超时、队列满 |
| 连接池 | 使用中 / 上限 | 等待数 | 连接失败 |
| 数据库 | 活动连接、缓存命中率 | 锁等待 | 死锁 |
| 磁盘 | 数据目录可用空间 | I/O 等待(Linux) | — |
| 连接 | MCP 会话、实时推送流,按客户端名字分 | — | — |
进程事件
启动、正常停止、异常退出,带发布编号和运行时长。
- 在途请求日志:每个请求进来时先写一行到本地文件,结束时再写一行。下次启动时,没有结束行的就是崩溃时正在处理的请求。文件放在 systemd 给服务的运行目录里,也就是内存:它只要活过进程,不用活过机器。实测放在生产的机械盘上,写入中位数 10 微秒,但 ext4 每 5 秒的日志提交会卡住一次写入 10 到 30 毫秒,而这是主线程;放在内存里最长 76 微秒。
- 崩溃报告:打开 Node 的
--report-on-fatalerror,加--report-exclude-env,下次启动时读进来:退出原因、堆状态、资源用量。实测堆耗尽时写出约 18 KB 的 JSON,不含环境变量;此时 JavaScript 调用栈不可用,所以还要靠在途请求日志。 - 不留内存转储:进程内存里有密钥。服务单元同时设
LimitCORE=0和CoredumpFilter=0:实测只设前者没用,apport 照样把内存写进报告;两个都设后只剩约 23 KB 元数据。
6. 怎么写入
- 请求路径上只往内存队列放,不等待。
- 后台每 5 秒或攒够 500 行批量写一次,用单独的连接,不占业务连接池。
- 命令行这类短命进程,退出前把队列写完。
- 队列满了就丢弃并计数。丢了多少显示在运行状况页上,不悄悄丢。
- 观测本身出错,不影响请求。
- 保留 14 天,后台分批删除。这个量级用不上分区。
- 备份不含诊断数据的行,只含表结构:否则 14 份每日备份会让数据活过保留期,备份也跟着访问量变大。
这些是诊断数据,不进只追加的记录链,不验摘要,不作依据,和 B08 R[0] 一致。
7. 怎么显示
记录页(现有页面加功能)
- 时间条:表格上方,一格一个时间段,可切数量、p95、出错数。点一格,表格收窄到这一段。
- 过滤栏:
tool:get_nodes user:张三 latency:>2 level:ERROR,和现有筛选是同一份状态。 - 表格:加一列占比条,显示数据库、沙箱、来源和自身各占多少。
- 追踪详情:点一行看步骤的树和时间线,旁边是用量。控制台已有时间线图表。
- 会话视图:一个终端的全部调用连起来看。
运行状况页(新页面,管理员)
- 主线程、内存、沙箱、连接池、数据库五张卡:当前值和 24 小时曲线。
- 后台任务表:每种任务的间隔、上次耗时、读的行数和字节,按占用排序。
- 崩溃和重启列表:点开看当时在处理什么,以及之前 5 分钟的曲线。
- 最慢的查询:来自
pg_stat_statements,按累计耗时和读出行数排序。 - 按客户端:每种客户端的连接数、调用量和 p95,这是多终端、多平台的视图。
- 按发布:每个发布的 p95,看部署前后的变化。
- 观测自身:写入延迟和丢弃数。
告警
阈值和窗口照 Langfuse 的做法。先显示在控制台顶部和运行状况页。默认几条来自决定 146:
- 堆超过上限的 80%;
- 事件循环延迟 p99 超过 500 毫秒;
- 交互类调用 p95 超过 2 秒;
- 沙箱或连接池持续排队 1 分钟;
- 进程异常退出。
权限
成员在记录页只看自己的调用,包括这些调用的步骤和用量(B08 R[4])。运行状况页只给管理员。
8. 模型改动
- B08 的定义从“每次 MCP 调用”扩到“每次进入 Hub 的请求和每轮后台任务”,加步骤、用量和客户端声明三个字段。
- B08 加一条规则:诊断数据保留 14 天,不进记录链,不作根据。
- 词表加三个轴:步骤类型、级别、入口(
step_type、step_level、trace_origin;入口是埋点任务里加的)。
9. 分步
- 埋点:追踪上下文,数据库、沙箱、来源三处累加用量,队列和宽表。
- 资源和崩溃:10 秒采样、进程事件、在途请求日志、崩溃报告。
- 控制台:记录页和运行状况页,先出截图稿。
- 告警和转发开关。字段和 OpenTelemetry 对齐,转发到外部默认关闭,诊断数据默认不出 Hub。
第 1、2 步上线后调优就能开始,不用等界面:用命令行先读这些数据。
10. 验收
用今天的三件真实事情,在合成数据上重现:
- 空转的定时维护:后台任务表第一行就是它,读的行数和字节都写着。
- 堆耗尽崩溃:崩溃列表能指出当时正在处理的请求。
- 固定 5 秒以上的
resolve_reference:追踪详情说得出时间花在哪一步。
另外,观测本身不能成为负担:请求路径不等待写入,队列满时计数,观测的开销显示在页面上。
11. 已定
所有者 2026-11-04:
- 诊断数据保留 14 天。
- 成员能看自己调用的用量。
- 留一个把追踪转发到外部的开关,默认关闭。