你的 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 丢弃了。
- 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
