Featured image of post Elasticsearch 创建、删除索引慢:快速排查 Runbook

Elasticsearch 创建、删除索引慢:快速排查 Runbook

创建索引、删除索引、ILM rollover 和 mapping 更新同时变慢时,按 cluster state、master、shard 规模、allocation、recovery 和磁盘负载逐项排查。

Elasticsearch 创建、删除索引慢:快速排查 Runbook

PUT /<index>DELETE /<index>、ILM rollover 和 mapping 更新一起变慢时,我通常不会先看某个索引有多大。这几类操作都要修改 cluster state,也都要经过 master。先查它们共用的这段链路,往往比盯着单个索引更快。

下面是一套偏现场排障的检查顺序。先用只读命令判断问题大致落在哪一类,再继续深挖:

  • cluster state 更新积压或 master 节点压力;
  • index、shard 总量过高,也就是 oversharding;
  • shard allocation 或 recovery 未完成;
  • JVM、线程池、磁盘或网络资源拥塞;
  • 删除请求已经完成,但底层文件和磁盘空间尚未回收。

为什么这些操作会一起慢

创建索引、删除索引、更新 mapping、创建别名和 ILM rollover 都会改集群元数据。当前 master 处理请求、生成新的 cluster state,再把它发布给其他节点。元数据变大、更新过密,或者 master 的 CPU、heap、GC 顶不住,最终看到的现象都差不多:几个管理接口一起慢下来。

创建索引还可能等待 primary shard 变为 active。创建索引响应中的两个字段含义不同:

  • acknowledged 表示创建索引的 cluster-state 更新已在超时前得到确认;
  • shards_acknowledged 表示所需数量的 shard 已在超时前启动。

所以,看到 acknowledged: trueshards_acknowledged: false 时,先去查 shard allocation 和启动过程。cluster-state 更新本身已经得到确认。

删除索引也有两个时间点。DELETE API 返回成功,说明 cluster-state 里的删除变更已经得到确认;节点删文件、释放文件句柄,以及存储系统显示出新的可用空间,都可能更晚。现场先问清楚到底是哪一种慢:

  1. DELETE 请求本身很慢:查 master 和 cluster state。
  2. DELETE 很快返回,但磁盘空间迟迟不下降:查磁盘 I/O、文件句柄、节点状态和存储监控。

先拆开请求的等待阶段

如果客户端能记录请求耗时,最好同时记录响应体和请求参数。很多管理 API 都有 master_timeouttimeout:前者控制等待 master 响应的时间,后者通常控制等待 active shard 的时间;具体可用参数仍要以当前版本和具体 API 的文档为准。

这能提供一个很有用的线索:master_timeout 经常触发,先查 cluster-state 和 master;cluster-state 很快确认,但 timeoutshards_acknowledged 迟迟不满足,先查 allocation、recovery 和磁盘。客户端自己的 HTTP 超时只能说明调用方放弃等待,不能说明 Elasticsearch 已经停止处理请求。

排查前的采集原则

下面的命令都不会修改集群。_cluster/allocation/explain 虽然使用 POST,但只解释分配决策,不会执行 reroute。

最好趁慢请求还在发生时采一次,过 30~60 秒再采一遍。线程池 rejected、GC 次数、I/O 字节数有不少是累计值,一张快照看不出它们是否还在增长。顺手记下这些现场信息:

  • 请求开始时间、结束时间和客户端超时时间;
  • 请求响应体,尤其是 acknowledgedshards_acknowledged 和错误信息;
  • Elasticsearch 版本、目标索引名和操作类型;
  • 同时段是否发生部署、批量建索引、ILM rollover、恢复、快照或节点重启。

不同 Elasticsearch 版本支持的 CAT 列略有差异。遇到列名报错,直接用 ?help 查当前版本:

1
2
GET /_cat/nodes?help
GET /_cat/shards?help

第一步:确认集群状态和 cluster-state 队列

先执行以下四条命令:

1
2
3
4
GET /_cluster/health?level=cluster&timeout=30s
GET /_cluster/health?level=shards&timeout=30s
GET /_cluster/pending_tasks?local=false&master_timeout=30s
GET /_cluster/stats?filter_path=indices.count,indices.shards.*,nodes.count.*
命令重点字段如何判断
_cluster/healthstatusinitializing_shardsrelocating_shardsunassigned_shardsnumber_of_pending_taskstask_max_waiting_in_queue_millisshard 未稳定时进入 allocation/recovery 分支;pending task 数量和最长等待时间持续增加时进入 master 分支
_cluster/health?level=shards各 index、shard 的状态用于定位异常 index 和 shard,之后执行 allocation explain
_cluster/pending_taskssourceprioritytime_in_queue_milliscreate/delete/mapping/shard-started 等任务长时间排队,说明 master 处理 cluster-state 更新跟不上
_cluster/statsindex 数、primary/total shard 数、节点数index、shard 总量持续增长,管理操作也越来越慢,继续查 oversharding

_cluster/pending_tasks 只列出尚未执行的 cluster-state 更新任务,和 _tasks 不是一回事。空列表只代表采样那一刻没有任务排队。任务可能刚处理完,也可能慢在发布阶段,所以还得对照前后两次采样和请求时间。

第二步:检查 index、shard 规模与分布

1
2
3
GET /_cat/indices?v=true&h=health,status,index,pri,rep,docs.count,store.size,pri.store.size&s=pri.store.size:desc
GET /_cat/shards?v=true&h=index,shard,prirep,state,store,node&s=state,index,shard,prirep
GET /_cat/allocation?v=true&h=node,shards,disk.indices,disk.used,disk.avail,disk.percent

_cat/indices:是否存在大量小索引、小 shard

先看 prireppri.store.size。单个索引的平均 primary shard 大小可以粗略算成:

1
平均 primary shard 大小 ≈ pri.store.size / pri

有些集群数据总量并不夸张,却堆了成千上万个几十 MB、几百 MB 的小索引,每个索引还带多个 primary 和 replica。元数据、segment、文件句柄以及每个 shard 的固定开销就这样累积起来了。这正是常见的 oversharding。

_cat/shards:是否有大量 shard 正在移动或等待

关注 statenode

  • 大量 INITIALIZING:shard 正在启动或恢复;
  • 大量 RELOCATING:集群正在搬迁数据;
  • 存在 UNASSIGNED:需要用 allocation explain 查明原因;
  • shard 明显集中在少数节点:检查节点角色、allocation filter 和磁盘水位。

_cat/allocation:节点是否接近磁盘水位线

对比各节点的 shard 数、disk.availdisk.percent。磁盘接近 low、high 或 flood-stage watermark 时,Elasticsearch 会限制分配,严重时还可能对索引施加只读保护。磁盘使用不均也可能让少数节点先触发限制,从而拖慢整个集群的 shard 稳定过程。

“每个节点最多放多少 shard”没有一个放之四海皆准的数字。节点规格、heap、数据模型、segment 数、写入模式和 Elasticsearch 版本都会影响结果。下面这组现象同时出现时,判断才比较站得住:

1
2
3
4
index/shard 总量持续上升
    + cluster-state 任务等待时间上升
    + master CPU、heap 或 GC 恶化
    + 创建、删除、rollover、mapping 更新同步变慢

第三步:检查 master、JVM 和运行中任务

1
2
3
4
GET /_cat/nodes?v=true&h=name,master,node.role,heap.percent,ram.percent,cpu,load_1m
GET /_nodes/stats/discovery,jvm?filter_path=nodes.*.name,nodes.*.roles,nodes.*.jvm.mem,nodes.*.jvm.gc,nodes.*.discovery
GET /_tasks?detailed=true&group_by=parents&actions=cluster:*,indices:admin/*
GET /_nodes/hot_threads?threads=10&interval=500ms

先找到当前 master

_cat/nodesmaster 列里,* 通常就是当前 master。把它的 CPU、load 和 heap 与其他节点放在一起看。若只有 master 长时间高负载,pending tasks 又多是 create、delete、mapping、shard-started,基本可以沿 cluster coordination 这条线继续查。

某一刻的 heap.percent 很高,说明不了太多。把 _nodes/stats 里的 GC 次数和耗时做前后对比,再看下面几种情况是否出现:

  • old GC 频繁且耗时明显增长;
  • heap 回收后仍长期处于高位;
  • master 的 CPU 与 pending task 等待时间同步上升;
  • 版本支持时,cluster-state publish 相关统计出现明显延迟。

_tasks 补充 pending tasks

_tasks 能看到还在运行的快照、reindex 和管理任务,正好补上 _cluster/pending_tasks 看不到的部分。查询时限制 actions;大型集群上直接拉全部任务,响应本身就可能很大。

用 hot threads 看 CPU 花在哪里

如果 CPU 持续偏高,再采集 hot threads:

  • MasterService、cluster coordination 或 cluster-state publish 相关栈占比高,继续沿 master/元数据方向排查;
  • Lucene merge、IndexWriter、refresh 或 flush 相关栈占比高,继续检查写入放大和磁盘;
  • GC 相关线程频繁出现,结合 GC 日志和 heap 使用确认是否存在内存压力。

完整的 GET /_cluster/state/metadata 不适合当常规排查命令。索引多、mapping 大时,响应会很重。确实要看元数据,就限定到一个索引,再用 filter_path 裁掉无关字段。

第四步:检查 shard allocation 与 recovery

1
GET /_cat/recovery?v=true&active_only=true

_cluster/allocation/explain 需要指定一个目标 shard。不要发送没有 index 和 shard 的裸请求。定位到异常 shard 后,显式指定目标:

1
2
3
4
5
6
POST /_cluster/allocation/explain
{
  "index": "<index>",
  "shard": 0,
  "primary": true
}

_cat/recovery 会给出恢复阶段、传输字节、文件数、进度和耗时。大量 recovery 同时跑,或者进度长时间不动,就去看磁盘吞吐、网络、源节点负载和 shard 大小。

allocation explain 里每个 decider 都会写明同意或拒绝的原因。现场常见的是:

  • 节点磁盘超过 watermark;
  • index 或 cluster allocation filter 排除了可用节点;
  • data tier、节点角色或 awareness 属性不匹配;
  • 同一 shard 的副本不能与 primary 分配到同一节点;
  • 可用节点或容量不足;
  • allocation 被临时关闭或延迟。

看到 UNASSIGNED 先别急着 reroute。若 decider 拒绝分配的根因还在,强制操作只会盖住容量问题,有时还会把数据置于风险中。

第五步:检查线程池、磁盘和后台负载

1
2
GET /_cat/thread_pool?v=true&h=node_name,name,active,queue,rejected,completed
GET /_nodes/stats/jvm,fs,thread_pool,indices?filter_path=nodes.*.name,nodes.*.jvm,nodes.*.fs.io_stats,nodes.*.thread_pool,nodes.*.indices.merges,nodes.*.indices.segments,nodes.*.indices.indexing,nodes.*.indices.refresh,nodes.*.indices.flush

把两次采样放在一起比较:

  • queue 长期不下降,表示任务进入速度超过处理速度;
  • rejected 持续增长,表示对应线程池已经无法接收更多任务;
  • merge 的累计时间、字节数快速增长,可能存在显著写入或 merge 压力;
  • segment 数量异常多,可能放大 heap、文件句柄和 merge 开销;
  • refresh、flush 或 indexing 指标短时间快速增加,需要与业务流量核对。

fs.io_stats 也是累计计数,不能拿它单独证明磁盘延迟高。主机或云盘监控至少还要看:

  • CPU iowait;
  • read/write latency;
  • queue depth;
  • disk utilization;
  • 可用空间和 inode;
  • 节点间网络延迟、丢包和吞吐。

如果 DELETE 已经 ack,空间却没回来,检查 Elasticsearch 进程是否还持有已删除文件、节点是否健康,以及底层存储或容器文件系统的统计有没有延迟。这个场景继续盯 pending tasks 通常收获不大。

快速决策树

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
PUT / DELETE / ILM rollover / mapping update 都慢
|
+-- pending_tasks 多且等待时间持续增长
|   `-- Master / cluster-state
|       `-- 查 master CPU、heap/GC、index/shard 总量和元数据规模
|
+-- INITIALIZING / RELOCATING / UNASSIGNED 较多
|   `-- Allocation / recovery
|       `-- 查 recovery、allocation explain、磁盘水位和网络
|
+-- DELETE 已 ack,但空间迟迟未回收
|   `-- 磁盘 I/O、打开文件、节点状态或底层存储空间统计
|
`-- 上述均无明显异常
    `-- 查 JVM、线程池、hot threads、OS 磁盘、CPU 和同期后台任务

这棵树不是互斥选项。比如 recovery 产生的大量 shard-started 任务,既会让 pending tasks 增长,也会让 shard 长时间处于 INITIALIZING。两种信号同时出现时,先查能解释更多现象的那条线,同时保留另一条线的采样结果。

五条命令完成第一轮分流

时间紧张时,先采集下面五条:

1
2
3
4
5
GET /_cluster/health?level=cluster&timeout=30s
GET /_cluster/pending_tasks?local=false&master_timeout=30s
GET /_cluster/stats?filter_path=indices.count,indices.shards.*,nodes.count.*
GET /_cat/indices?v=true&h=index,pri,rep,store.size,pri.store.size
GET /_cat/allocation?v=true&h=node,shards,disk.used,disk.avail,disk.percent

这五条跑完,第一轮方向通常就有了:

观察结果优先方向
pending tasks 等待长,index/shard 总量高cluster-state、master、oversharding
initializing、relocating、unassigned shard 多allocation、recovery
少数节点磁盘高、shard 分布不均disk watermark、allocation rule、节点容量
上述基本正常JVM、线程池、hot threads、OS 与同期任务

几个容易走偏的地方

先入为主地怪罪大索引

新建空索引时还没有业务数据。多个管理操作一起慢,先看它们共用的 cluster-state 和 master 路径。大 shard 确实麻烦,但影响更常出现在 recovery、relocation、merge 和节点重启恢复时。

把一次空的 pending tasks 当作结论

pending tasks 是瞬时视图。任务可能刚处理完,也可能卡在 cluster-state 发布或节点确认阶段。要把前后两次采样与 master 资源、真实请求耗时对上,才能判断。

认为 DELETE 成功后磁盘必须立刻下降

API 确认和物理空间可见是两个时间点。文件句柄、底层文件系统、存储平台统计和节点 I/O 都可能影响空间回收的可见时间。

一看到 shard 多就改集群配置

删除索引、降低副本、关闭 allocation、强制 reroute 都会影响业务。先确认根因,再做容量评估、数据保留确认和变更审批。

查到原因之后

方向确认后,可以暂停非必要的批量建索引,把 rollover 或 recovery 错开,必要时压低同期写入峰值。这些动作能争取处理时间,不能代替后续治理。

后续可以从这些地方下手:

  • 合并过细的时间索引,按真实数据量设计 rollover 条件;
  • 用 index template 统一合理的 primary 和 replica 数;
  • 定期统计 index、shard、segment 和 mapping 字段数量的增长趋势;
  • 为 master-eligible 节点保留稳定的 CPU、heap 和网络资源;
  • 为 cluster operation latency、pending task 最大等待时间、unassigned shard 和磁盘水位建立告警;
  • 在扩容或版本升级前,用真实 mapping、shard 数和变更频率进行容量验证。

我实际使用的排查顺序

现场排查时,我会按这条顺序走:

  1. 用 cluster health 和 pending tasks 判断 cluster-state 是否积压;
  2. 用 cluster stats、indices 和 shards 判断是否 oversharding;
  3. 用 master JVM、tasks 和 hot threads 确认协调节点压力;
  4. 用 recovery 和 allocation explain 定位 shard 无法稳定的原因;
  5. 用双时点线程池、I/O 与主机监控判断底层资源瓶颈。

shard 数量、某一刻的 heap,或者单条 API 的 ack,都不能独立证明根因。先把前后两次采样对起来,确定问题落在 cluster-state/master、oversharding、allocation/recovery 还是 disk/OS,再动配置。

记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计