深入 Kibana 15 - 性能模型:bundle、bootstrap 与异步搜索会话
插件数量增长会直接推高前端包体和启动成本,长查询又要求结果能够在页面离开后继续保存。Kibana 的性能治理因此同时覆盖 bundle 拆分、bootstrap 顺序和后台搜索。这里的“搜索会话”在 8.15 起已经进入弃用路径,9.0 默认关闭,9.2 之后由 background search 接棒。本篇把首屏加载与后台查询放在一张性能模型里讨论。
前端 bundle 体系
Kibana 的前端代码量巨大。Discover、Lens、Dashboard、Maps、Security、Observability 加在一起,编译后的 JavaScript 体积以百 MB 计。如果把所有代码打成一个 bundle 让用户一次性下载,首屏时间会无法接受。
Kibana 的解法是三层 bundle 结构。
1 | |
bootstrap.js 是 Kibana server 动态生成的,它携带当前用户能访问的插件列表(受 Spaces 和许可证过滤)以及每个插件 bundle 的哈希路径。这意味着两个用户登录同一个 Kibana,拿到的 bootstrap.js 内容可能不同。
core bundle 包含所有插件都会用到的平台服务,在 bootstrap.js 之后立即加载。从 Kibana 8 起,core bundle 经过积极的 tree-shaking,把不被任何插件用到的服务剔出去。
app plugin bundle 使用 Webpack code splitting 生成。当用户在 SPA 里导航到某个应用(比如从 Home 点进 Discover),Kibana 的 ApplicationService 才触发对应 plugin bundle 的动态 import。在此之前,这个插件的代码不在内存里。
现在 bundle 的治理也不只是“拆出去就完了”。@kbn/optimizer 负责产出可分发的插件产物,而 CI 还会按插件的 page load bundle size 设上限;超限时,limits.yml 才是要动的地方。换句话说,首屏 bundle 是发布合同,不是编译后的参考值。
bootstrap 阶段的详细序列
用浏览器 DevTools 的 Network 面板看一次完整的 Kibana 首次加载,可以观察到以下顺序。
1 | |
步骤 5 的 capabilities 请求很关键:Kibana 在渲染导航菜单之前需要知道当前用户在当前 Space 里能访问哪些应用,这个请求的耗时直接影响用户看到主导航的时间。
共享依赖(React、ReactDOM、RxJS、lodash、EUI)被提取到 kbn-ui-shared-deps bundle 里,所有插件共享同一份,不重复打包。这是控制总体积的关键措施:否则每个插件各自打包一份 React,体积会翻数倍。
Node.js server 的内存模型
Kibana server 是一个 Node.js 进程,默认不做多进程/多线程(Worker Threads 在部分场景下使用,如 reporting)。
1 | |
单进程模型意味着 CPU 密集型任务(如大报表生成)会阻塞事件循环。Kibana 为此把 Reporting 和部分 ML 任务移到了独立的后台处理路径。
关键配置项:
server.maxPayload:默认 1048576 bytes(1 MB),控制请求体上限。Dashboard 导入大型 ndjson 文件时经常需要调大。elasticsearch.requestTimeout:默认 30000 ms。超过这个时间 Kibana server 会主动放弃等待 ES 响应。node.options:可以通过--max-old-space-size调整 V8 heap 上限,默认约 1.4 GB。
Search Session / Background Search:长查询的后台运行
Kibana 的 Dashboard 经常包含几十个 panel,每个 panel 是一条独立的 ES 查询。在数据量大或集群负载高时,完整刷新一个 Dashboard 可能需要数分钟。如果用户在查询完成前关掉了浏览器标签,所有进行中的查询就浪费了。
Search Session(9.2 之后改称 background search)是解这个问题的机制。它在 8.15 起被标记为 deprecated;9.0 默认关闭,升级到 9.2 以后,旧的 search sessions 会自动并入 background searches。
1 | |
Search 结果的复用主要取决于后台搜索的有效期和缓存状态:同一批查询可以在稍后重新打开,但一旦过期,就只能重新发起。它不是内存态的“页面缓存”,而是带 TTL 的异步任务结果。
Search Session 的 Saved Object 本身有过期时间,由 data.search.sessions.defaultExpiration 配置(默认 7 天)。Task Manager 会定时清理过期结果;在 9.2 之后,这个设置名继续沿用,但 feature 名称已经换成 background search。
实验:观察 bundle 加载序列和 Background Search
第一个实验:用 Chrome DevTools 观察 bundle 加载。
打开一个空的 Kibana 实例(清空浏览器缓存),打开 DevTools Network 面板,勾选 Disable cache,然后访问 Kibana 主页。
过滤 .js 请求,观察:
bootstrap.js是不是最先出现core.chunk.js和kbn-ui-shared-deps-npm.js的体积- 点击进入 Discover 时,是否出现一个新的
discover.chunk.js请求
用 DevTools 的 Coverage 工具(More tools → Coverage)可以看到各 bundle 文件的实际执行覆盖率,未使用代码比例通常在 60-80%,说明 tree-shaking 还有很大空间。
第二个实验:创建和恢复 Background Search。
在 Kibana 打开一个含多个 panel 的 Dashboard,使用 “Send to background” 把当前查询推入后台。9.2 之后可以从 Discover 或 Dashboards 的应用菜单进入 Background searches;自管理部署若未显示入口,先检查 data.search.sessions.enabled。该能力列在 Elastic 免费 Basic 订阅中,不要求 Platinum 许可证。
关掉标签,过一分钟后在 Discover / Dashboards 的 background searches 列表里找到刚才的会话,点击 View,观察 Dashboard 是否无需等待就渲染完成。
用 Kibana 的 Dev Tools 直接检查 session 状态:
1 | |
模式提炼
三层 bundle(bootstrap + core + plugin)是大型 SPA 的标准分层:最小 shell 先到,平台依赖次之,业务功能按需到。这个模式在 React 生态里对应 React.lazy + Suspense 的拆分策略。
共享依赖提取(kbn-ui-shared-deps)是多插件架构下控制体积的必要措施。没有这一层,每个插件各自打包 React 和 EUI,总体积会是现在的数倍。
background search 的设计把"查询结果"建模成 Saved Object,使结果具备了持久化、可恢复、带过期时间的属性。这比在内存里缓存查询结果更可靠,也更容易在多实例 Kibana 集群里统一管理。
工程迁移表
| 问题 | Kibana 解法 | 迁移到其他系统的等价模式 |
|---|---|---|
| 大型 SPA 首屏慢 | 三层 bundle + lazy loading | Route-based code splitting + shared vendor chunk |
| 各插件重复打包依赖 | kbn-ui-shared-deps 共享层 | Module Federation 或 externals 提取 |
| 长查询用户等不住 | Async Search + background search | 提交任务 → 返回 job ID → 轮询 → 结果持久化 |
| 查询结果无法跨会话复用 | background search + Task Manager | 结果持久化到 DB,带 TTL,后台任务清理 |
| 内存堆上限不够 | node.options max-old-space-size | JVM -Xmx / Go GOMEMLIMIT 等效调整 |
常见误解
增加 Kibana 实例数就能线性提升查询性能。不能。Kibana 实例是无状态的请求代理,查询性能的瓶颈在 Elasticsearch 集群,多开 Kibana 实例不会提升 ES 的处理能力。
background search 只对慢查询有用。不对。background search 对任何需要"稍后回来看结果"或"把查询状态留到下一次继续查看"的场景都有价值,即使单条查询只要几秒。
elasticsearch.requestTimeout 控制 ES 集群内部查询的超时。不对。这个参数控制的是 Kibana server 等待 ES HTTP 响应的时间,不是 ES 内部执行查询的时间。ES 内部的查询超时由 ES 侧的 search.default_search_timeout 和 cancel_after_time_interval 控制。
练习
-
在本地 Kibana 开发环境里运行
yarn build --no-optimizer和yarn build,再对比产物目录、metrics.json和page load bundle size的变化,理解limits.yml是怎么把 bundle 体积变成约束的。 -
用
GET /api/ui_counters查看 Kibana 内置的前端性能指标上报,再对照page load bundle size的 CI 限额,理解 bundle 体积为什么会直接变成发布约束。 -
在 Dev Tools 里用
POST /api/data_enhanced/search直接提交一条慢查询(给一个大索引做 expensive aggregation),记录返回的id,然后用GET /api/data_enhanced/search/{id}轮询结果,观察isRunning字段从true变为false的过程。
