分片大小的黄金法则与再平衡策略
分片大小的黄金法则与再平衡策略
分片(shard)是 ES 中最底层的 Lucene 索引实例,也是数据分布的最小单元。分片数量和大小设计不当,轻则查询变慢,重则集群不可用。本文介绍分片规划的黄金法则和实操策略。
单分片最佳大小:10-50GB
业界公认的单分片最佳大小是 10GB 到 50GB。这个数字背后有详实的工程依据:
为什么不能太小(< 10GB)?
分片过小意味着分片数量过多。想象一个 50GB 的索引切成了 50 个 1GB 的分片,每个节点要维护几十个分片的小索引实例:
- 每个分片都是一个完整的 Lucene 索引,维护自己的 segment 文件、commit point、translog。
- 分片越多,搜索请求需要在更多分片上执行,协调节点的 merge-sort 开销线性增长。
- 分片元数据存储在 Cluster State 中,分片数过多会撑大 Cluster State,导致 Master 节点 GC 压力增大。
为什么不能太大(> 50GB)?
- 迁移慢:节点宕机后,分片需要从剩余副本重建。50GB 在千兆网络下全量拷贝约需 7 分钟,100GB 要 14 分钟。迁移期集群处于 degraded 状态。
- 恢复慢:分片 Recovery 不光传输文件,还要回放 translog。分片越大,translog 越大,回放越久。
- Merge 慢:Lucene 的后台 Merge 操作需要重写 segment 文件。一个 100GB 分片的 Major Merge 可能持续数小时,期间磁盘 IO 几乎被 Merge 独占,严重挤占写入和查询资源。
具体规划公式:
单分片大小 = 总数据量 / 主分片数 / (1 + 副本数)
通常按照 30-40GB 来定。比如一个预计 3TB 的订单索引,可以设:
主分片数 = 3000GB / 30GB ≈ 100 → 主分片 10 个 + 2 副本 = 30 个分片总数 → 每分片约 100GB × 3 / 30 = 10GB × ...
更简洁的公式:主分片数 = ceil(总数据量 / 30GB),再根据节点数适当调整。
分片过多 vs 分片过大:两难对比
| 问题 | 分片过多(单分片 < 10GB) | 分片过大(单分片 > 50GB) |
|---|---|---|
| 搜索延迟 | 协调节点归并开销大,扇出多 | 单分片内 segment 多,遍历慢 |
| 写入吞吐 | 无明显影响 | 可能因单分片 merge 影响 |
| 节点恢复 | 分片多并行恢复快但总开销大 | 单个分片恢复慢,串行瓶颈 |
| Master 压力 | Cluster State 大,发布慢 | 小 |
| 磁盘浪费 | 可能有多余的小 segment | 紧凑 |
分片过多的踩坑案例:某日志平台的默认索引模板设置了 50 个主分片(因为最初规划的数据量很大),但实际数据量只有 50GB/天。结果每天每个索引只有 1GB 的分片,半年下来集群 shard 总数接近 10000。Master 节点的 Cluster State 发布一次要 3 秒,节点加入集群要 2 分钟。
分片过大的踩坑案例:某电商将订单索引设计为 2 主分片 + 1 副本,一个月后单分片达到 120GB。一次节点滚动重启,分片恢复耗时 40 分钟,所有查询超时。最终不得不停机 reindex 到 6 个分片。
再平衡策略
自动再平衡
ES 默认开启自动分片再平衡,受以下参数控制:
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.rebalance.enable": "all",
"cluster.routing.allocation.allow_rebalance": "indices_all_active",
"cluster.routing.allocation.cluster_concurrent_rebalance": 2,
"indices.recovery.max_bytes_per_sec": "40mb"
}
}
cluster_concurrent_rebalance:集群级并发迁移分片数,默认 2。太小了慢,太大了影响业务。indices.recovery.max_bytes_per_sec:每个节点的恢复带宽限制,默认无限制。建议设为 40mb 防止打满网卡。
手动词 reroute 迁移
当自动平衡不满足需求时(比如想把热点索引从该节点移走),可以用 reroute API:
POST /_cluster/reroute
{
"commands": [
{
"move": {
"index": "hot_index_2024",
"shard": 0,
"from_node": "node-1",
"to_node": "node-3"
}
}
]
}
reroute 三大命令:
| 命令 | 作用 | 风险 |
|---|---|---|
move |
将已分配的分片从节点 A 移到节点 B | 无风险,正常迁移 |
allocate_replica |
从未分配的副本分配指定副本 | 无风险 |
allocate_empty_primary |
分配空主分片(强制) | 数据将丢失!仅用于灾难恢复 |
cancel |
取消正在进行的 allocation | 可用于终止异常 recovery |
total_shards_per_node 限制
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.total_shards_per_node": 600
}
}
这个参数限制每个节点能承载的主分片+副本总数。当某节点超过限制时,新索引的分片不会分配到该节点。强烈建议生产环境设置此参数,防止某个节点分片数爆炸。
更多节点级限制(ES 7.16+):
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.total_primary_shards_per_node": 300,
"cluster.routing.allocation.total_shards_per_node": 600
}
}
可以分别限制主分片数和总分片数,更精准。
磁盘水位线保护
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.disk.watermark.low": "85%",
"cluster.routing.allocation.disk.watermark.high": "90%",
"cluster.routing.allocation.disk.watermark.flood_stage": "95%",
"cluster.info.update.interval": "30s"
}
}
low (85%):不再向该节点分配新分片(现有分片不受影响)high (90%):开始将分片从该节点迁出flood_stage (95%):将所有包含该节点分片的索引设为read_only_allow_delete
分片分配感知(Allocation Awareness)
额外提一下 shard allocation awareness,可以防止所有副本落在同一个机架:
PUT /_cluster/settings
{
"persistent": {
"cluster.routing.allocation.awareness.attributes": "rack_id",
"cluster.routing.allocation.awareness.force.rack_id.values": ["rack1", "rack2", "rack3"]
}
}
配合节点属性:node.attr.rack_id: rack1 配置,ES 会确保同一索引的主副分片不会落在同一个 rack 上。
小结
分片大小和数量是 ES 集群设计的"地基"。对大多数场景,单分片 30GB、每节点最多 600 个分片 是安全舒适区。一旦超出就要用 reroute 和 total_shards_per_node 等机制主动管控,切忌等到出事再救火。