ES|QL|有了列出和查询异步任务的 API,不用再盲等

9.1 给 ES|QL 加了 list / get query 两个 API。作用很朴素:让你能查看正在跑的异步查询有哪些、各自什么状态。以前只能拿到一个 id 然后等结果,现在可以主动问。

说明

来源和边界先交代:本文的依据出自 v9.4.2 标签下的官方文件(9.1.0.yml、EsqlListQueriesAction.java、EsqlGetQueryAction.java),文中引用的原文均来自这些文件。示例取自官方示例;哪些是文档写明、哪些是我的推断,正文里逐处标注。

实测环境:本地另起一个 Elasticsearch 9.4.2 单节点实例(官方 tar 包,端口 9200,独立 path.data),安全特性与机器学习均关闭;实验产生的索引与异步任务都在这台实例上。文末「在 9.4.2 上跑一遍」里的命令与响应均取自该实例。

图 1:官方文件 9.1.0.yml(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 9.1.0.yml

官方条目

9.1 的 changelog 标为 feature(9.1.0.yml 第 564–568 行 @ v9.4.2),area: ES|QL,issue 124827。

只有一行 summary。实质内容来自源码。

图 2:官方文件 EsqlListQueriesAction.java(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 EsqlListQueriesAction.java

先说异步查询的背景

ES|QL 支持异步执行:查询提交后立刻返回一个 id,结果稍后再取。

为什么要异步:有些查询跑得久(扫大范围、跨集群、复杂聚合),让 HTTP 连接一直挂着不合适。异步模式下:

  1. 提交查询 → 拿到一个 id;
  2. 之后用 id 去取结果(可以轮询,也可以等通知)。

这套流程里原本缺一环:你不知道当前有哪些查询在跑、各自到哪一步了。

你能拿到结果的只有"已完成"的那些(用 id 取)。正在跑的、失败的、卡住的,没有统一入口能看。

图 3:官方文件 EsqlGetQueryAction.java(v9.4.2 标签)。来源:GitHub elastic/elasticsearch v9.4.2 EsqlGetQueryAction.java

两个 API 分别做什么

List:列出查询。对应 EsqlListQueriesAction(源码 @ v9.4.2)。

Get:按 id 查单个查询的详情。对应 EsqlGetQueryAction(源码 @ v9.4.2)。

两者的关系很直白:List 给清单,Get 给细节。通常的用法是先 List 找到目标,再 Get 看它的具体情况。

请求结构上看出的差别

这两个 API 的请求对象写得很有对比性。

Get 请求(EsqlGetQueryRequest.java 第 18–31 行,源码 @ v9.4.2):

public class EsqlGetQueryRequest extends ActionRequest {
    ...
    public EsqlGetQueryRequest(AsyncExecutionId asyncExecutionId) {
    ...
    public AsyncExecutionId id() {
    ...
    asyncExecutionId = AsyncExecutionId.decode(streamInput.readString());

它必须带一个 AsyncExecutionId——没 id 就没法查。这是 Get 的语义决定的。

List 请求(EsqlListQueriesRequest.java 第 16–24 行,源码 @ v9.4.2):

public class EsqlListQueriesRequest extends LegacyActionRequest {
    public EsqlListQueriesRequest() {}
    ...
    public ActionRequestValidationException validate() {

构造函数是空的,没有必需参数。 你不需要知道任何 id 就能列。

这个小对比说明了一件事:List 是"发现"入口,Get 是"查看"入口。前者用来找到目标,后者用来看细节。设计上很清楚。

(说明:这两个 API 的 HTTP 路径已在文末实测——list 是 GET /_query/queries,get 是 GET /_query/queries/{id};完整参数只核对了本机用到的部分。)

为什么需要它

按使用阶段拆一下,就明白缺了什么:

阶段以前能做现在能做
提交拿到 id同
查看有哪些在跑没有入口List
看某一个的详情只能取结果(且要完成)Get
取结果用 id 取同
停止有(EsqlAsyncStopAction)同

缺的正是中间两行。

实际影响是什么?举个例子:你提交了一批异步查询,其中一条迟迟没结果。以前你只能反复用 id 取,看是"还没好"还是"失败了"——而这两者在某些情况下返回可能相似。

有了 Get,可以直接查它的状态。有了 List,可以看到"是不是还有别的一起卡住了"。

一个能确定的用途:卡住的任务

源码目录里还有 EsqlAsyncStopAction(源码 @ v9.4.2)——异步查询的停止接口。

List + Get + Stop 三者配合,才构成完整的任务管理:

  1. List 找出所有在跑的;
  2. Get 看哪条状态不对;
  3. Stop 停掉它。

没有 List 和 Get 的时候,Stop 很难用——你得先知道要停哪个 id,而 id 可能早就找不到了(比如提交端重启了)。

所以判断是:这条改动补的是"可观测性",而可观测性往往是控制能力的前提。 能停不等于会用——你得先能看见。

没有它会遇到什么具体麻烦

上面说"失去掌控"比较抽象,这里说两个具体场景,都是别处见过的同类问题。

场景一,提交端重启了。 服务提交了一批异步查询,然后进程重启,内存里记账的 id 全丢了。这时那些查询还在集群里跑着(或者已经跑完占着资源),但你不知道它们存在。

  • 有 List → 重新拿回清单,该取的取、该停的停;
  • 没有 List → 那些查询变成"孤儿",结果没人取、资源一直占着,直到它们自己过期。

场景二,某条查询卡住了。 一条查询跑了很久没结果,你怀疑它卡了。

  • 有 Get → 查它的状态,看是还在跑、还是已经失败;
  • 没有 Get → 只能反复取结果。而"还没完成"和"失败了"在某些返回里区分度不高,容易一直等下去。

这两个场景的共同点:问题是你要靠猜,不是"做不到"。而猜的成本在排查时会放大——尤其当同时有多条查询时,你连该怀疑哪条都不知道。

把这个写出来是因为:管理类 API 的价值往往体现在异常时刻。平时用不上,一旦需要就是"有它五分钟、没它两小时"的差别。

和别的管理接口放一起看

ES 里"先有管理能力、后有查看能力"的顺序不太常见,通常反过来。整理一下相关的:

能力说明
提交异步查询早期就有
取结果早期就有
停止(EsqlAsyncStopAction)有
列出 / 查看(本篇)9.1 才有

停止比列出先有,这个顺序有点反直觉。推测原因是:停止是用 id 直接操作的,实现简单;列出要维护一个可查询的任务清单,涉及状态存储和跨节点汇总,成本高。

(说明:这个顺序的原因是推测,官方没解释过。)

实际怎么用

HTTP 路径已在文末实测,具体 curl 见「在 9.4.2 上跑一遍」;这里只给用法思路:

场景一,排查"我的查询去哪了"。 用 List 看当前有哪些异步查询,找到你关心的那条 id。

场景二,看某条为什么没结果。 用 Get 查它的状态——是还在跑、还是失败了、失败原因是啥。

场景三,清理。 List 找出长时间没结束的,确认后 Stop 掉。

我的默认做法是:如果你在应用里大量使用异步查询,把 List 接进巡检或监控。它能回答"现在积压了多少异步查询"——这是以前完全看不到的指标。积压数突然上涨往往意味着上游有问题。

这条改动的价值在使用规模

说句实话:如果你只是偶尔用一次异步查询,这条改动对你几乎没影响——你本来就记得住 id。

它的价值随使用规模增长:

  • 少量、手工 → 没什么用;
  • 程序化提交、量大 → 必需。因为程序提交的任务你不会手工记账,没有 List 就等于失去了对它们的掌控。

所以我的建议是:先确认异步查询的使用规模,再决定要不要花时间接这两个 API。规模小就不用管;规模大或者计划变大,值得早点接上。

这也解释了一个现象:这类"管理类 API"在 changelog 里都很不起眼(一行 summary),但对特定使用者可能是关键能力。

所以看 changelog 时,条目的价值不能按字数判断。 做法是:遇到 area 属于常用领域的、哪怕只有一行 summary,也点进去看一眼——因为真正影响日常工作的,往往是这些不起眼的管理能力,而不是那些大特性。

在 9.4.2 上跑一遍

两个 API 的路径和返回字段直接实测:发起一个稍慢的异步查询,任务在列表里能查到、按 id 能看到状态,跑完之后列表清空。

图 4:9.4.2 单节点实例的实测输出。GET /_query/queries 在任务运行中列出 id 与 start_time_millis / running_time_nanos / query 三个字段;GET /_query/queries/{id} 多出 documents_found / values_loaded 两个进度计数;任务完成后列表为空,按 id 查询报「isn't running and hasn't stored its results」;DELETE /_query/async/{id} 返回 acknowledged:true。

QID=$(curl -s -X POST 'http://localhost:9200/_query/async' \
  -H 'Content-Type: application/json' \
  -d '{"query":"FROM big-idx | EVAL t = CONCAT(s, s, s, s, s, s, s, s) | EVAL k = CONCAT(t, t, t, t) | SORT k | LIMIT 1","wait_for_completion_timeout":"0ms"}' \
  | jq -r '.id'); printf 'task_id='; echo "$QID" | cut -c1-20
# task_id=FkpzU3hmd3RsUnJDQUxt
curl -s 'http://localhost:9200/_query/queries' | jq -c '[.queries | to_entries[] | {id:(.key|.[0:16]), fields:(.value|keys)}]'
# [{"id":"FkpzU3hmd3RsUnJD","fields":["query","running_time_nanos","start_time_millis"]}]
curl -s "http://localhost:9200/_query/queries/$QID" | jq -c '{fields:(keys), docs:.documents_found, values:.values_loaded}'
# {"fields":["documents_found","query","running_time_nanos","start_time_millis","values_loaded"],"docs":0,"values":0}
sleep 2
curl -s 'http://localhost:9200/_query/queries' | jq -c '.queries | length'
# 0
curl -s "http://localhost:9200/_query/queries/$QID" | jq -r '.error.root_cause[0].reason // "ok"' | cut -c1-105
# task [wi8XTTtWT-CZLTIr0BFPFA:1274] isn't running and hasn't stored its results
curl -s -X DELETE "http://localhost:9200/_query/async/$QID" | jq -c '.acknowledged'
# true

图 4 把正文标注「未核对」的路径和返回字段都补上了:list 是 GET /_query/queries,get 是 GET /_query/queries/{id},前者只给三个元字段,后者带进度计数。任务一旦跑完就从列表消失、get 也查不到,所以「查状态」只对还在跑的任务有意义。

边界:

  • 图 4 出自 9.4.2 单节点实例,任务是小数据量查询,documents_found / values_loaded 都还是 0,大任务下两个计数怎么涨未验证;
  • 完整参数只核对了本机用到的部分;
  • 「停止能力先于列出能力出现」的原因仍是推测。

写在最后

两个很朴素的 API:列出查询、按 id 查详情。

一,List 列出异步查询,Get 查单个详情。 源码上 Get 必须带 AsyncExecutionId,List 无需参数——设计上一个是"查看"入口,一个是"发现"入口,这个差别从请求对象的构造函数就能看出来。

二,它补的是可观测性。 和既有的 Stop 配合,才构成完整的任务管理:看见 → 判断 → 停止。

三,价值随异步查询的使用规模增长。 手工用几次没影响;程序化大量提交时,没有它等于失去掌控——尤其是提交端重启后那些任务就变成孤儿了。

另外诚实标注两处:两个 API 的完整参数只核对了本机用到的部分;"停止先于列出出现"的原因是推测。

参考

本文对照 Elasticsearch v9.4.2 标签的 changelog 与源码核对,引文与代码逐字取自原文。「在 9.4.2 上跑一遍」里已实际调用两个 API:list 为 GET /_query/queries(运行中列出三个元字段、完成后为空),get 为 GET /_query/queries/{id}(多出两个进度计数、完成后报「isn't running and hasn't stored its results」),DELETE /_query/async/{id} 返回 acknowledged:true。完整参数只核对了本机用到的部分,大任务下进度计数怎么涨未验证;"停止能力先于列出能力出现"的原因是推测,官方未解释。

0
0
0
0
评论
未登录
暂无评论