搜索集群的运维决策有个残酷特征:分片数在索引创建时写下,之后基本改不了。要回答的不是"怎么调",而是"一开始按什么口径定、事后用什么代价补救"。下面按分片 → 副本 → 容量 → 角色 → 索引设计 → 查询归因 → 变更窗口给出代价结构与失效边界。参数按 Elasticsearch 7.x/8.x 通用语义,默认值按所部署版本核对。
1. 分片决策的三个维度与硬约束
| 维度 | 决定什么 | 估算输入 |
|---|---|---|
| 数据量 | 分片数量下限 | 单分片目标大小 × 索引总数 |
| 查询并发 | 分片数量上限(扇出成本) | 每秒查询数、可用线程、单分片延迟 |
| 写入吞吐 | 分片数量下限(并行度) | 每秒文档数、单分片写入能力 |
PUT /logs-2026.11
{
"settings": {
"number_of_shards": 6,
"number_of_replicas": 1,
"refresh_interval": "10s"
}
}
硬约束:number_of_shards 只能靠 _shrink / _split / _reindex 改动,三者都要重写数据。定分片数要按未来一年数据量算,为当前量优化是这类集群最常见的错误。
2. 分片过多与过少的代价
| 方向 | 代价 | 表现 |
|---|---|---|
| 过多 | 堆内存 | 每分片有独立段元数据与缓冲,堆压力随分片数线性上升 |
| 过多 | 文件句柄 | 段文件 × 分片数,容易撞 ulimit/系统上限 |
| 过多 | 查询扇出 | 一次查询打到所有分片,协调节点成为瓶颈 |
| 过多 | 恢复时间 | 节点重启后分片并行恢复,集群长时间处于 yellow/red |
| 过少 | 并行度不足 | 单分片写入与查询无法横向扩展 |
| 过少 | 再平衡粒度粗 | 扩容时无法把负载均摊到新节点 |
# 用这两个接口判断现状:分片规模分布 与 单分片大小
GET _cat/shards/logs-*?v&h=index,shard,prirep,store,node
GET _cat/indices/logs-*?v&h=index,pripri.store.size,docs.count,pri
判断口径:单分片目标量级通常落在数十 GB 区间(常见经验 30–50GB,按版本与硬件核对)。若某索引分片数远超数据量所需,先合并而不是先加节点。
3. 副本:它能做什么,不能做什么
| 副本解决的问题 | 副本不解决的问题 |
|---|---|
| 读请求横向扩展 | 写入吞吐上限(写要复制到每个副本) |
| 节点故障时数据可用 | 单分片写入热点 |
| 滚动升级期间的服务连续 | 段合并与刷盘的 IO 压力 |
| 查询失败重试的其他选择 | 深分页与高基数聚合的成本 |
PUT /logs-2026.11/_settings
{ "index": { "auto_expand_replicas": "0-1" } }
auto_expand_replicas 适合节点数会变的集群,可避免"节点少副本多 → 长期 yellow";但副本数变化会触发分片迁移,IO 与网络受影响,变更放低峰。
4. 容量估算:堆、数据与合并开销
| 估算项 | 口径 | 误差来源 |
|---|---|---|
| 数据总量 | 单文档字节 × 文档数 × (1 + 副本数) | 字段映射膨胀、_source 重复存储 |
| 堆需求 | 与分片数、段数、聚合并发相关 | 大聚合与脚本查询瞬时占用 |
| 合并开销 | 写入期持续产生,与段数相关 | 批量导入时合并压力集中 |
| 预留 | 磁盘留 15%–20%,堆不超物理内存一半 | 预留被临时索引吃掉 |
分片数下限 = ceil(预期数据量 / 单分片目标大小)
分片数上限 = min(节点数 × 每节点可承载分片数, 查询并发可承受扇出)
堆与段数正相关,按实测曲线核对
不要用"堆:数据 = 1:30"这类固定比例决策,它只在特定查询形态下成立,聚合与脚本多的集群明显偏离。
5. 节点角色划分
| 角色 | 职责 | 资源特征 |
|---|---|---|
| 数据节点 | 存分片、执行查询与写入 | 磁盘与堆并重,IO 敏感 |
| 协调节点 | 扇出、合并结果 | CPU 与网络,堆中等 |
| 主节点 | 集群状态管理 | 稳定低延迟,不与数据节点混部 |
| 预处理节点 | ingest pipeline | CPU,易被大文档拖慢 |
# elasticsearch.yml 角色声明(按版本核对可用角色名)
node.roles: [ data_hot, data_content ]
# 专用协调节点
# node.roles: [ ]
# 专用主节点(奇数个,3 个起)
# node.roles: [ master ]
角色分离的收益是故障域隔离:大聚合打满协调节点不会连带把选主拖进无响应。代价是节点数与跨节点流量上升,小集群分离反而降低利用率。
6. 索引设计的取舍
| 方案 | 适用 | 代价 |
|---|---|---|
| 按时间分索引 | 日志、事件流,可整索引删除 | 索引数量多,分片总数上升 |
| 按业务分索引 | 数据保留期一致、查询域清晰 | 单索引持续增长,删除只能按文档 |
| 单索引多 type | 已不可用(7.x 起移除) | — |
PUT _ilm/policy/logs-hot-warm
{
"policy": {
"phases": {
"hot": { "actions": { "rollover": { "max_primary_shard_size": "40gb" } } },
"warm": { "min_age": "7d", "actions": { "forcemerge": { "max_num_segments": 1 } } },
"delete": { "min_age": "90d", "actions": { "delete": {} } }
}
}
}
按时间分索引的价值不在查询快,而在删除是元数据操作:删索引瞬时完成,按文档删除会产生大量 deleted docs 并触发合并。
7. refresh、段合并与 deleted docs
| 设置 | 提高 | 降低 | 建议 |
|---|---|---|---|
refresh_interval 增大 |
写入吞吐 | 数据可见延迟 | 批量导入期临时设 -1 或 30s |
| 频繁 forcemerge | 查询速度(段少) | 磁盘 IO、CPU,且产生大段 | 只在只读索引上做 |
| 大量按文档删除 | — | 查询效率、磁盘 | 改用整索引删除或 ILM |
# 批量导入:先关刷新,导完再恢复并 forcemerge
PUT /bulk-index/_settings { "index": { "refresh_interval": "-1" } }
POST /bulk-index/_forcemerge?max_num_segments=1
PUT /bulk-index/_settings { "index": { "refresh_interval": "1s" } }
deleted docs 堆积的信号是"文档数没涨,磁盘和查询延迟都在涨"。它不会自动消失,只能靠合并回收——这也是为什么"按天删索引"比"按天删文档"健康得多。
8. 查询性能的常见归因
| 症状 | 常见原因 | 验证方式 |
|---|---|---|
| 越翻越慢 | 深分页(from + size 累积) |
对比 from=0 与 from=9000 的耗时 |
| 个别查询打满 CPU | 脚本、正则、通配前缀 | profile API 看各阶段耗时 |
| 聚合慢 | 高基数字段聚合、嵌套聚合 | 降基数或改用预聚合 |
| 全集群变慢 | 分片过多导致扇出、协调节点瓶颈 | 对比单分片查询与全索引查询 |
GET /logs-*/_search
{ "profile": true, "query": { "match": { "msg": "timeout" } } }
归因顺序:先分清单查询慢还是集群整体慢。前者看语句与映射,后者看分片数、段数、协调节点与合并线程。混在一起排查,通常得到"加机器"这种最贵的结论。
9. 扩容、再平衡与滚动升级
# 迁移限速:避免迁分片打满网络与磁盘
PUT _cluster/settings
{ "persistent": {
"cluster.routing.allocation.node_concurrent_recoveries": 2,
"indices.recovery.max_bytes_per_sec": "80mb"
} }
| 操作 | 触发时机 | 业务表现 |
|---|---|---|
| 加分片再平衡 | 节点容量不均、热点集中 | 迁移期 IO 上升、查询延迟抬升 |
| 滚动升级 | 版本修复、配置变更 | 每次重启一个节点,副本顶上 |
| 主节点替换 | 主节点不稳 | 选主期间集群状态短暂不可写 |
升级顺序:先专用主节点(选举稳定)→ 协调节点 → 数据节点。每步验证三件事:健康色、_cat/shards 无长期 RELOCATING、写入拒绝归零。跳过验证直接推进,是把小问题叠成事故的标准路径。
10. 监控指标与成本优化
| 指标 | 位置 | 阈值形态 |
|---|---|---|
| 堆压力 | _nodes/stats/jvm |
老年代持续 > 75% 且 GC 频繁 |
| 段数 | _cat/segments |
单分片段数长期偏高 |
| 合并线程 | _nodes/stats/indices/merge |
合并队列积压 |
| 写入拒绝 | _nodes/stats/thread_pool |
rejected 持续增长 |
| 慢查询 | 慢日志 | 按 P99 设阈值 |
| 成本手段 | 收益 | 代价 |
|---|---|---|
| 冷热分层 | 冷数据用大容量低配盘 | 查询冷数据延迟上升 |
压缩(index.codec: best_compression) |
磁盘下降 | 写入与查询 CPU 上升 |
| 快照归档后删除 | 存储最省 | 恢复需要时间与流程 |
| 降低副本数 | 存储减半 | 可用性与读扩展下降 |
成本优化优先级:先删无用数据与索引,再谈压缩与分层。多数存储浪费来自没人敢删的历史索引,而不是编码效率。
11. 排错:六种集群异常
| 现象 | 原因 | 处理 |
|---|---|---|
| 长期 yellow | 副本无处分配(节点数不足) | 降副本或加节点,查 allocation 过滤 |
| 分片无法分配 | 磁盘水位超过低水位线 | 清理或调整 watermark,先看 _cluster/allocation/explain |
| 写入被拒 | 线程池队列满、合并跟不上 | 降批量并发,加 refresh 间隔,查合并压力 |
| 查询突然变慢 | 合并/快照/迁移争抢 IO | 看 _cat/thread_pool |
| 重启后长时间红 | 并发恢复低、磁盘慢 | 提并发恢复数 |
| 磁盘只涨不降 | deleted docs 未合并 | forcemerge 只读索引 |
排查入口固定为一句:GET _cluster/allocation/explain——多数"分片不分配"的答案在这里,不在日志里。
12. 边界:这些情况不必上集群
| 场景 | 判断 | 理由 |
|---|---|---|
| 数据量小(单机可容纳) | 单分片、单节点 | 分片与副本只增加复杂度 |
| 写入远大于查询 | 优先考虑别的存储 | 搜索引擎的写入成本高于日志型存储 |
| 查询以主键精确匹配为主 | 用 KV/关系库 | 倒排索引的收益体现不出来 |
| 强事务与实时一致 | 关系库 + 缓存 | 搜索引擎的近实时语义不满足 |
| 检索需求简单(前缀、模糊) | 数据库索引即可 | 引入集群的运维成本不划算 |
搜索引擎的定位是检索与聚合,不是通用存储。当主库用,代价会以"数据丢失窗口 + 重建时间"的形式出现在事故里。
13. 自测
- 为什么分片数要按未来一年的数据量定,而不是当前量?
- 分片过多的四类代价里,哪一类在节点重启时才暴露得最明显?
- 副本能扩展读,为什么不能扩展写?
deleted docs堆积的信号是什么?为什么按索引删除比按文档删除健康?- 集群整体变慢与单查询变慢,排查入口有什么不同?
- 滚动升级时先升哪类节点,每一步验证哪三件事?