你的 APM 正在悄悄丢弃 LLM 遥测数据,而 Bug 就隐藏在这些缝隙中
目前你的系统中有一个损坏的 prompt 影响了约 3% 的流量,但你的仪表盘根本察觉不到它的存在。p99 延迟图表是绿色的。错误率保持平稳。模型调用成功率指标高达四个九。唯一的故障迹象出现在一张平台团队无法复现的客户支持工单中,而等这张工单进入调试环节时,相关的 trace 已经因为采样而被丢弃了。
这不是监控缺失,而是一个分类错误。你正在运行的 APM 是为维度受限(如 endpoint、status_code、region、service)的世界设计的,在这种情况下,增加一个标签的成本最多只是增加几个新的时间序列。LLM 工作负载完全不符合这种模式。真正有趣的维度是用户的 prompt、检索到的 context ID、工具调用序列、模型版本、prompt 模板版本、租户(tenant)、语言区域(locale),以及请求所属的 eval bucket。每一个维度都是高基数(high-cardinality)的,只要你用其中任何一个子集来标记 span,指标存储瞬间就会爆炸。
团队的反应每次都如出一辙:聚合掉那些有问题的字段。为了 防止成本曲线翻倍,放弃按租户细分的视图。从指标中剥离 prompt_hash,以阻止时间序列数量持续攀升。每一个决定在局部看都是理性的,但在全局看却是灾难性的,因为刚刚被移除的正是唯一能让故障可见的维度。聚合后的 p99 对一个群体取了平均值,在这个群体中,简单的查询总是通过,而困难的查询总是失败;损坏的 prompt 正隐身在“总是失败”的尾部,而这个尾部恰恰被平滑处理掉了。
为什么 LLM 遥测具有产品分析类的基数特征
典型的服务发出的遥测数据看起来像是一个低秩矩阵(low-rank matrix)。通常只有几百个端点、少数几个状态码、几个区域以及同时运行的少数几个服务版本。将它们相乘,你会得到数万个唯一的标签组合。Prometheus、Datadog 以及其他的指标栈正是为此设计的,且表现极其出色。只要每个维度的唯一值保持在大约一百个以内,单位基数的成本曲线是平缓的。
LLM 遥测则完全不同。每个 prompt 都是唯一的字符串。检索层为几乎每个请求返回不同的文档 ID 组合。工具调用的 trace 是一棵树,其形状取决于模型的决策。Prompt 模板本周在 eval 套件中已经更新到了第 14 个版本。模型本身有三个版本同时在线,因为金丝雀、对照和影子评估(canary, control, and shadow eval)正在同步运行。租户集包含数千个客户。语言区域集有几十个。将所有这些相乘,你得到的不是数万个序列,而是一个实际上无界的结果 — — 它更接近点击流分析(clickstream analytics),而非服务指标。
一个典型的检索增强生成(RAG)流水线 —— 向量查询、重排序(rerank)、模型调用、后处理 —— 发出的遥测数据量是同等无状态 API 调用的 10 到 50 倍,且遥测数据以高基数维度为主。这些基数没有一个是“多余的”。每个字段都是工程师调试特定类别故障时必须切分的维度。检索团队需要知道哪些文档 ID 出现在 context 中。Prompt 团队需要知道哪个模板版本处于激活状态。模型团队需要知道是哪个版本给出的回答。产品团队需要知道是哪个租户在投诉。剥离其中任何一个都不是优化,而是在有计划地破坏某人所需的信号。
指标层的悄然崩塌
这种失败在实践中很少表现为宕机。它是一种缓慢的漂移,仪表盘保持绿色,而操作人员虽然嘴上不说,但内心逐渐不再信任它们。通常会依次发生三件事。
首先,指标存储预算超支,告警层开始丢弃序列。在大多数平台上,这是静默发生的。Datadog 的自定义指标超额费用随基数线性增长,增加一个具有 1000 个唯一值的标签可能会使指标数量翻万倍,账单每月每项指标增加数千美元。一个简单的 prompt.template_id 标签(包含 200 个模板)加上一个 tenant_id 标签(包含 500 个租户)就会为一个指标创建 10 万个序列。财务团队往往比运维团队更早察觉到账单异常,而非数据丢失。
其次,团队通过聚合来应对。高基数维度从标签变成了丢弃规则,从丢弃规则变成了“我们会把它们留在日志里”,从日志 变成了“我们会把它们留在采样率为 1% 的 trace 里”。每一步都保留了全局指标,却摧毁了细分指标。仪表盘上的数据形状保持不变,但它能回答的问题却随着每一次迭代而变得越来越窄。
最后,长尾变得不可见。当一个模板针对某个特定租户在特定语言区域下损坏时,全局质量指标不会波动,因为该故障仅占总请求量的一小部分。故障只有在客户投诉升级时才会浮出水面,而投诉转交到的团队,其工具链已被静默配置为不显示刚才发生的情况。平台团队开始说“我们无法复现”,因为他们确实无法复现 —— 采样层在他们介入之前就已经把 trace 丢弃了。
为什么尾部采样是必要但不够的
人的第一直觉是转向尾部采样(tail-based sampling)。OpenTelemetry collector 的尾部采样处理器允许你等待追踪(trace)完成后,再根据发生的情况——如延迟过高、错误、特定租户、特定的工具调用模式——来决定是否保留它。这确实是正确的架构形式。首部采样(Head-based sampling)在追踪开始时就做决定,那时团队还不知道该追踪是否有意义;而尾部采样在事后做决定,让系统偏向于那些至关重要的长尾情况。
但尾部采样也有其自身的基数成本。Collector 必须在做出采样决策之前将整个追踪保存在内存中,这意味着如果每秒有一千个追踪,决策窗口为 60 秒,Collector 就需要同时缓存 6 万个追踪,每个追踪包含多个 span 和许多属性。内存压力随每个 span 的属性数量而增加,而 LLM 追踪的每 个 span 属性非常多,因为 GenAI 语义约定会将 prompt 文本、响应文本、工具调用结构和完整的 token 级元数据都推送到 span 上。结果是,原本旨在为你节省基数开销的采样层本身也有基数预算——一旦溢出,就意味着追踪在被采样之前就被丢弃了,这是两头落空的最坏情况。
正确的做法是分两层进行采样决策,而不是一层。首部采样器在请求进入时运行,无条件保留一个精简的基准线——例如,千分之一的追踪——这样你就拥有了用于核心指标的均匀样本。尾部采样器在 collector 处运行,根据重要性信号筛选其余部分:错误、长尾延迟、特定租户、评估桶(eval-bucket)失败、异常命中。这两股流进入具有不同保留策略的不同存储。基准流驱动仪表板;重要性流驱动调试。
真正能在生产环境中生存的分层架构
能够扩展的模式——也是大多数团队在经历一两次事故后趋向一致的模式——分为三层,每一层都独立分配基数预算。
指标层(Metrics tier)仅保存有限基数(bounded-cardinality)的维度:模型类别、prompt 模板版本(汇总到数百个,而非数百万个)、租户层级(免费、付费、企业)、地域、请求状态。总基数保持在 10 万个序列以内。这是仪表板绘图和告警触发的基础。由于设计使然,它不会显现单个租户的单个损坏 prompt。这不是 bug——正是这一点保证了账单受控且图表可查询。
追踪层(Trace tier)为采样后的子集保存完整保真的宽事件,采样偏向于重要性。每个 span 都携带完整的高基数有效负载——prompt 模板 ID、检索到的文档 ID、工具调用序列、评估桶、租户——而追踪存储是一个原生处理高基数连接的列式引擎(ClickHouse 类)。在这种数据上实现 15 到 50 倍的压缩在列式存储中是很正常的;同样的数据在传统的指标存储中,只要采样率超过百分之几,成本就无法承受。这是调试环节查询的对象。指标层负责告警;追踪层负责复盘(post-mortem)。
事件层(Events tier)——有时合并到追踪中,有时独立出来——是连接两者的按 prompt 版本汇总的层。它的存在是因为“当我们推出 v37 模板时,情况有变化吗?”这个问题是指标层无法回答的(模板 ID 不是指标标签),也是追踪层无法廉价回答的(你必须扫描两个时间窗口内每个追踪的代表性样本)。该汇总层将追踪级事件预聚合为可管理的基数——按模板版本、按租户层级、按评估桶——并驱动 prompt 工程团队实际使用的 prompt 对比仪表板。
计费级遥测与运维级遥测之间的划分是横跨这三个层级的另一个维度。计费级遥测——token 计数、模型调用、付费层级请求——必须是不采样的,因为每条记录都驱动着收入确认或成本归属。运维级遥测可以进行激进的采样,因为丢失一部分代表性追踪并不会改变运维全景。将两者混为一谈会产生最糟糕的偏移:一个在评审中无人指出的采样变更,会在六周后悄无声息地破坏成本仪表板,而财务团队会比平台团队更早发现这一点。
架构上的认知飞跃
这件事之所以难,并不是 技术问题。列式追踪存储是存在的。OpenTelemetry 的 GenAI 语义约定在 2026 年已经稳定,Datadog 现在也原生支持它们。尾部采样处理器是一个被广泛理解的基础设施组件。组件都是现成的。
大多数团队所缺失的是这样一种认识:LLM 工作负载不是一个带有额外字段的服务指标问题。它们是一个披着服务指标外衣的产品分析问题。LLM 遥测系统的正确参考点更接近点击跟踪流水线(click-tracking pipeline),而不是主机指标流水线。基数特征、查询模式、长尾调试需求以及基于重要性的采样,都完美匹配用户行为分析(user-event analytics),而很难匹配团队现有的有限维度指标栈。
尽早意识到这一点的团队最终会在与其服务遥测不同的技术栈上运行 LLM 遥测,并且不再为此感到抱歉。没能意识到这一点的团队最终会每半年购买更大规模的指标存储层,每季度丢弃更多维度,并积累一堆无法复现且心照不宣的待处理升级任务。账单在涨,信号在降;事故复盘会议总是以“我们将改进遥测”作为一项永远无法真正落地的行动项而结束,因为团队正在改进的遥测栈在结构上就错误地估计了工作负载的形式。
解决方案不是单个仪表板或单个工具。这是一种架构决策,将工作负载的基数特征视为一等公民的设计输入——并围绕团队在凌晨三点实际需要回答的问题来构建遥测栈,而不是围绕指标存储恰好能廉价回答的问题。做到这一点的团队能交付可观测的 AI 功能。做不到的团队则是在付钱给指标供应商,让他们在信号最关键的时刻丢掉信号。
- https://opentelemetry.io/docs/specs/semconv/gen-ai/
- https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
- https://www.honeycomb.io/blog/llms-demand-observability-driven-development
- https://www.honeycomb.io/use-cases/ai-llm-observability
- https://docs.honeycomb.io/get-started/basics/observability/concepts/events-metrics-logs
- https://docs.datadoghq.com/account_management/billing/custom_metrics/
- https://docs.datadoghq.com/llm_observability/
- https://www.datadoghq.com/blog/llm-otel-semantic-convention/
- https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/tailsamplingprocessor/README.md
- https://opentelemetry.io/docs/concepts/sampling/
- https://clickhouse.com/resources/engineering/high-cardinality-slow-observability-challenge
- https://grafana.com/blog/2022/10/20/how-to-manage-high-cardinality-metrics-in-prometheus-and-kubernetes/
- https://oneuptime.com/blog/post/2026-04-01-ai-workload-observability-cost-crisis/view
- https://isburmistrov.substack.com/p/all-you-need-is-wide-events-not-metrics
