Elasticsearch 查询语言 ES|QL 现在支持在 FROM 中使用子查询了。三个索引,不同的数据模式,只需一个查询;每个数据源都有专属的管道,并带有各自独立的过滤器和转换逻辑。再也不用写一连串的 CASE 语句了。再也不用在客户端进行数据拼接了。想要添加第四个数据源?只需加上第四个分支;对现有的三个分支零修改。
问题所在:异构数据,统一查询
设想一次生产环境的故障排查。错误日志散落在三个微服务中:API 网关、支付服务和认证服务,每个服务都有不同的字段名称和命名约定。在支持子查询之前,若要通过单个 ES|QL 查询将它们组合在一起,意味着要把所有东西都塞进一个 FROM 子句中,并写上一连串的 CASE 语句:
FROM svc-gateway-*, svc-payments-*, svc-auth-* METADATA _index
| WHERE http.response.status_code >= 500
OR transaction.status IN ("failed", "timeout")
OR (event.action == "login" AND event.outcome == "failure")
| EVAL
service = CASE(
_index LIKE "svc-gateway*", "gateway",
... /* 每个数据源一个分支 */),
error_detail = CASE(
_index LIKE "svc-gateway*", CONCAT("HTTP ", http.response.status_code::string),
... /* 每个数据源一个分支 */)
| KEEP @timestamp, service, error_detail, source.ip
| SORT @timestamp DESC
这种做法既脆弱又缓慢。析取条件 OR 会阻碍“谓词下推”;每个索引都必须扫描所有条件。每增加一个数据源,每一个 CASE 链都会变得更长。如果你把这段代码复制到 5 个仪表板和 3 个告警规则中,那么一旦有任何变动,你就需要更新 8 个地方。
解决方案:独立的管道
子查询取代了庞大的单一 FROM + CASE 模式。每个数据源都获得了自己完整的独立管道:
FROM
(FROM svc-gateway-*
| WHERE http.response.status_code >= 500
| EVAL service = "gateway",
error_detail = CONCAT("HTTP ", http.response.status_code::string)
| KEEP @timestamp, service, error_detail, source.ip),
(FROM svc-payments-*
| WHERE transaction.status IN ("failed", "timeout")
| EVAL service = "payments",
error_detail = transaction.status
| KEEP @timestamp, service, error_detail, source.ip),
(FROM svc-auth-*
| WHERE event.action == "login" AND event.outcome == "failure"
| EVAL service = "auth",
error_detail = CONCAT(event.action, " ", event.outcome)
| KEEP @timestamp, service, error_detail, source.ip)
| SORT @timestamp DESC
| LIMIT 20
网关分支仅扫描 HTTP 500 错误。支付分支仅查看交易状态。认证分支仅检查登录失败事件。因为每个分支都有自己的 WHERE 子句,优化器会将过滤器独立地推送到每个索引中,从而恢复了单个带有 OR 条件的 FROM 子句所阻碍的谓词下推功能。在某个分支中存在但在其他分支中不存在的字段将被自动填充为 null。
添加第四个服务意味着只需添加第四个分支。现有的分支完全不需要修改。
将其保存为视图
这就是子查询与逻辑视图(logical views)强强联手的地方。只需一次 API 调用,就可以将上述子查询封装在一个命名视图中:
PUT _query/view/error_triage
{
"query": "FROM (FROM svc-gateway-* | WHERE ...) , (FROM svc-payments-* | WHERE ...) , (FROM svc-auth-* | WHERE ...)"
}
现在,调用方只需编写 FROM error_triage | STATS error_count = COUNT(*) BY service。三个索引,三个管道,汇聚成一个名称。如果你有 10 个仪表板和 5 个告警规则使用了这个模式,在以前你需要维护 15 份相同的逻辑代码;而使用视图后,只需维护一份定义。当你要添加第四个服务时,调用方(消费者)端“零修改”。请参阅 Elasticsearch ES|QL 视图 获取关于视图的完整深度解析。
分支内部可以做什么
每个分支都支持完整的 ES|QL 管道操作:WHERE、EVAL、STATS、LOOKUP JOIN、ENRICH 等等。请参阅子查询文档获取支持命令的完整列表。
聚合不同的指标,然后再合并
每个分支可以在结果合并之前计算自己的汇总数据。当不同的索引在不同的字段名称下记录相同的概念时,这非常有用:
FROM
(FROM svc-gateway-*
| STATS avg_latency = AVG(http.response.time_ms) BY hour = BUCKET(@timestamp, 1 hour)
| EVAL service = "gateway"),
(FROM svc-payments-*
| STATS avg_latency = AVG(transaction.duration_ms) BY hour = BUCKET(@timestamp, 1 hour)
| EVAL service = "payments")
| SORT hour DESC, service
两个分支都生成了 avg_latency 和 hour字段,但各自是从不同的源字段计算得出的。合并后的结果是一个统一的表格,你可以直接用于绘制图表或触发告警,而无需在数据写入阶段对字段名进行标准化。这种模式使用单一的 FROM 是不可能实现的;如果没有子查询,你无法对每个索引分别进行不同的聚合计算。
子查询 vs. FORK
ES|QL 同时还提供了 FORK 命令(现已正式 GA),它可以从同一个输入数据创建并行的执行分支。两者的区别在于:
不同的索引源 → 使用子查询。相同的数据,不同的分析 → 使用 FORK。
横向对比
如果你有使用其他查询语言的经验,以下是截至本文撰写时,ES|QL 子查询与其他技术的对比情况:
Splunk SPL/SPL2 在经典的 SPL 中有 append 和 multisearch,而在 SPL2 中添加了 union 命令,用于合并来自多个数据集的事件(这是与 ES|QL 子查询最接近的类似物)。联邦搜索(Federated Search)将其扩展到了远程的 Splunk 部署中(类似于 Elasticsearch 的 CCS 跨集群搜索)。区别在于引擎处理每个分支的方式:ES|QL 子查询赋予每个分支独立的谓词下推能力,这意味着过滤器会被分别推送到每个索引的分片级别结构中。SPL2 的 union 合并了数据集,但跨分支的优化受限于搜索调度程序可以并行化的程度。将 ES|QL 子查询封装在视图中,可以为你提供引擎级别的封装以及基于角色的访问控制 (RBAC);Splunk 对应的功能是保存的搜索和宏,它们是在解析时展开的文本替换。
SQL 数据库 拥有 UNION ALL,这是最接近的功能。区别在于 SQL 的 UNION ALL 通常要求在解析时各分支列的数量和类型完全匹配。ES|QL 子查询更为宽容:在一个分支中存在而在另一个分支中不存在的列会自动用 null 填充,当你的数据源具有不同的 schema 时(这在可观测性数据中是常态),这一点至关重要。SQL 视图同样能解决逻辑复用问题,但 ES|QL 视图是集群级别的对象,而不是受限于某个数据库作用域;它们甚至可以跨越跨集群搜索(CCS)的边界工作。
Grafana / Datadog / 其他仪表板工具 通常在可视化层处理多数据源的组合:运行不同的独立查询,然后在面板面板中进行合并。这种方式用于展示没问题,但对于告警、下游查询以及任何需要以编程方式获取单一结果集的场景就会失效。ES|QL 子查询将数据的组合下推到了引擎层,因此告警、视图和 API 调用方都能获得完全一致的统一结果。
当前的限制
在技术预览版期间,子查询是非关联的;各个分支独立运行,无法引用外部查询的数据。目前子查询仅在 FROM 中支持(不支持 TS 命令),并且 FORK 不能在子查询内部或子查询之后使用。具体详情请参阅子查询官方文档。
子查询的下一步是什么
WHERE 子查询 —— 例如 WHERE field IN (FROM other_index | ...) 以及其他关联形式 —— 将会把这种组合模式从 FROM 扩展到过滤层。这会将 SQL 中广为大家熟悉的嵌套过滤模式带入到 ES|QL 中。