深入 Kibana 03 - Data View:查询之前的字段抽象
Elasticsearch mapping 只描述字段在索引中的物理类型,Kibana 还需要知道默认时间字段、显示格式和运行时扩展。Data View 位于这两层之间,把一组索引转换成可供 Discover、Lens 等应用消费的字段语义模型。它远不止一个索引名称配置项。
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/data_views 为主,/api/index_patterns 主要保留给兼容路径。
时间字段绑定
每个 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 的早期实现)现在已经是历史包袱。9.0 起,Data Views 管理页不再允许新建 scripted fields;已有 scripted fields 仍可编辑或删除,但官方推荐迁移到运行时字段或直接改写为 ES|QL 查询。两者的本质差异:Scripted fields 走的是 Kibana 自己封装的脚本层,运行时字段直接走 ES 原生的 runtime_mappings API,后者的行为更可预测,也可以在 ES 侧直接测试。
field_caps API 与字段刷新
Data View 里的字段列表不是静态配置,而是通过 ES 的 field_caps API 动态解析的。field_caps 返回匹配 title pattern 的所有索引里每个字段的类型信息,Kibana 把结果缓存起来用于字段列表显示和 KQL 自动补全。
索引 mapping 变化后,Data View 的缓存字段列表不一定立即更新。需要马上使用新字段时,可以在 Data View 管理页刷新字段列表,或强制重新加载;不能假设 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 文本的一步。
运行时字段在查询时执行脚本,通常比 indexed 字段更慢;高命中量的聚合尤其容易放大这段开销。它不会通过某个开关直接“升级”为 indexed 字段。要把计算结果改成索引期字段,需要修改 mapping 或写入流程,再 reindex 既有数据。Synthetic _source 解决的是 _source 的存储方式,和运行时字段是否建立索引是两条独立机制。
删除 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,对比两次结果的一致性。
