Elasticsearch mapping 只描述字段在索引中的物理类型,Kibana 还需要知道默认时间字段、显示格式和运行时扩展。Data View 位于这两层之间,把一组索引转换成可供 Discover、Lens 等应用消费的字段语义模型。它远不止一个索引名称配置项。

Data View 在链路里的位置

1
2
3
4
5
6
7
8
9
ES 物理索引                Data View (SO)                 Kibana UI
────────────────── ────────────────────────── ──────────────────────
logs-app-2026.08.*title: "logs-app-*" → Discover 字段列表
logs-app-2026.07.* timeField: "@timestamp" KQL 自动补全
metrics-host-* fieldFormats: 时间轴 histogram
bytes → human readable Lens 字段选择器
runtimeFields: Dashboard 变量
hour_of_day: painless
(resolved via field_caps API)

直接对 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
2
3
4
5
6
7
8
9
10
{
"fieldAttrs": {
"bytes": {
"format": { "id": "bytes", "params": { "pattern": "0.0b" } }
},
"url.original": {
"format": { "id": "url", "params": { "urlTemplate": "{{value}}", "labelTemplate": "{{value}}" } }
}
}
}

格式化在 Kibana 渲染层发生,不影响 ES 里的原始数据,也不影响查询结果的实际值。_search 返回的 bytes 仍然是 1258291,格式化是 Discover/Lens 在把值写入 DOM 之前做的最后一步转换。

运行时字段

运行时字段(Runtime Field)是 Elasticsearch 7.11 引入的特性,允许在查询时用 Painless 脚本临时计算一个字段的值,不需要重新索引。Data View 提供了在 Kibana 层定义运行时字段的界面,定义后存储在 Data View 的 runtimeFieldMap 属性里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"runtimeFieldMap": {
"hour_of_day": {
"type": "long",
"script": {
"source": "emit(doc['@timestamp'].value.getHour())"
}
},
"url_domain": {
"type": "keyword",
"script": {
"source": "def u = doc['url.original'].value; int i = u.indexOf('/', 8); emit(i > 0 ? u.substring(0, i) : u);"
}
}
}
}

Kibana 在构造 _search 请求时,把 runtimeFieldMap 里的定义注入到 ES 请求的 runtime_mappings 参数里。ES 收到请求后,在查询执行阶段为每个命中的文档临时计算这些字段的值。对 Discover 用户来说,hour_of_dayurl_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 创建 Data View(替换 logs-* 为实际存在的索引 pattern)
POST kbn:/api/data_views/data_view
{
"data_view": {
"title": "logs-*",
"timeFieldName": "@timestamp",
"runtimeFieldMap": {
"hour_of_day": {
"type": "long",
"script": { "source": "emit(doc['@timestamp'].value.getHour())" }
}
}
}
}
# 返回包含新 Data View 的 id
1
2
# 2. 查询这个 Data View 下所有字段(包括运行时字段)
GET kbn:/api/data_views/data_view/<id>/fields
1
2
# 3. 直接对 ES 执行 field_caps,对比 Kibana 和 ES 层面看到的字段差异
GET logs-*/_field_caps?fields=*

对比第 2 步和第 3 步的结果:field_caps 返回的是 ES mapping 里的物理字段,Data View API 返回的字段列表还包含 runtimeFieldMap 里定义的运行时字段(hour_of_day),以及 Data View 层配置的字段格式元数据。运行时字段不出现在 field_caps 里,因为它们不在 ES 的 mapping 里,只存在于 Data View 的 attributes 里,由 Kibana 在查询时动态注入。

接着验证运行时字段在实际查询里的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4. 直接向 ES 发请求,注入运行时字段定义
POST logs-*/_search
{
"runtime_mappings": {
"hour_of_day": {
"type": "long",
"script": { "source": "emit(doc['@timestamp'].value.getHour())" }
}
},
"aggs": {
"by_hour": {
"terms": { "field": "hour_of_day", "size": 24 }
}
},
"size": 0
}

这一步复现了 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,titletimeFieldNamefieldAttrsruntimeFieldMap 等都在 attributes 里。

GET /api/data_views/data_view/<id>/fields 的实现:DataViewsService.getFieldsForIndexPattern() → 向 ES 发 field_caps 请求 → 合并 runtimeFieldMap → 合并 fieldAttrs(格式化配置)→ 返回增强后的字段列表。

模式提炼

字段语义与存储分离。ES mapping 描述字段的存储类型(longkeyworddate),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,对比两次结果的一致性。

系列导航

篇目 主题
00 导读:Kibana 的状态存在 Elasticsearch 里
01 架构:浏览器、Node.js server 与 New Platform
02 Saved Object:一切状态的统一模型
03 Data View:查询之前的字段抽象
04 Search Source 与查询翻译
05 Discover:交互式检索的执行模型
06 聚合式可视化:Visualize 与 bucket/metric
07 Lens:拖拽背后的自动聚合推断
08 TSVB、Timelion 与 Vega
09 Dashboard 与 Embeddable
10 Alerting:Rule、Connector 与 Action
11 Reporting:从 Dashboard 到 PDF/PNG
12 Task Manager:分布式任务调度
13 Spaces 与安全:RBAC、Feature Controls 与多租户
14 插件体系:setup/start 生命周期
15 性能模型:bundle、bootstrap 与异步搜索会话
16 Kibana vs Grafana:两种平台的设计取舍
17 演进:从纯前端到 Platform 再到 Serverless

参考资料