ES索引优化教程:从分片设置到查询提速的实操步骤
先判断你的索引到底卡在哪
ES 索引优化不是把所有参数都调一遍,而是先定位瓶颈。
常见现象有三类:写入慢、查询慢、磁盘占用高。
对应的排查方向完全不同。
写入慢通常和分片数、刷新间隔、副本数有关;
查询慢要看 Mapping 字段类型、是否用到了 filter、有没有深分页;
磁盘占用高则多半是段文件太多,需要合并。
在动手前,先拿到当前索引状态。
下面命令在 Kibana Dev Tools 或 curl 里都能执行:
GET _cat/indices?v&h=index,health,pri,rep,docs.count,store.size
GET my_index/_settings
GET my_index/_mapping
重点看 pri(主分片数)、rep(副本数)、store.size(占用空间)。
如果单分片超过 50GB,或者分片总数过多导致集群 cluster.routing.allocation 频繁触发,就要考虑重建索引。
注意:索引一旦创建,主分片数无法直接修改,只能通过 _reindex 重建。副本数可以随时调整。
分片和副本怎么设才合理
分片是 ES 把数据切分存储的最小单位,分片越多并行度越高,但元数据和协调开销也越大。
零基础用户记住一个经验值:单个分片控制在 10GB 到 50GB 之间,分片总数尽量不超过节点数的 20 倍。
假设你有 3 个数据节点,每天产生 30GB 日志,保留 7 天,总数据约 210GB。
可以按每天一个索引,每个索引设 1 个主分片、1 个副本,这样每天 1 主 1 副,总主分片数 7 个,分布到 3 个节点比较均衡。
创建索引时显式指定:
PUT /logs-2025-01-01
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 1,
"refresh_interval": "30s"
}
}
如果已经建好索引想改副本数:
PUT /logs-2025-01-01/_settings
{
"number_of_replicas": 0
}
把副本设为 0 可以临时加快写入,但会失去高可用,生产环境不建议长期关闭。
写入高峰期过后再调回 1。
Mapping 和刷新间隔是查询提速的关键
Mapping 决定字段怎么被索引。
默认情况下 ES 会对字符串做全文分词,如果你只是用来精确匹配的订单号、用户 ID,应该用 keyword 类型,避免不必要的分词和倒排索引膨胀。
一个常见的优化写法:
PUT /orders
{
"mappings": {
"properties": {
"order_id": { "type": "keyword" },
"status": { "type": "keyword" },
"amount": { "type": "scaled_float", "scaling_factor": 100 },
"created_at": { "type": "date" },
"remark": { "type": "text" }
}
}
}
scaled_float 比 double 更省空间,适合金额类字段。keyword 字段不会分词,做 term 查询和聚合更快。
刷新间隔 refresh_interval 控制数据写入后多久能被搜索到。
默认 1 秒,意味着每秒生成一个新段,写入压力大。
对日志类、实时性要求不高的索引,可以调成 30s 甚至 60s:
PUT /logs-2025-01-01/_settings
{
"refresh_interval": "30s"
}
写入完成后如果要立即搜索,手动执行一次刷新:
POST /logs-2025-01-01/_refresh
段合并与查询语句检查
ES 每次刷新都会产生一个段文件,段越多查询越慢。force merge 可以把多个段合并成更少的大段,但会消耗大量 IO,建议在低峰期对只读索引执行:
POST /logs-2025-01-01/_forcemerge?max_num_segments=1
只读的历史索引合并收益最大,正在写入的索引不要频繁合并。
查询侧同样有优化空间。
能用 filter 就不用 must,因为 filter 不计算相关性得分,还能利用缓存:
GET /orders/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "status": "paid" } },
{ "range": { "created_at": { "gte": "2025-01-01" } } }
]
}
}
}
避免 from + size 超过 10000 的深分页,改用 search_after。
查询时只取需要的字段,加 _source 过滤:
GET /orders/_search
{
"_source": ["order_id", "amount"],
"query": { "term": { "status": "paid" } }
}
改完怎么验证有没有效果
优化不是改完就算,要用数据对比。
执行下面命令观察查询耗时:
GET /orders/_search
{
"profile": true,
"query": { "term": { "status": "paid" } }
}
profile 会输出每个阶段的耗时,能看出是查询重写、倒排索引扫描还是取回字段慢。
也可以对比优化前后的 took 字段。
集群层面用 _cat/indices 看段数量和存储大小变化:
GET _cat/indices/orders?v&h=index,docs.count,store.size,pri,rep
如果合并后 store.size 明显下降,说明段文件被有效整理。
查询 took 从几百毫秒降到几十毫秒,说明 filter 和字段裁剪起了作用。
容易踩的坑
第一,不要一上来就把主分片设成几十个。
分片过多会导致集群状态更新变慢,甚至出现 cluster_block_exception。
第二,refresh_interval 改成 -1 会关闭自动刷新,数据写入后搜不到,必须手动 _refresh,容易误判为数据丢失。
第三,force merge 对正在写入的索引执行会阻塞写入,生产环境务必确认索引已转为只读。
第四,修改 Mapping 字段类型不能直接改,需要新建索引再 _reindex。
操作前先用 _reindex 的 dest 指向新索引,确认数据量一致后再切换别名。
第五,副本数设为 0 期间如果节点故障,数据可能丢失,只建议在可重建的数据上临时使用。
常见疑问
优化后需要重启 ES 吗? 大多数设置如 refresh_interval、number_of_replicas 都是动态的,不需要重启。
分片数变更需要重建索引,也不涉及重启集群。
小集群只有一台机器,副本数怎么设? 单节点集群副本无法分配,建议设 number_of_replicas: 0,否则集群健康会一直是 yellow。
日志索引每天一个还是按月一个? 数据量小可以按月,单月超过 50GB 建议按天,配合 ILM 生命周期策略自动删除旧索引。
query 和 filter 到底差在哪? query 会计算相关性得分并排序,filter 只判断是否匹配,结果可缓存。
做条件筛选优先用 filter,做全文搜索才用 query。
ES 索引优化的核心是让分片大小合理、Mapping 类型准确、刷新不过于频繁、段文件不过多。
先按本文命令检查现状,再针对瓶颈逐项调整,每次只改一个变量并记录前后指标,这样更容易判断哪一步真正有效。