Elasticsearch ES|QL “读取时模式”:你的未映射字段一直都在那里
作者来自 Elastic Tyler PerkinsES|QL 的新未映射字段功能使任何从未映射的字段都可以针对历史数据进行查询无需重新索引。亲手体验 Elasticsearch深入探索 Elasticsearch Labs 仓库中的示例 notebooks开始免费的云试用或者现在就在你的本地机器上试用 Elastic。Elasticsearch Query Language ES|QL 现在支持读取时模式。只需添加一行SET unmapped_fieldsload_source 中的每个未映射字段都会变得可查询。无需重新索引。无需修改 pipeline。还可以追溯性地作用于你几个月前摄取的数据。添加 JSON_EXTRACT 用于从原始 JSON 字符串中进行精确提取flattened 字段、嵌入式 payloads、OTel resource attributes 60 多个标准映射不会单独索引的语义约定。它们共同将模式从一道门变成一个光谱索引字段用于速度_source 回退用于其他所有内容。另请参阅我们关于ES|QL views 和 ES|QL subqueries 的配套文章。映射悬崖边缘Elasticsearch mappings 定义了字段如何被索引。当 mapping 完整时查询速度很快它们会命中倒排索引、doc values以及 Elasticsearch 在索引时构建的所有性能结构。但当一个字段从未被映射时在 onboarding 期间遗漏、由新的 integration 添加或者根本没有被预料到它就是不可见的。引用它的查询会失败。传统修复方式是更新 mapping然后重新索引数据。对于一个多 TB 的索引这意味着数小时的重新处理以及在 reindex 窗口期间双倍的存储占用。ES|QL 的读取时模式schema-on-read改变了这一契约。mapping 不再是一个悬崖边缘 —— “已映射” 意味着可查询而 “未映射” 意味着不可见。相反它变成了一个光谱已映射字段→ 快速路径。查询命中已索引结构。这仍然是生产工作负载的首选模式。使用 SET unmapped_fieldsload 的未映射字段→ 在查询时从 _source 中可查询。比已索引路径更慢没有倒排索引来加速过滤但数据可以在几秒内访问而不是完全无法访问立即生效、可追溯生效并且适用于历史索引。你可以先发现并查询然后再决定一个字段是否值得为了性能而进行 mapping。未映射字段访问战略性抽象SET 指令是查询级别的设置出现在 ES|QL 查询顶部、FROM 子句之前。它们用于配置查询引擎在该特定查询中的行为而不会影响其他任何内容。unmapped_fields 指令控制当查询引用一个不存在于 mapping 中的字段时会发生什么。考虑一个 OTel logs 索引其中 resource.cost_center 从未被映射。如果没有 SET unmapped_fields引用它会产生错误-- This fails: Unknown column [resource.cost_center] FROM otel-logs-* | STATS errors COUNT(*) BY service.name, resource.cost_center添加一行后查询即可工作SET unmapped_fieldsload; FROM otel-logs-* | WHERE log.level IN (error, warn) | STATS errors COUNT(*), latest MAX(timestamp) BY service.name, resource.cost_center | SORT errors DESC该字段会在查询时从 _source 中加载。无需修改 mapping。无需重新索引。可作用于数周或数月前摄取的数据。模式行为使用场景default未映射字段不可见引用它们的查询会报错 9.4 之前的行为。你希望严格的 schema 强制执行。nullify未映射字段会作为值为null的列出现。你希望保留列结构而不加载数据或者你需要与 subqueries 和 views 兼容。load未映射字段会在查询时从_source中加载。你需要过滤、聚合或检查实际值。当你希望即使 wildcard pattern 中的某些索引没有映射特定字段查询仍然能够成功时nullify 会很有用。而当你真正需要这些数据时load 才是你应该使用的模式。部分映射字段unmapped_fields 的强大之处在于 wildcard index patterns通配索引模式其中某个字段在部分索引中存在映射但在其他索引中不存在。假设 resource.team 在较新的索引中已被映射但在较旧的索引中没有SET unmapped_fieldsload; FROM otel-logs-* | WHERE log.level IN (error, warn) | STATS errors COUNT(*), latest MAX(timestamp) BY service.name, resource.team | SORT errors DESC对于已映射的索引值来自快速的索引路径。对于未映射的索引值从 _source 中加载。查询会在整个时间范围内返回统一的结果无需对历史数据进行重新索引。JSON_EXTRACT更底层的工具SET unmapped_fields 是针对那些在 _source 中以可预测名称存在字段的正确方案。但有些数据需要更精细的提取例如从存储在 text 字段中的 JSON 字符串中读取内容或者在 flattened 字段类型中导航嵌套对象此时点号语法无法访问某些子键。JSON_EXTRACT 是更底层的 “逃生舱口escape hatch”。它接收一个 source 字段和一个 JSON path 表达式并返回该路径对应的值从字符串字段中提取支付服务通常会将结构化错误详情以 JSON 字符串的形式存储在 response body 字段中。JSON_EXTRACT 可以在查询时直接从该字符串中提取内容FROM svc-payments-* | WHERE transaction.status IN (failed, timeout) | EVAL error_code JSON_EXTRACT(response_body, $.error_code), reason JSON_EXTRACT(response_body, $.reason), retryable JSON_EXTRACT(response_body, $.retry) | STATS failure_count COUNT(*) BY error_code, reason, retryable | SORT failure_count DESC无需 ingest pipeline 即可从 response body 中提取 error_code。schema 可以演进而无需重新索引。从 _source 中提取flattened 字段与 OTel 数据对于被映射为 flattened 的字段例如许多 OTel 索引中的 resource.attributes它可能包含每个文档 20–30 个嵌套键整个 JSON 对象会作为一个不透明的 token 存储。Elasticsearch 会将叶子值索引为扁平的 keyword 用于过滤但不会将其拆解为单独的已映射字段因此点号语法无法解析到对应的子结构。JSON_EXTRACT 在 _source 上运行时会按照存储文档中的真实嵌套结构进行解析FROM svc-auth-* METADATA _source | EVAL svc_version JSON_EXTRACT(_source, $.resource.attributes[service.version]), env JSON_EXTRACT(_source, $.resource.attributes[deployment.environment]), host JSON_EXTRACT(_source, $.resource.attributes[host.name]) | STATS login_failures COUNT(*) BY svc_version, env, host | SORT login_failures DESC对嵌套结构使用点号表示法而对包含点号的叶子键使用方括号表示法例如将 service.version 当作一个整体键名。这是 OpenTelemetry 数据中的常见模式。什么时候用什么场景工具原因字段从未被映射SET unmapped_fieldsload面向 schema 演进的战略方案。一行指令可追溯历史数据。需要从原始 JSON 字符串中提取某个 keyJSON_EXTRACT对字符串字段进行精确提取。path 表达式提供细粒度控制。字段位于 flattened 类型内部在_source上使用JSON_EXTRACT桥接方案。ES|QL 中对 flattened 字段的原生支持已在规划中。查询需要兼容 subqueries 或 viewsSET unmapped_fieldsnullify当前版本中load模式与 subqueries 和 views 不兼容nullify可以正常工作。SET unmapped_fields是大多数用户应首先采用的抽象。JSON_EXTRACT则用于需要直接进行 JSON 操作的场景或像 flattened 这类当前尚未被原生完整处理的数据模式。对比一下“schema on read读取时模式” 是 Splunk 的创始叙事将所有数据以原始文本索引并在查询时决定 schema。ES|QL 采取了一个根本不同的立场两者都给你。下面是它在实践中的表现。Splunk SPL使用 spath、rex、search-time field extractions 以及 calculated fields 等 search commands 在查询时提取字段。其优势是灵活你永远不需要预先声明 schema。代价是字段值查询会扫描原始事件数据。Splunk 的 TSIDX 文件只索引元数据host、source、sourcetype和 index-time fields但用户定义的 search-time fields 会在每次查询时命中 raw events。一个涉及十亿 events 的查询就会扫描十亿 events。Splunk 通过 summary indexing 和 data models 进行补偿但这些是手动、预配置的加速机制需要在灵活性和性能之间再次做权衡本质上是 Elasticsearch mappings 所做权衡的另一种形式只是步骤更多。ES|QL 的 SET unmapped_fields 提供了同样 “查询你从未声明过的字段” 的灵活性但已映射字段仍然可以命中 Elasticsearch 的索引结构并以全速运行。只有未映射字段才会承担 _source 扫描成本而不是查询中的每一个字段。ElasticsearchQuery DSL 有 runtime fieldsOpenSearch有 derived fields —— 两者都可以在查询时从 _source 提取数据而无需重新索引。但它们都需要按字段配置runtime fields 需要为每个字段编写 Painless script 并声明类型可以在 index mapping 层或每个查询中定义derived fields 需要 index-level 或 cluster-level 配置。你必须在查询之前知道字段名并定义提取逻辑。SET unmapped_fieldsload 是一个 per-query 指令绕过了这一切一行即可让索引中所有未映射字段变得可查询。无需 per-field 定义无需修改 index settings无需脚本。ClickHouse需要严格 schema 来定义标准 columns新增字段意味着 ALTER TABLE。然而 ClickHouse 的 JSON 类型在 25.3 GA会在写入时自动为遇到的每个路径创建 typed dynamic subcolumns无需逐字段声明。限制在于历史数据访问已经写入 String column 的数据必须使用 JSONExtract* 函数访问字段类似 ES|QL 的 JSON_EXTRACT并且无法在不改变数据 pipeline 的情况下 retroactively 迁移到 JSON column。也没有类似 SET unmapped_fields 的机制可以在不触碰 schema 或不重新 ingest 的情况下让任意历史字段变得可查询。CapabilitySplunk SPLES|QLOpenSearch / Query DSLClickHouseSchema model读取时模式在搜索时提取字段两者兼具索引快速路径 _source回退写入时模式 按字段查询时运行时提取写入时模式JSON 类型自动创建子列Unmapped field access始终可用所有字段在搜索时都会被提取SET unmapped_fieldsload按查询配置零配置runtime fields / derived fields需要逐字段配置JSON 类型自动String 列仅支持JSONExtract*JSON extractionspathJSON_EXTRACTJSONPath 子集Painless 脚本JSONExtract*函数Performance on mapped fields全量扫描没有索引结构倒排索引 doc values 列式结构倒排索引 doc values列式存储 主索引Retroactive access to old data可以始终基于原始事件数据可以通过_source可以runtime / derived fields需要逐字段配置不可以String 列无法回溯迁移为 JSONCost of flexibility每个查询都承担扫描成本仅未映射字段承担_source扫描成本每个字段需要 Painless 脚本或 index-level 配置必须在建表时选择 JSON 类型关键区别Splunk 在平台层面让你在灵活性和性能之间做选择ES|QL 则允许你在字段和查询粒度上做选择。已映射字段是高速路径未映射字段是可访问路径 —— 你不必为整个数据集选择单一模型。当前限制在当前版本中SET unmapped_fieldsload与 subqueries 和 views 不兼容在构建查询时应使用nullify模式替代。详见 SET 文档。下一步发展schema-on-read 不只是两个功能而是一种策略。未来的方向是在不要求 ingest 阶段具备完美 schema 的情况下让更多数据在查询时变得可查询。原生 flattened 字段支持下一步允许直接使用点号语法访问 flattened 字段而无需通过_source上的JSON_EXTRACT作为绕行方案。这将消除当前用户最常使用JSON_EXTRACT的主要原因之一并且已在规划中。解除 load 模式对 subqueries 和 views 的限制这将允许 schema-on-read 与 views 和 subqueries 等查询组合能力一起使用相关内容见 views 与 subqueries 的文章。长期目标SET unmapped_fields将成为用户处理 schema 演进的主要方式而JSON_EXTRACT仅用于真正精细的 JSON 操作场景。试用方式未映射字段访问与JSON_EXTRACT当前均作为Tech Preview 功能提供。你可以在 Kibana Dev Tools 或 Discover 中试用。欢迎反馈体验也可以在 GitHub 提交带有ES|QL 标签的问题。ES|QL 未映射字段访问与JSON_EXTRACT目前属于 Tech Preview 功能。Tech Preview 功能可能会发生变化并且不受 GA 功能的支持 SLA 覆盖。本文章中所描述的任何功能或特性的发布与时间安排均由 Elastic 全权决定。任何当前尚未可用的功能或特性可能无法按时交付甚至可能不会交付。原文https://www.elastic.co/search-labs/blog/esql-unmapped-fields