当前位置: 首页 > news >正文

不要把指标数据浪费掉:使用 ES|QL TS 命令来查询它们

作者:来自 Elastic Felix Barnsteiner

重新校准你对时间序列查询的心智模型:了解为什么 FROM 可能会对 metrics 产生不准确结果,TS 如何修复这一点,以及何时使用每个命令。

如果你在 logs 和 traces 中使用 ES|QL ,那么 FROM 可能已经成为你的第二天性,但在 metrics 上它可能会返回数值错误的结果。一条类似 FROM metrics-* | STATS SUM(request_count) 的查询会把所有主机上每个采样点的累积计数值加总。结果会无限增长,并且既不是速率,也不是计数,或任何有意义的指标。TS 通过先将数据按时间序列分组来解决这个问题,然后提供 RATE 、 AVG_OVER_TIME 、 LAST_OVER_TIME 等函数,这些函数在每个时间序列内部进行计算。

关于 metrics analytics 在 ES|QL 和 Discover 中的高层概览,可以参考 在 Elastic Observability 中轻松探索和分析指标 。本文将聚焦其底层机制。

以下是五点心智模型:

  • FROM 将每一条 document 当作独立行处理。这在 events 中是正确的,但 metrics 的聚合通常需要依赖每一行所属的 time series。
  • TS 增加了 time series 上下文:它在任何其他聚合之前先按 time series 对数据点进行分组和聚合,并启用 RATE 、 AVG_OVER_TIME 和 LAST_OVER_TIME 等函数。
  • 一个 TS | STATS 查询通常包含两个聚合阶段。内部阶段对每个 time series 内部的 samples 进行归约;外部阶段再对这些每个 series 的结果进行分组和合并。
  • 默认的内部聚合是 LAST_OVER_TIME,这就是为什么 TS metrics | STATS AVG(cpu_usage) 和 FROM metrics | STATS AVG(cpu_usage) 可能返回不同结果的原因。
  • 使用 TS 查询 time series data stream ( TSDS )。使用 FROM 处理 events 和原始 document 检查。

时间序列到底是什么?

时间序列是由一组(时间戳,数值)数据点构成的序列,由指标名称以及一组唯一的维度值来标识。

例如,在数据中心 dc1 中,由主机 h1 每 30 秒上报的 request_count 是一个时间序列。同样的指标在 dc1 中的主机 h2 上报,则是另一个不同的时间序列。

在 time series data stream 中,每一条指标文档都会携带一个内部的 _tsid 字段,用来唯一标识一个时间序列。共享同一个 _tsid 的样本属于同一条时间序列,并按时间戳排序顺序存储。

这种存储布局使得按时间序列进行高效聚合成为可能。这也解释了为什么 TS 只能作用于 time series data streams。其他索引模式没有时间序列的概念,因此 TS 所依赖的按序列操作无法绑定到任何标识符上。FROM 不支持这些操作,这也是下一部分要讨论的内容。

为什么 FROM 会让指标数据“留在桌面上”

考虑一个名为 request_count 的计数器,它每 30 秒从三个主机采集一次。

计数器是一种累积指标:每个采样点都是自进程开始报告以来的累计总数。对于 request_count 来说,值为 1000 表示 “该时间序列已经观测到 1000 次请求”,而不是 “在上一个采样点之后发生了 1000 次请求”。当进程重启时,计数器会重置为 0,因此在 1004 之后出现的 4 是一个新的计数起点,而不是负流量。ES|QL 的 RATE 函数会在时间序列内部计算每秒变化,并正确处理重置,不会出现异常。

你希望计算所有主机的总请求速率,并按 5 分钟进行分桶。

如果你习惯在事件数据上编写 ES|QL,你可能会从这样的查询开始:

FROM metrics-* | WHERE TRANGE(1h) | STATS SUM(request_count) BY BUCKET(@timestamp, 5m)

它生成的图表一开始看起来是合理的:一条随时间上升的曲线。但 y 轴上的数值实际上是该时间桶内所有累积计数器值的求和结果。每一个主机都会在每个采样点贡献它自己的运行总计,并被重复累加。由于查询对这些累计值使用了 SUM ,结果既不是速率,也不是时间桶内的请求数量,并且即使应用已经不再接收请求,曲线仍然会无界增长。

request_count 是一个单调递增计数器,因此它的原始值表示 “该主机历史上累计发生的请求数”,而不是 “该时间桶内发生的请求数”。正确的计算方式是 “先在每个主机的时间序列内计算计数器每秒增长量,然后再跨主机求和”。FROM 无法直接表达这种操作。它可以按字段对行进行分组,但它没有“同一时间序列随时间变化”的内建概念,也无法在每个时间序列内部计算 counter 的变化。同时,它也不支持类似 RATE(request_count, 5m) 这样的滑动窗口时间序列函数,我们将在下文回到这一点。

TS 正是为此引入的,它提供了一种简洁的语法来表达时间序列聚合:

TS metrics-* | WHERE TRANGE(1h) | STATS SUM(RATE(request_count)) BY TBUCKET(5m)

RATE(request_count) 在每个时间序列内部运行,生成按秒计算的速率,并正确处理计数器重置。随后 SUM 会在主机之间对这些速率进行求和。

两个聚合阶段:inner 与 outer

每一个 TS | STATS 查询都包含两个明确的聚合阶段。

我们用一个计算按数据中心(data center)请求速率的查询来具体说明这一点:

TS metrics-* | WHERE TRANGE(1h) | STATS SUM(RATE(request_count)) BY datacenter, TBUCKET(5m)

下面的图展示 TS 如何评估这个查询。它首先在每个 time series 内部对 samples 进行归约,然后将这些 per-series 值按 datacenter 和 time bucket 分组并合并为每个 datacenter 和 time bucket 的单一结果。

这些阶段分别是:

内层(Inner,时间序列内部)

在每一条 time series 内部运行。它针对每个时间序列分别执行,并在每个 bucket 内将多个(timestamp, value)数据点压缩为一个值。这个值来自 inner aggregation function,例如上例中的 RATE。

支持的函数包括:RATE、AVG_OVER_TIME、MAX_OVER_TIME、LAST_OVER_TIME、STDDEV_OVER_TIME 等,更多函数可见 time series aggregation functions页面。

外层(Outer,跨时间序列的 “分组” 阶段)

将每条 time series 的结果进一步合并,在同一个 group 和 bucket 内进行聚合。

使用的是常规 ES|QL 聚合函数,例如:SUM、AVG、MAX、MIN、percentiles 等。

在 SUM(RATE(request_count)) BY datacenter, TBUCKET(5m) 中:

  • RATE(request_count) 是内层聚合(inner aggregation),它在每个 time series 内运行

  • SUM(...) 是外层聚合(outer aggregation),它把同一个 datacenter + bucket 内的多个 time series 结果合并

  • TBUCKET(5m) 定义时间桶边界(等价于 BUCKET(@timestamp, 5m))

外层聚合是可选的

如果你只需要每条 time series 的结果,可以直接使用 time series aggregation function,而不需要 outer aggregation:

TS metrics-* | WHERE TRANGE(1h) | STATS request_rate = RATE(request_count) BY TBUCKET(5m)

它会保留每个 time series 在每个 bucket 内的 per-series rate 结果,而不是再用 SUM 、 AVG 或其他跨 time series 的聚合把它们合并成单个值。

默认的内层聚合:LAST_OVER_TIME

TS 必须在执行外层聚合之前先在每个 time series 内部对原始 samples 进行归约。这意味着 TS | STATS 聚合中的每一个 metric 字段都需要一个 inner aggregation,即使查询中没有显式写出来。

考虑一个名为 cpu_usage 的指标。它是一个 gauge:一种在时间点上记录数值并且可以自由上下波动的指标。一个 0.42 的 sample 表示 “该主机在此时刻 CPU 使用率为 42%”。对于 gauge 来说,一个 bucket 中 “自然的值” 就是最新的 sample。

这正是 ES|QL 自动为你填充的内容。如果你写 TS metrics | STATS AVG(cpu_usage) BY host.name, TBUCKET(5m),那么隐式的 inner aggregation 就是 LAST_OVER_TIME(cpu_usage),该查询等价于:

TS metrics | WHERE TRANGE(1h) | STATS AVG(LAST_OVER_TIME(cpu_usage)) BY host.name, TBUCKET(5m)

对于每个 time series,LAST_OVER_TIME 会在 bucket 内选择最新的 sample。然后 AVG 再在所有 time series 之间做平均。

这也是为什么看起来相同的 FROM 和 TS 查询可能会返回不同结果。FROM 会对每一条 document 进行平均,而 TS 会先在每个 bucket 内把每个 time series 归约成一个值,再进行平均。如果你的主机以不同频率上报数据,这些平均值就会产生差异。例如在一个 5 分钟的 bucket 中,一个每秒上报的主机会贡献 300 条 document,而一个每两分钟上报的主机只贡献 2 到 3 条。使用 FROM | STATS AVG(cpu_usage) 时,数据上报更频繁的主机会在平均值中占更大权重。而在 TS 中,每个 time series 会先被归约为一个 bucket 值,因此外层平均时,每个主机只贡献一个值。

如果你想要的是 bucket 内的平均值,而不是最新值,就需要显式指定 inner aggregation:

TS metrics-* | WHERE TRANGE(1h) | STATS AVG(AVG_OVER_TIME(cpu_usage)) BY host.name, TBUCKET(5m)

AVG_OVER_TIME 会对每个 time series 内的所有 CPU 使用率 samples 求平均。然后外层 AVG 再在匹配的 time series 之间对这些值进行平均。这种方式的含义是:在每个 time series 内是 sample-weighted(按采样分布加权),在 time series 之间是 equally weighted(等权)。当你关心 bucket 内“整体如何变化”,而不是“最终停在哪里”时,这种方式是合适的。

同样的规则也适用于峰值和谷值。对于 CPU 峰值图表,应使用 MAX(MAX_OVER_TIME(cpu_usage)),而不是简单的 MAX(cpu_usage)。内层 MAX_OVER_TIME 在每个 time series 内找峰值,外层 MAX 再在匹配的 time series 之间找最大值。

counter 的情况则相反。它们的 sample 值是累计总量,因此单独看最新 sample 往往没有意义。对于 counter,你几乎总是需要的内层聚合是 RATE(得到每秒速率)或者 INCREASE(得到 bucket 内增量)。如果退回默认的 LAST_OVER_TIME,就会得到最新的累积值,这正是上一节 FROM 查询掉入的陷阱。

要有意识地选择 inner function,而 outer function 通常更简单。

什么时候用 TS,什么时候用 FROM

一个实用的经验规则:

  • 用 TS:用于 time series data stream 上的 metric 聚合。它是为这种数据设计的命令,默认提供 per-series 语义。

  • 用 FROM:用于 events,例如 logs、traces、审计记录、事务数据。每一行都是独立的,没有 time series 上下文。

FROM 也可以作用在 TSDS index 上,有时也有用,比如你只想查看原始 metric documents 而不做 per-series 分组。但在 dashboard、告警以及任何图表场景中,TS 才是默认正确选择。

如果你需要先发现数据中有哪些 metrics 或 time series,可以在 TS 之后、STATS 之前使用 METRICS_INFO 或 TS_INFO。更深入的说明可以参考 ES |QL METRICS_INFO 和 TS_INFO:编目你的时间序列数据。

使用 ES|QL 对 TS 结果进行后处理

第一个 STATS 命令是时间序列处理与普通 ES|QL 处理之间的边界。在第一个 STATS 之前,TS 需要按 _tsid 保持数据分组,因此不允许执行会改变行顺序或结构的命令。在第一个 STATS 之后,输出就变成了标准的 ES|QL 表,可以进行排序、限制、关联 lookup 数据、丰富字段,或者计算派生列。

例如,这个查询计算每个主机在每个 bucket 的平均 CPU,然后找出每个主机的最大 bucket 平均值,并返回其比率:

TS metrics-* | WHERE TRANGE(1h) | STATS avg_cpu = AVG(AVG_OVER_TIME(cpu_usage)) BY host.name, time_bucket = TBUCKET(5m) | INLINE STATS max_avg_cpu = MAX(avg_cpu) BY host.name | EVAL cpu_ratio = avg_cpu / max_avg_cpu | KEEP host.name, time_bucket, cpu_ratio | SORT host.name, time_bucket DESC

内层聚合的滑动窗口

时间序列聚合函数接受第二个参数:用于 inner phase 的窗口大小。

TS metrics-* | WHERE TRANGE(1h) | STATS AVG(RATE(app.requests, 5m)) BY TBUCKET(1m)

这会在一个 5 分钟滑动窗口内计算 rate,但每 1 分钟输出一个值。当你想在较细粒度的 bucket 上得到更平滑的图表时,这个功能非常有用。

这个 window 是 ES |QL 对 PromQL range vector selector 的对应实现:RATE(app.requests, 5m) 在语义上等价于 rate(app_requests[5m])。

一些值得注意的坑(Gotchas)

TS 中有一些行为在从基于事件的 FROM 心智模型切换过来时可能会觉得“反直觉”。这些都不是 bug,而是 per-series 模型的直接结果。

COUNT(*) 会被拒绝

如果你想知道每个 bucket 内每个 service 收集了多少 samples,FROM 的直觉会让你写 COUNT(*),但 TS 会拒绝这个写法:在按 time series 分组之后,“行”已经不再存在,因此 row count 没有明确意义。

你需要明确你真正想统计的内容:

每个 service 的 sample 数量:
STATS samples = SUM(COUNT_OVER_TIME(cpu_usage)) BY service.name, TBUCKET(5m)
内层 COUNT_OVER_TIME 统计每个 time series 的 samples,外层 SUM 在分组内合并这些 time series。

每个 service 报告的唯一 host 数量:
STATS hosts = COUNT_DISTINCT(host.name) BY service.name, TBUCKET(5m)
这会在 time series 之间对标签值进行去重统计。

在 STATS 之前不能 SORT / LIMIT / LOOKUP / ENRICH

TS metrics | SORT @timestamp | STATS ... 这样的查询会失败。

因为在 STATS 之前必须先完成 _tsid 的分组,否则数据语义不成立。

如果需要过滤范围,请使用 WHERE 来缩小数据集。

在第一个 STATS 之后,输出就变成普通 ES|QL,可以像之前章节一样继续使用任意命令。

Gauge vs Counter 映射

时间序列函数对字段 mapping 中的 metric 类型非常敏感:

  • RATE 只适用于 counter

  • *_OVER_TIME 用于 gauge

如果你手动构建 TSDS mapping,需要特别注意这一点。

与 Prometheus 的摩擦点

这对 Prometheus 用户来说可能会比较“卡”。

Prometheus 的 metric type metadata 并不总是能随着数据进入 Elasticsearch,因此类型可能需要通过命名规则推断(例如 _total 表示 counter)。这些 heuristics 并不完美,因此如果 metric 被误分类,就可能被本应支持它的函数拒绝。

关于 Prometheus Remote Write 如何映射 metric type 到 TSDS 的细节,可以参考 Prometheus Remote Write 在 Elasticsearch 中是如何工作的。

未来改进

计划中的显式转换函数(gauge-to-counter 和 counter-to-gauge)将使这类问题在查询时更容易修复。

Kibana 图表在放大时变空

在 Kibana 中,TBUCKET 会随时间选择器动态调整。放大视图会缩小 bucket size。当 bucket size 小于数据采集间隔时,会出现大量空 bucket,RATE 等函数返回 null,图表就会“静默变空”。

Elastic 正在评估改进方案,例如:

  • 当 bucket 过小时发出运行时警告

  • 可配置最小 bucket size

  • 自动扩大 window 或 bucket size

总结

对于 metric 查询,优先使用 TS,除非你明确需要原始 documents。然后根据每个 time series 内部“值应该代表什么”来选择 inner aggregation:counter 用 RATE,gauge 当前值用 LAST_OVER_TIME,峰值、均值、最小值或分布则使用显式的 *_OVER_TIME 函数。

当 per-series 的值计算正确后,outer aggregation 就是更熟悉的部分:把这些 time series 聚合成你需要的图表、告警或表格。

完整参考请查看 TS 命令文档以及 time series aggregation functions 列表。

常见问题

为什么 ES|QL FROM 在 counter 指标上会返回错误数值?
FROM 会把每条 metric document 当作独立 row,因此 SUM(request_count) 会把跨主机、跨时间的累积计数相加。结果会无限增长,并不是 rate 或请求数。应使用 TS + RATE 在每个 time series 内计算每秒速率,然后再跨主机 SUM。

ES|QL 中 TS 和 FROM 的区别是什么?
TS 是 time series data streams (TSDS) 的源命令。它会在聚合前按 time series 分组,从而支持 RATE、AVG_OVER_TIME、LAST_OVER_TIME 等函数。FROM 将 document 当作独立 row 读取,没有 per-series 语义。TS 用于 time series 数据查询,FROM 用于事件和原始文档检查。

为什么 TS metrics | STATS AVG(cpu_usage) 和 FROM metrics | STATS AVG(cpu_usage) 的平均值不同?
TS 使用隐式 inner aggregation(默认 LAST_OVER_TIME),因此每个 time series 每个 bucket 只贡献一个值。FROM 则对每条 document 求平均,因此高频上报的 host 会权重大很多。TS 是“每个 time series 等权”,FROM 是“每个 document 等权”。

如何在 ES|QL 中计算 counter 的每秒速率?
使用 TS 和 RATE 函数:
TS metrics-* | STATS SUM(RATE(request_count)) BY TBUCKET(5m), host.name
RATE 在每个 time series 内计算 per-second 变化并正确处理 counter reset,外层 SUM 再跨主机汇总。

什么时候需要在 ES|QL metrics 查询中添加时间过滤?
在 Kibana 的 Discover 或 dashboard 等 ES|QL 应用中,系统会自动应用全局时间选择器,因此不需要手动加过滤条件。在 Kibana 之外(例如 Dev Tools)或独立执行查询时,必须显式添加 timestamp filter,以限制扫描范围:

WHERE @timestamp > NOW() - 1 hour AND @timestamp <= NOW()

或者等价的简写形式:

WHERE TRANGE(1h)

TRANGE(1h) 是用于最近时间窗口的推荐简写。它等价于把 @timestamp 过滤到最近一小时,并以 NOW() 作为结束时间。

TS | STATS 查询的两个聚合阶段是什么?

inner phase(内层阶段)在每个 time series 内运行,它会使用 time series function(RATE、AVG_OVER_TIME、MAX_OVER_TIME、LAST_OVER_TIME 等)把每个 bucket 内多个(timestamp, value)样本压缩成每个 time series 每个 bucket 的一个值。

outer phase(外层阶段)在 time series 之间运行,使用常规 ES|QL 聚合函数(SUM、AVG、MAX、percentiles 等)将这些 per-series 的值进一步合并。

结论是:inner function 决定“每条 time series 内怎么计算”,outer function 是熟悉的“跨 series 如何汇总”。正确性主要取决于 inner function。

为什么我的 Kibana metrics 图表在缩放时会变空?

TBUCKET 会根据时间选择器自动调整 bucket size。缩放时 bucket 会变小,当 bucket size 小于数据采集间隔时,一些 bucket 没有 sample,RATE 返回 null,图表就会静默变空。

解决方法是扩大时间范围,或者在 inner aggregation 中设置更长窗口,例如 RATE(request_count, 5m)。

在 TS 查询中可以在 STATS 之前使用 SORT、LIMIT 或 LOOKUP JOIN 吗?

不可以。TS 在第一个 STATS 之前必须保持数据按 _tsid 分组,因此任何会改变行顺序或结构的操作都会被拒绝。

正确方式是:先用 WHERE 过滤,再执行 STATS,然后在 STATS 之后对生成的标准 ES|QL 表进行 SORT、LIMIT、join 或 enrichment 操作。

原文:ES|QL TS command: Query metrics with time series context — Elastic Observability Labs

http://www.jsqmd.com/news/922171/

相关文章:

  • 10.ThinkPadT14 Gen2 AMD版+Ubuntu cinnamon系统显卡 initramfs问题,通用思路amdgpu 没进 initramfs
  • 2026无锡卫生间/阳台/厨房/屋顶漏水怎么办?本地根治方法+避坑全攻略 - 吉修匠
  • 美白牙膏怎么选不伤牙?敏感牙黄人群选择指南 - 资讯焦点
  • 2026年geo服务十强竞争力报告及选型指南 - 资讯焦点
  • GPT-5技术架构与实战应用深度解析:从智能路由到性能飞跃
  • 2026年|降AIGC率去AI痕迹:Gemini三组降维指令与3款工具协同压测(99%→10%) - 降AI实验室
  • 别慌!FNPLicensingService.exe不是病毒,它是Photoshop/CAD正版授权的‘守门员’
  • blibili视频怎么下载全场景合规操作步骤与水印处理方案汇总 - 科技热点发布
  • 百考通AI智能化梳理学术脉络,让研究起点更清晰
  • 从‘一片死水’到‘波光粼粼’:UE材质新手也能搞定的水面交互特效入门指南
  • 3分钟掌握RCM注入:NS-USBloader的跨平台Payload管理指南
  • 深度解析:如何通过AlienFX Tools实现专业级硬件控制工具与系统性能优化
  • 深圳市盛鑫旺木业:盐田靠谱的木架定制公司有哪些 - LYL仔仔
  • 怎样高效获取网络视频:专业媒体下载工具完整指南
  • AMD Ryzen终极调试指南:如何用SMUDebugTool实现专业级硬件控制
  • 别再手动抠窗户了!3dMax 2016+ 用 PolyWindow 插件5分钟搞定异形窗建模
  • 去中心化社交与预测市场融合:AI驱动的信息验证新范式
  • AMD Ryzen硬件调试终极指南:SMUDebugTool深度实战手册
  • 机制设计:从分蛋糕到区块链与AI的规则艺术
  • 2026年5月北京老房翻新装修公司推荐:十大排名防开裂脱落评测专业价格 - 品牌推荐
  • 3个实用技巧让你的Parsec-vdd虚拟显示器发挥最大价值
  • 如何免费解锁Wand专业版:3步完成游戏增强与远程控制
  • 2026 哈密设备吊装搬运厂家优选榜:室内移位、折臂吊装、重型高空、厂房机床、工厂整厂搬迁服务商综合推荐指南 - 海棠依旧大
  • 2026西安卫生间天花板漏水处理靠谱团队TOP4:本地修缮实力榜单 专业防水公司排名推荐(2026年5月防水补漏最新TOP权威排名) - 冠盾建筑修缮
  • 艾尔登法环帧率解锁与画面增强:3分钟快速上手指南
  • 高精度空间解算,镜像视界核心技术落地全场景视频孪生
  • 用Flask和Python搞定m3u8视频下载与备份:本地存储+Cloudflare R2上传保姆级教程
  • 2026年10款口碑佳CRM推荐:客户资源管理平台 - Joyky
  • AI算力:驱动智能时代的隐形引擎
  • 数字身份困境与死寂互联网:虚假身份泛滥下的网络生态危机与应对