
作者来自 Elastic Yannis Roussos 及 Vinay Chandrasekhar在 9.4 版本中 Elasticsearch 指标运行在完全列式引擎之上存储减少 6.6 倍查询速度提升 160 倍原生支持 PromQL 和 OTel 。获取 Elasticsearch 的实战体验深入我们的示例 notebooks在 Elasticsearch Labs repo 中开始一个免费的 cloud trial或立即在你的本地机器上尝试 Elastic。一年的关于时间序列 data streamsTSDS和 Elasticsearch Query LanguageES|QL的工作使 Elasticsearch 变成了一个指标数据存储系统在数据摄入、存储和查询性能上可以与 Prometheus、Mimir 和 ClickHouse 相匹配甚至超越。Elasticsearch 指标现在对 OpenTelemetry 指标来说每个数据点仅占 3.75 bytes而一年前是 25 bytes。indexing throughput 提升最高达 50%。时间序列查询速度最高提升 160 倍。这就是 TSDS 和 ES|QL 一年工作的成果一个完全列式的指标引擎可以在同一平台上存储和查询 OpenTelemetryOTel以及 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 valuesLucene 的列式存储进行存储并由 doc value skippers 支持这是一种轻量级的分层稀疏索引。这使得磁盘布局端到端变为列式结构从而让查询引擎可以一次只读取一列并通过向量化执行进行处理。存储每个数据点从 25 bytes 降到 3.75 bytes当大多数数据点都携带一组唯一维度时这是 OTel 和 Prometheus 数据的典型情况每个文档只包含一个数据点。这种结构在 OTel 场景下最初是每个数据点 25 bytes。有四项改进将其一路降低到 3.75变更节省字节数可用版本doc value skippers-10 bytesv9.3更大的数值编码块numeric codec blocks-2 bytesv9.3合成_id-5 bytesv9.4序列号裁剪sequence number trimming-4 bytesv9.4总计-21 bytes25 → 3.75doc 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 ProtocolOTLP和 Prometheus remote write 接入这些二进制消息而不再需要先转换为 bulk 请求。对于这两种协议解析二进制比解析 JSON 更便宜_tsid哈希在协调节点只计算一次并在数据节点复用维度哈希在同一消息中的多个数据点之间摊销计算成本。再加上 doc value skippers 带来的索引 CPU 节省以及 9.1 引入的 synthetic recovery sourceOTel 的索引吞吐提升最高达 50%由于 Prometheus remote write 复用同一摄取路径它也能获得相同优化。OTLP 接入点在 9.3 达到 GAPrometheus 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 发生变化为止并通过向量化与并行执行完成整个计算过程。TS metrics | WHERE TRANGE(1d) | STATS SUM(RATE(search_requests)) BY host.name, TBUCKET(1h)在此基础上叠加了多个优化零拷贝解码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 histogramsexponential_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映射为RATEsum映射为SUM等并构建TS执行计划从而让 PromQL 查询同样获得向量化、并行执行能力。PROMQL sum(rate(http_requests_total))在 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 GAPrometheus 指标通过 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