作者:来自 Elastic Yannis Roussos 及 Vinay Chandrasekhar
在 9.4 版本中, Elasticsearch 指标运行在完全列式引擎之上:存储减少 6.6 倍,查询速度提升 160 倍,原生支持 PromQL 和 OTel 。
获取 Elasticsearch 的实战体验:深入我们的示例 notebooks(在 Elasticsearch Labs repo 中),开始一个免费的 cloud trial,或立即在你的本地机器上尝试 Elastic。
一年的关于时间序列 data streams(TSDS)和 Elasticsearch Query Language(ES|QL)的工作,使 Elasticsearch 变成了一个指标数据存储系统,在数据摄入、存储和查询性能上可以与 Prometheus、Mimir 和 ClickHouse 相匹配甚至超越。
Elasticsearch 指标现在对 OpenTelemetry 指标来说,每个数据点仅占 3.75 bytes,而一年前是 25 bytes。indexing throughput 提升最高达 50%。时间序列查询速度最高提升 160 倍。这就是 TSDS 和 ES|QL 一年工作的成果:一个完全列式的指标引擎,可以在同一平台上存储和查询 OpenTelemetry(OTel)以及 Prometheus 数据,同时处理日志、traces 和文档。如果你想了解更详细的故事,包括架构细节和 benchmarks,可以阅读我们如何将 Elasticsearch 重构为领先的列式指标数据存储系统。
这篇文章将这些工作以及背后的深度解析汇总到一个地方。先快速介绍一下后续内容的背景。TSDS 是 Elasticsearch 用于指标的 index mode:在索引上启用它后,Elasticsearch 会按 time series 对每个 document 进行排序,将同一 series 路由到同一个 shard,并应用面向指标的压缩,而无需手动调优。ES|QL 是 Elasticsearch 的管道式查询语言,用来读取这些数据,在这项工作之后,它将 time series 作为一等数据类型来处理。下面所有内容都是为了让 TSDS 在存储上更小、写入更快,并让 ES|QL 更高效地查询它。
核心结论很简单:对于 TSDS 来说,Elasticsearch 现在不仅是大多数人熟悉的文档存储系统,同时也是一个完全列式的指标引擎。同一个平台可以同时存储日志、traces 和文档,并且 ES|QL 可以统一查询所有数据。
Elasticsearch 如何将指标存储为列式数据
Elasticsearch 中的列式布局建立在 TSDS 从 8.7 版本开始强制执行的四个特性之上:
-
指标名称加上 维度 名称和值会生成
_tsid,它是每个时间序列的唯一标识符。 -
数据按
[_tsid 升序, @timestamp 降序]排序,因此每个时间序列在磁盘上是连续存放的,并且维度值会聚集在一起。 -
分片路由基于
_tsid,因此某个时间序列只会存在于一个分片中。 -
底层索引按时间范围分段,并且不会重叠。
过去一年的关键变化是:停止为每个字段维护独立的倒排索引或 BKD 树,而是改为将每个字段作为 doc values(Lucene 的列式存储)进行存储,并由 doc value skippers 支持,这是一种轻量级的分层稀疏索引。这使得磁盘布局端到端变为列式结构,从而让查询引擎可以一次只读取一列,并通过向量化执行进行处理。
存储:每个数据点从 25 bytes 降到 3.75 bytes
当大多数数据点都携带一组唯一维度时(这是 OTel 和 Prometheus 数据的典型情况),每个文档只包含一个数据点。这种结构在 OTel 场景下最初是每个数据点 25 bytes。有四项改进将其一路降低到 3.75:
| 变更节省字节数可用版本 | ||
|---|---|---|
| doc value skippers | -10 bytes | v9.3 |
| 更大的数值编码块(numeric codec blocks) | -2 bytes | v9.3 |
合成 _id | -5 bytes | v9.4 |
| 序列号裁剪(sequence number trimming) | -4 bytes | v9.4 |
| 总计 | -21 bytes(25 → 3.75) |
-
doc value skippers 替代倒排索引和 BKD 树(-10 bytes)。 doc value skipper 是一种分层稀疏索引,它会记录每一批文档的最小值和最大值,因此范围查询可以跳过整块数据。由于 TSDS 按
_tsid和@timestamp排序,维度值在磁盘上是聚集的,因此 skippers 在这种结构上效果很好,并且没有可观测的查询性能回退。Lucene 层面的机制可以参考 Lucene 10 中的 DocValuesSkippers 如何让范围查询更快。自 9.3 版本起,TSDS 默认启用 skippers。 -
合成
_id(-5 bytes)。 Elasticsearch 不再为_id构建倒排索引,而是从_tsid和@timestamp推导出标识符,并使用 segment 级别的布隆过滤器进行去重(deduplication),只有在布隆过滤器命中时才回退到 doc values。Document API 保持不变。其实现细节,包括如何保持布隆过滤器低误判率,可以参考 Elasticsearch如何通过合成 _id 和布隆过滤器削减时间序列存储。该功能在 9.4 版本默认启用。 -
序列号裁剪(-4 bytes)。
_seq_no用于复制和乐观并发控制,但指标数据是追加写入的,很少进行 compare-and-swap 更新。对于 TSDS,当 segment merge 经过全局 checkpoint 之后,系统会裁剪掉序列号,从而避免后续 merge 开销。相关权衡以及如何恢复启用的方式见 Elasticsearch 如何通过在复制后移除序列号来减少 41% 的指标存储。该功能在 9.4 版本 GA。 -
更大的数值编码块(-2 bytes)。 在 9.3 版本中,将 numeric block size 从 128 提升到 512 个元素,使编码器能够更好地压缩重复序列,例如维度中包含 IP 和 MAC 地址的情况。
索引:原生 OTLP 与 Prometheus 摄取
OTel 和 Prometheus 都通过 protocol buffers 传输指标数据。Elasticsearch 现在可以直接通过原生 OpenTelemetry Protocol(OTLP)和 Prometheus remote write 接入这些二进制消息,而不再需要先转换为 bulk 请求。对于这两种协议,解析二进制比解析 JSON 更便宜;_tsid 哈希在协调节点只计算一次,并在数据节点复用;维度哈希在同一消息中的多个数据点之间摊销计算成本。再加上 doc value skippers 带来的索引 CPU 节省,以及 9.1 引入的 synthetic recovery source,OTel 的索引吞吐提升最高达 50%;由于 Prometheus remote write 复用同一摄取路径,它也能获得相同优化。OTLP 接入点在 9.3 达到 GA;Prometheus remote write 接入点在 9.4 以技术预览形式提供。
使用 ES|QL 查询 Elasticsearch 时间序列指标
只有当查询引擎也以列式方式读取数据时,列式布局才真正能发挥作用。ES|QL 中的 TS source command 以一个双层模型运行时间序列查询:先在每个 series 内进行内层聚合(例如 RATE 或 AVG_OVER_TIME),然后再在 series 之间进行外层聚合(例如 SUM 或 AVG)。
由于数据按 _tsid 顺序到达,执行引擎会在获取到的 metric value 列上持续应用内层函数,直到 _tsid 或时间 bucket 发生变化为止,并通过向量化与并行执行完成整个计算过程。
`
1. TS metrics
2. | WHERE TRANGE(1d)
3. | STATS SUM(RATE(search_requests)) BY host.name, TBUCKET(1h)
`AI写代码
在此基础上叠加了多个优化:
-
零拷贝解码(Zero-copy decoding) 从磁盘直接读取数据到计算引擎所使用的原始类型数组中,并对重复的
_tsid和维度值使用游程编码(run-length encoding),同时在 Lucene 层面对空指标进行过滤。 -
计数器速率计算(Counter rate evaluation) 为每个线程分配有序的
_tsid范围,从而在保持顺序扫描的同时正确检测重置(reset)。同时在时间 bucket 边界进行值插值,以计算准确的每桶增量。 -
滑动窗口(Sliding windows) 允许一个聚合跨越多个 bucket(例如:1 小时窗口、5 分钟 bucket),用于平滑噪声,并通过两阶段计算避免重复扫描数据。
这些优化组合在一起,使查询延迟相比早期 TSDS 版本最高提升达 160 倍。TS 以及窗口函数支持在 9.4 版本中达到正式发布。
ES|QL:将时间序列与指标作为一等公民
ES|QL 中的向量化时间序列引擎(可带来最高 160x 查询性能提升)是访问列式指标存储的方式。
时间序列支持在 9.2 版本作为技术预览通过 TS 命令引入,详见 9.2 ES|QL 更新。9.3 版本扩展了函数库并提升延迟性能,如在 Elastic 指标分析性能提升 5 倍中所述, 例如 PERCENTILE_OVER_TIME、STDDEV_OVER_TIME、VARIANCE_OVER_TIME 和用于趋势分析的 DERIV,用于约束噪声的 CLAMP,时间过滤的 TRANGE,以及时间序列聚合中的滑动窗口参数。到 9.4 版本,TS 命令及其时间序列聚合函数已正式发布。完整参考见 TS 命令文档 和 时间序列聚合函数文档。
ES|QL 的指标能力还包括三项核心增强:
-
原生指数直方图(Native exponential histograms)
exponential_histogram字段类型可以直接存储 OTel 指数直方图,因此可以在查询时计算任意百分位数,误差有界,无需固定桶或损失转换。::exponential_histogram转换也可以在同一查询中读取旧的 T-Digest 直方图数据。该能力在 9.4 正式发布。更多细节见 原生指数直方图支持。 -
时间序列发现(Time series discovery)
在大规模指标系统中,“发现有哪些数据”本身就是一项挑战。METRICS_INFO 和 TS_INFO 命令可以在当前查询上下文中返回真实存在数据的指标与序列,而不是 mapping 中声明的所有字段,包括类型、单位和维度信息。它们运行在同一TS执行引擎上,在数十亿文档规模下仍保持响应性。详见 METRICS_INFO 和 TS_INFO。 -
完全可查询的降采样(Downsampling)
降采样现在支持两种方式:last value(最大存储节省)和 aggregate(保留 min、max、sum、count,并支持 counter reset 以保证 rate 准确性)。两种方式都支持 histogram。从 9.4 开始,基于原始数据构建的 ES|QL dashboard 可以无修改地运行在降采样数据之上。详见 最后值采样 vs. 聚合采样。
由于这一切都基于 ES|QL,指标查询可以与语言其他能力组合使用,包括 LOOKUP JOIN 和 INLINE STATS,这是仅支持 PromQL 的系统无法做到的。
Prometheus 与 PromQL 兼容性
已经使用 PromQL 和 Grafana 仪表板超过十年的团队,不应该为了获得 Elasticsearch 的全部能力而重写查询。因此 Elasticsearch 现在提供端到端的 Prometheus 支持:既可以通过 Prometheus remote write 接收指标,也可以直接运行 PromQL 查询。
在数据摄取侧,原生 Prometheus remote write endpoint 可以直接接收来自 Prometheus 或 Grafana Alloy 的 Snappy 压缩 protobuf 数据,无需 adapter,并将 labels 映射为 TSDS dimensions,同时根据命名约定推断指标类型。其内部实现(从 protobuf 解析到数据流路由)详见 Prometheus Remote Write 在 Elasticsearch 中的摄取机制是如何工作的。由于 remote write 复用同一存储与查询引擎,Prometheus 工作负载也能获得相同的存储与查询性能提升。
PROMQL source command 可以在 ES|QL 内直接运行 PromQL。它不使用独立引擎,而是解析 PromQL 表达式,将函数映射为 ES|QL 等价实现(例如 rate 映射为 RATE,sum 映射为 SUM 等),并构建 TS 执行计划,从而让 PromQL 查询同样获得向量化、并行执行能力。
`PROMQL sum(rate(http_requests_total))`AI写代码
在 Kibana 中,该命令会从日期选择器中自动推断 start、end 和 step,并且结果会以标准 ES|QL 表格形式返回,你可以对其进行过滤、排序,并通过 LOOKUP JOIN 做数据增强。完整设计见 在 Elasticsearch 中使用原生 PromQL 支持查询 Prometheus 指标。
Prometheus remote write 和 PromQL 在 9.4 版本中均处于技术预览阶段。
Elasticsearch 指标的未来发展方向
三个正在推进的重点方向:
-
TSDS 编解码优化:进一步降低每个数据点的字节占用,并提供更可配置的布局。
-
摄取指标的批处理:减少结构良好数据的同步开销。
-
预计算块级聚合:doc value skippers 将携带 sum 和 count,用于加速查询处理。
PromQL 覆盖能力以及 Prometheus remote write 正在向 GA 推进。
方向已经明确:一个统一平台,配合面向指标的列式引擎,可以无缝存储并查询指标、日志与 traces。
开始免费 cloud trial,将你的 OTel 或 Prometheus 指标接入,然后运行 TS 或 PROMQL 查询。
本文描述的任何功能或发布时间均由 Elastic 自行决定,任何当前不可用的功能或特性可能无法按时交付或最终不交付。
常见问题解答
Elasticsearch 每个指标数据点占用多少字节?
在 9.4 版本中,OTel 指标每个数据点仅占 3.75 bytes,而一年前是 25 bytes。这一 6.6 倍的下降来自 TSDS 的四项优化:doc value skippers、合成 _id、序列号裁剪以及更大的 codec blocks。
Elasticsearch 是面向指标的列式存储吗?
对于 TSDS 指标来说,是的。每个指标和维度字段都以 Lucene doc values 形式存储在独立文件中,没有单独的倒排索引或 BKD 树。再结合 ES|QL 的向量化执行引擎,就形成了端到端的列式存储与查询执行。
如何在 ES|QL 中查询时间序列指标?
使用 TS source command(自 9.4 起 GA)。它会对每个 series 执行内层聚合(如 RATE、AVG_OVER_TIME),再执行跨 series 的外层聚合,并通过向量化并行执行。例如:TS metrics* | STATS SUM(RATE(search_requests)) BY host.name, TBUCKET(1h)。
可以用 PromQL 查询 Elasticsearch 指标吗?
可以。一种方式是使用 ES|QL 中的 PROMQL source command。它会解析 PromQL,将函数映射为 ES|QL 等价实现,并构建 TS 执行计划,使查询运行在同一引擎上。该功能在 9.4 版本中为技术预览,并正在扩展 PromQL 覆盖范围。你也可以将任何 Prometheus 兼容客户端直接指向 Elasticsearch 并运行 PromQL。
Elasticsearch 是否原生支持 OpenTelemetry 和 Prometheus 指标?
支持,两者都支持。OTel 指标通过 OTLP protobuf 端点直接接入(9.3 GA),Prometheus 指标通过 native Prometheus remote write 端点接入(9.4 技术预览),都无需 adapter 或 bulk 转换。OTel exponential histograms 以原生 exponential_histogram 字段类型存储(9.4 GA),因此可以在查询时计算任意百分位数。
原文:Elasticsearch metrics: Columnar engine, 160x faster queries - Elasticsearch Labs