深入 Kibana 03 - Data View:查询之前的字段抽象
上一篇确立了 Saved Object 作为 Kibana 一切状态的统一持久化模型,type、attributes、references 三段结构解决了跨插件的状态管理问题。这一篇进入第一种具体且重要的 Saved Object 类型:Data View。Data View 容易被误解成"告诉 Kibana 去查哪个索引的配置项"。更准确的说法是:Data View 是一层字段语义抽象,把 ES 里裸的 mapping 信息转换为 Kibana 可以推理的字段模型——带时间轴绑定、带显示格式、带运行时扩展能力。本文只抓一个问题:时间字段绑定、字段格式化、运行时字段三个机制解决了什么,Data View 与直接查索引的本质区别在哪里。
Data View 在链路里的位置
1 | |
直接对 ES 执行 _search 时,调用方要自行处理字段类型推断、时间范围注入、字段显示格式。Data View 把这些关注点提升到一个可复用的抽象层,所有使用同一 Data View 的 Discover 页、Lens 面板、Dashboard 变量共享同一套字段定义,修改一处全局生效。
命名沿革
自 Kibana 7.x 起该对象的 UI 名称是"Index Pattern",Saved Object 的 type 字段为 index-pattern。自 Kibana 8.0 起 UI 名称改为"Data View",但底层 Saved Object 的 type 字段仍保持 index-pattern 以保证向后兼容,API 路径也同时接受 /api/index_patterns 和 /api/data_views 两个前缀(8.x 主流版本推荐使用 /api/data_views)。
时间字段绑定
每个 Data View 有一个可选的 timeFieldName 属性,指向索引里一个 date 类型字段(通常是 @timestamp)。这个绑定有三个用途:
Discover 的时间轴 histogram 依赖它做 date_histogram 聚合。没有时间字段的 Data View 在 Discover 里不显示时间轴,搜索结果按文档顺序排列而非时间顺序。
时间选择器(time picker)把用户选定的时间范围注入为 ES 查询里的 range filter,filter 的字段名就是 timeFieldName。Kibana 在构造 _search 请求时自动在 query.bool.filter 里追加这个 range 条件,调用方不需要手动拼。
数据可视化和告警规则的"过去 N 分钟"相对时间表达式(now-15m)也通过时间字段解析。告警规则执行时把 now 替换为当前服务器时间,再传给 ES 的 range query。
没有时间字段不代表 Data View 不可用,只是丧失了时间轴和时间过滤能力——适用于静态参考数据(geo lookup 表、配置表)。
字段格式化
ES 存储的 bytes 字段是一个 long,原始值是 1258291。Data View 的字段格式层可以给这个字段配置一个 bytes 格式化器,Discover 里显示为 1.2 MB,用户不需要手写 Painless 脚本。
字段格式化配置存在 Data View 的 attributes 里:
1 | |
格式化在 Kibana 渲染层发生,不影响 ES 里的原始数据,也不影响查询结果的实际值。_search 返回的 bytes 仍然是 1258291,格式化是 Discover/Lens 在把值写入 DOM 之前做的最后一步转换。
运行时字段
运行时字段(Runtime Field)是 Elasticsearch 7.11 引入的特性,允许在查询时用 Painless 脚本临时计算一个字段的值,不需要重新索引。Data View 提供了在 Kibana 层定义运行时字段的界面,定义后存储在 Data View 的 runtimeFieldMap 属性里:
1 | |
Kibana 在构造 _search 请求时,把 runtimeFieldMap 里的定义注入到 ES 请求的 runtime_mappings 参数里。ES 收到请求后,在查询执行阶段为每个命中的文档临时计算这些字段的值。对 Discover 用户来说,hour_of_day 和 url_domain 出现在字段列表里,行为与普通 mapping 字段完全一致。
运行时字段适合探索阶段的快速迭代:先用运行时字段验证业务逻辑,确认有用后再把计算逻辑移到 ingest pipeline 或 index mapping 里做索引时计算,换取查询性能提升。
Scripted fields(7.x 的早期实现)已在 8.x 中标记为 deprecated,推荐迁移到运行时字段。两者的本质差异:Scripted fields 用的是 Kibana 自己封装的脚本层,运行时字段直接走 ES 原生的 runtime_mappings API,后者的行为更可预测,也可以在 ES 侧直接测试。
field_caps API 与字段刷新
Data View 里的字段列表不是静态配置,而是通过 ES 的 field_caps API 动态解析的。field_caps 返回匹配 title pattern 的所有索引里每个字段的类型信息,Kibana 把结果缓存起来用于字段列表显示和 KQL 自动补全。
当索引 mapping 变化(新增字段)时,Kibana 不会自动感知,需要手动刷新 Data View(UI 里有"Refresh field list"按钮,或通过 API 触发)。自 Kibana 8.x 起,Discover 在每次搜索时会做轻量级的 mapping 变化检测,发现新字段时会在页面上提示用户刷新。
实验:通过 API 创建 Data View 并观察字段
在 Dev Tools 里通过 Data Views API 创建一个 Data View,附带运行时字段定义:
1 | |
1 | |
1 | |
对比第 2 步和第 3 步的结果:field_caps 返回的是 ES mapping 里的物理字段,Data View API 返回的字段列表还包含 runtimeFieldMap 里定义的运行时字段(hour_of_day),以及 Data View 层配置的字段格式元数据。运行时字段不出现在 field_caps 里,因为它们不在 ES 的 mapping 里,只存在于 Data View 的 attributes 里,由 Kibana 在查询时动态注入。
接着验证运行时字段在实际查询里的行为:
1 | |
这一步复现了 Kibana 内部的行为——当 Discover 或 Lens 对包含运行时字段的 Data View 执行查询时,Kibana 就是把 runtimeFieldMap 里的定义展开成 runtime_mappings 参数,附加到 ES 请求体里。
将实验结果映射回内部对象
POST /api/data_views/data_view 的实现路径:server 端路由 → DataViewsService.createAndSave() → 调用 SavedObjectsRepository.create('index-pattern', attributes) 写入 .kibana 索引。Data View 本质上是一个 index-pattern 类型的 Saved Object,title、timeFieldName、fieldAttrs、runtimeFieldMap 等都在 attributes 里。
GET /api/data_views/data_view/<id>/fields 的实现:DataViewsService.getFieldsForIndexPattern() → 向 ES 发 field_caps 请求 → 合并 runtimeFieldMap → 合并 fieldAttrs(格式化配置)→ 返回增强后的字段列表。
模式提炼
字段语义与存储分离。ES mapping 描述字段的存储类型(long、keyword、date),Data View 描述字段在 UI 层的语义(格式化方式、标签、运行时计算逻辑)。两层解耦让 ES 侧的 mapping 变化不需要同步修改 UI 配置,UI 侧的显示调整也不需要重新索引。
运行时字段把探索成本前移。传统做法是先设计好 mapping、ingest pipeline,再索引数据。运行时字段允许先索引原始数据,后期在 Data View 里追加派生字段,把"不知道需要什么字段"的探索成本从重建索引(高)降到修改 Data View(低)。确定有用后再固化到 mapping 里,是一种渐进式的 schema 演化路径。
工程迁移表
| Kibana 概念 | Grafana 对应 | BI 语义层对应 | dbt 模型对应 |
|---|---|---|---|
| Data View title (wildcard) | DataSource query target | 语义层数据集 / dataset | dbt model source |
| timeFieldName | Time series field binding | 日期维度字段 | 事件时间戳列 |
| fieldAttrs (format) | Unit / display override in panel | 指标格式化 / display format | column meta (description) |
| runtimeFieldMap | Grafana calculated field | 计算字段 / calculated measure | dbt metrics / calculated column |
| field_caps refresh | DataSource schema refresh | BI 语义层元数据刷新 | dbt source freshness |
| Data View → Discover | DataSource → Explore panel | 数据集 → 临时分析 | dbt model → ad-hoc query |
常见误解
Data View 和 ES 索引是一对一的关系。Data View 的 title 支持通配符和逗号分隔的多个 pattern(如 logs-app-*,logs-infra-*),可以跨多个物理索引。field_caps 会对所有匹配的索引做联合 schema 查询,返回的字段类型以所有索引的 union 为准(冲突的字段类型标记为 conflict)。
修改 Data View 的字段格式会影响 ES 里的数据。字段格式化完全在 Kibana 渲染层,不写 ES,不影响查询结果的原始值。_search 返回的 bytes 字段值始终是裸数字,格式化只发生在 React 组件把值渲染为 DOM 文本的一步。
运行时字段性能与 Painless 脚本等价,比 indexed 字段慢。运行时字段在查询时对每个命中文档执行脚本,时间复杂度与命中文档数量线性相关。对高命中量的聚合查询,性能代差显著。自 ES 8.1 起支持把运行时字段"升级"为 indexed 字段(synthesize_source 相关),但在 Kibana Data View 层没有一键迁移工具,需要在 ES 侧手动更新 mapping 并 reindex。
删除 Data View 会删除依赖它的 Dashboard 和 Lens。删除 Data View 只删除那个 Saved Object,不会级联删除引用它的 Dashboard、Lens、Saved Search。这些对象的 references 里仍有指向已删除 Data View 的条目,打开时会报"Data View not found",需要手动重新关联或删除。
练习
在 Stack Management → Data Views 里找到一个已有的 Data View,点击字段列表里任意一个 long 类型字段,给它配置 Bytes 格式化器,然后在 Discover 里验证该字段的显示值发生了变化,而 Dev Tools 里直接查 ES 返回的值不变。
在 Dev Tools 里执行 GET .kibana/_doc/index-pattern:<data_view_id>,把 API 返回的 Data View attributes 与 ES 文档里的 _source.index-pattern 对比,确认字段格式化配置和运行时字段定义存储的具体字段名。
为一个 Data View 添加一个运行时字段,在 Discover 里选择该字段做一个 terms aggregation(用 Lens 的 Top values),再用 Dev Tools 把同样的查询附带 runtime_mappings 参数发给 ES,对比两次结果的一致性。
系列导航
| 篇 | 主题 | 核心问题 |
|---|---|---|
| 00 | 导读:状态存在 ES 里 | Kibana 是应用平台,不是 GUI |
| 01 | 架构:三层与 New Platform | 为什么需要 Node.js server |
| 02 | Saved Object:统一状态模型 | type / attributes / references |
| 03(本篇) | Data View:查询前的字段抽象 | 时间字段、字段格式化、运行时字段 |
