只查到一行 200 OK 之后:LLM 可观测性该采的四类数据

举报
yd_238528621 发表于 2026/11/01 12:38:35 2026/11/01
【摘要】 每天 12 万次调用、一天 3000 万条 span,难点不在要不要采,在采到什么粒度还不亏钱。本文按请求层、步骤层、调用层拆 span 结构,实算 span、原文、指标、回流样本四类数据的存储成本,给出异常全留加正常 3% 采样、异步落库、prompt hash 进 span 三个取舍与最小骨架。

引言

先给两个数字:每天 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 没有可测影响。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。