只查到一行 200 OK 之后:LLM 可观测性该采的四类数据
引言
先给两个数字:每天 12 万次模型调用,如果把每次请求内部的每个步骤都落成一条 span,一天是 3000 万条。可观测性真正的难点不是"要不要采",而是"采到什么粒度还不亏钱"。
让我们重做这套东西的,是一张查不出线索的排查单。客服系统答错了一条退款政策,值班同学翻日志只找到一行 200 OK,请求 ID 在、耗时 1.8 秒,但那次调用用的是哪个 prompt 版本、检索回了哪几段、模型输出被后处理改了什么,一个都查不到。最后靠 git log 和发布记录对时间,才确认是前一天下午动过的 prompt —— 那一版 diff 只有三行。
一、把一次请求拆成三层 span
我们最后定下来的结构是三层,用同一个 trace_id 串起来,跨进程靠 header 透传。
- 请求层:一次用户请求一条,字段是渠道、用户标识、会话 ID、最终状态。这一层回答"是谁出的事"。
- 步骤层:检索、prompt 组装、模型调用、后处理,各占一条。这一层回答"哪一步出的事"。
- 调用层:单次模型 API 调用的 token 数、首 token 延迟、总耗时、finish_reason、重试次数。这一层回答"是不是模型本身的问题"。
这三层缺任何一层,排查就会退化成对着时间戳猜。我们最初只有调用层,因为 SDK 自带,接上就有。加上步骤层之后,一类问题才第一次变得可见:检索返回空、模型老老实实凭先验知识编了一段 —— 这在调用层看完全正常,状态码 200、耗时很短、token 也少。
二、四类数据,各自的账单
span:压缩后 8KB 一条
单条 span 打成 JSON 再压缩,含截断后的输入输出,大约 8KB。3000 万条就是一天 240GB。我们最终全量保留请求层和步骤层,调用层只往 trace 摘要里写关键字段,月存储落在 380GB —— 这是能接受的数字,前提是调用层不单独成表。
原文:最贵也最危险的一类
输入输出原文全量留 30 天要 4.6TB,而且里面几乎一定混着手机号、订单号和地址。做法是分层:错误请求(错误码非 0)、被后处理改写超过 30% 的请求、用户主动点踩的请求,100% 留;正常请求按 3% 采样。脱敏在写入前做,正则命中就掩码,不留原始副本。
原文全留和全不留之间没有中间态,只有按异常概率分层这一条路。
指标:用来发现,不用来定位
QPS、P95 延迟、token 消耗、重试率、finish_reason=length 的占比,按模型和 prompt 版本两个维度聚合。指标的价值是告诉你"今天不对劲",剩下的事还得靠 span 和原文。
回流样本:量最小,价值最高
被点踩、被人工改写、被转人工的请求抽出来进评测集。我们一个月沉淀 800 条左右,比任何公开榜单都贴近自己的业务,也比自己造题省事。
写入侧的几个实测数字
链路跑通之前我们压测过一轮,结论比选型文档更有用。峰值每秒 350 条 span 时,单进程批写 ClickHouse 的 P99 写入耗时 26 毫秒,够用;上到每秒 1200 条才开始出现写队列堆积,堆到 8 万条时内存涨了 900MB,这就是必须限流的地方。查询侧按 trace_id 精确查一次不到 40 毫秒,按 prompt hash 加时间范围扫一天的数据要 1.6 秒,扫一周就直接放弃、改看聚合指标。所以看板上所有"按周对比"的图,都指向指标表,不指向明细表 —— 明细表只服务于单次排查。
还有一条容易忽略的:不要把应用日志再抄一份进 trace。 我们一开始把每一步的 log 文本塞进 span 的 events 字段,结果 span 平均体积从 8KB 涨到 31KB,存储费用翻了近四倍,而排查时真正看的是其中两三条。现在的约定是日志留在日志系统,span 只存结构化字段和异常摘要,两边靠 trace_id 关联。
三、三个绕不过去的取舍
- 采样率不能一刀切。 一刀 3% 最难受的场景是:出问题时恰好没采到,比不采更让人恼火。异常请求必须 100%,正常请求才谈采样。
- 落库必须异步。 最初同步写 ClickHouse,P95 延迟涨了 40 毫秒,业务方直接投诉。改成写本地 ring buffer,2 秒或满 500 条刷一次,代价是进程被 kill 时可能丢最后一批,这个可以接受。
- prompt 版本必须进 span。 这是最容易漏的一个字段,因为 prompt 常常从文件读、热更新、不发版。我们的做法是把 prompt 内容算 hash 写进 span,hash 变了就是一次变更,回滚和对比都有了锚点。凌晨那两小时,如果 span 里有这个 hash,五分钟就能定位。
四、最小可用骨架
- 一次请求一个 trace,父子关系只用 parent_span_id 表达。单条 span 字段不超过 20 个,超了通常说明你想在一个 span 里装两件事。
- 表按天分区,热数据留 30 天,30 天前的聚合后归档,只留指标和异常样本。
- 报警只挂三类:错误率、P95 延迟、finish_reason=length 占比。其余进看板,不进报警,否则没人看。
- 每个字段都要能回答一句话:出事时它能不能帮我缩小一半范围。答不上来的,删掉。
小结
可观测性没有"做完"这个状态,只有"下次出事少查一小时"。我们现在的判据很朴素:任何一次线上问题复盘,如果发现当时没有任何一处数据能帮着排除一半可能,那这次问题就应当变成一行新增的埋点,而不是一段新的经验总结。
那张排查单最后落成了一行代码:prompt 内容 hash 写进 span。代价是每次请求多算一次 sha256,实测对 P99 没有可测影响。
- 点赞
- 收藏
- 关注作者
评论(0)