【可观测性工程】数据模型:时间序列、日志、Span、Profile 的内部表达
内容提要
本文解析可观测性五大支柱(指标、日志、链路、剖析、事件)的数据模型与存储成本。核心观点:存储结构须匹配查询模式,指标用标签倒排索引,日志靠标签索引加正文扫描,链路按trace_id分组,剖析依赖栈合并。成本公式显示日志和链路占存储账单85-95%,索引策略决定存储放大倍数,高基数标签是主要成本陷阱。
延伸解读
存储结构必须匹配查询模式
文章强调,可观测性数据的存储结构必须与查询模式精确匹配。例如,Prometheus 的倒排索引适合按标签聚合查询,Loki 的标签索引加正文扫描适合过滤加扫描,而 Tempo 按 trace_id 分组则适合精确查找。如果选型不当,比如用 Elasticsearch 全索引存储 Trace 或用 Loki 做高基数精确查找,会导致存储成本飙升和查询性能下降。理解这一点,有助于在选型时避免“用错误的数据模型回答正确的问题”。
日志和链路是存储成本的主要来源
文章指出,在可观测性五大支柱中,日志和链路(Trace)通常占存储账单的 85% 到 95%,而指标和剖析(Profile)占比相对较小。成本差异主要源于索引策略:日志的正文扫描和链路的属性索引会显著放大存储需求。例如,同样流量下,Elasticsearch 全索引存储日志的存储量可能是 Loki 的两倍以上;Jaeger 全索引存储链路比 Tempo 无索引方案高出约 4 倍。因此,控制成本的关键在于优化日志索引策略和链路采样。
高基数标签是成本陷阱
文章强调,高基数标签(如将 user_id 或动态 URL 路径作为标签)会显著增加存储和内存成本。对于指标,高基数会导致 series 数量激增,线性增加磁盘和内存占用;对于日志,高基数标签会破坏 Loki 的索引效率,导致查询扫描大量数据。文章建议通过标签模板化、限制标签数量、使用 Bloom Filter 等手段来缓解高基数问题,并指出这是成本控制的重要环节。
Q&A
可观测性五大支柱(指标、日志、链路、剖析、事件)分别采用什么数据模型?
指标(Metrics)采用时间序列模型,即 (labels, timestamp) → value;日志(Logs)采用日志流模型,即 (labels, timestamp) → line;链路(Traces)采用 Span 模型,按 trace_id 分组;剖析(Profiles)采用 (labels, timestamp, stack[]) → value 模型;事件(Events)采用 CloudEvents 模型,包含 id、source、type、time、data。
为什么说存储结构和索引策略必须匹配查询模式?
因为不同的查询模式对索引的需求不同。例如,指标查询需要按标签倒排索引快速定位序列;日志查询需要标签索引加正文扫描;链路查询需要按 trace_id 精确查找;剖析查询依赖栈合并。如果用错误的数据模型(如用 ES 全索引存 Trace、用 Loki 当高基数精确查找引擎)回答正确的问题,会导致存储成本高、查询性能差。
Prometheus TSDB 的 block 内部包含哪些关键文件?它们各自的作用是什么?
Prometheus TSDB block 包含 meta.json(记录时间范围、样本数、压缩层级等元数据)、index(倒排索引,将标签对映射到 series_ref)、chunks(Gorilla 压缩的样本数据)、tombstones(删除标记)。查询时通过 index 定位 series,再读取 chunks 解码样本。
Gorilla 压缩算法为什么能实现高压缩比?
Gorilla 利用时间戳等间距(delta-of-delta 常为 0)和相邻值变化小(XOR 前导零多)的假设,对时间戳和值进行高效编码。第一个时间戳完整存储,后续时间戳平均约 1.5 bit,counter 类指标值平均 1-2 字节,剧烈波动指标 4-6 字节,从而实现 10:1 到 20:1 的压缩比。
Loki 和 Elasticsearch 在日志存储和查询上有什么本质区别?
Elasticsearch 对日志所有字段建立倒排索引,支持任意字段搜索,但存储放大 1.5-3 倍;Loki 只对标签建立索引,正文不建索引,查询时通过标签过滤后扫描正文,存储放大仅 1.0-1.2 倍。因此 Loki 更节省成本,但高基数精确查找(如按 user_id 搜索)性能较差。
Tempo 和 Jaeger 在 Trace 存储上的设计取舍是什么?
Jaeger 采用全索引路线,将 Span 的 tags 建立倒排索引,支持任意属性搜索,但存储放大 3-5 倍;Tempo 不维护属性索引,按 trace_id 分组存储为 Parquet 块,查询 trace_id 时 O(1) 定位,但属性搜索(如 TraceQL)需要扫描块,延迟为秒级。Tempo 牺牲属性搜索性能换取存储成本降低。
为什么说日志和链路占存储账单的 85-95%?
因为日志和链路的写入吞吐极高,且存储成本受索引策略影响大。日志的存储放大系数(ES 为 2-3,Loki 为 1.0-1.2)和链路的索引放大(Jaeger 为 3-5,Tempo 为 1.0-1.3)导致成本差异巨大。相比之下,指标压缩率高,剖析数据量小,事件通常并入日志。因此控制成本的关键是优化日志索引和 Trace 采样。
高基数标签为什么是成本陷阱?如何缓解?
高基数标签(如将 user_id 或动态 URL 路径作为 label)会导致时间序列数量爆炸,线性增加存储和内存成本,甚至引发 OOM。缓解方法包括:路径模板化(如将 /api/users/123/orders/456 替换为 /api/users/:id/orders/:id)、使用 metric_relabel_configs 进行标签重写、避免将高基数信息放入 label,改用日志或 Trace 存储。
Profile 数据模型的核心是什么?为什么它占存储成本通常低于 5%?
Profile 数据模型核心是 Sample → Location → Function 的映射,通过符号去重和栈合并压缩数据。由于 profile 采集频率低(通常每分钟一次),单次体积小(几十到几百 KB),且压缩率高,因此相比日志和链路,其存储成本占比很小,通常低于 5%。
在选型可观测性后端时,应如何根据查询模式选择合适的数据模型?
应根据主要查询模式选择:如果主要查询是聚合指标趋势,选 Prometheus 或 VictoriaMetrics;如果主要查询是日志过滤和扫描,选 Loki 或 ClickHouse;如果主要查询是 trace_id 精确查找,选 Tempo;如果需要属性搜索,选 Jaeger 或考虑 TraceQL 的扫描能力。同时要避免用错误模型回答正确问题,如用 ES 存 Trace、用 Loki 做高基数精确查找。