在 Easysearch(兼容 Elasticsearch)的架构中,索引的主分片数(index.number_of_shards)一旦创建就无法直接修改。这给实际使用带来挑战:
- 设得太少,查询/写入瓶颈出现;
- 设得太多,资源浪费、集群不稳;
- 想变更结构,却发现配置是“写死”的。
本文将带你深入了解三种常见但本质不同的索引重构方式:split、shrink、reindex,教你如何选择合适方案、安全操作,并解释为什么split + shrink 无法取代 reindex。
📌 一张图概览三种方式
| 方法 | 是否重建索引 | 可否原名使用 | 改分片数限制 | 是否保留数据 | 是否改结构(mapping/settings) | 常见用途 |
|---|---|---|---|---|---|---|
split | ✅ 新建索引 | ❌ 不支持 | 只能 × 倍数(如 1→2→4) | ✅ 是 | ❌ 否 | 提升写入并发/读性能 |
shrink | ✅ 新建索引 | ❌ 不支持 | 只能 ÷ 因数(如 4→2→1) | ✅ 是 | ❌ 否 | 合并历史数据分片 |
reindex | ✅ 新建索引 | ✅ 支持(先删) | 任意 | ✅ 是 | ✅ 支持 | 自定义结构/分片/升级 |
🔧 一、split:将分片数量倍增(如 1 → 2 → 4)
适用于: 提升并发能力、增加查询/写入并行度。
✅ 条件要求:
- 原始索引必须设置
index.blocks.write: true(只读);主要是防止写入继续增长。 - 新分片数必须是原主分片的 倍数;
- 不能使用原名,目标索引必须另起新名。
🛠 操作示例:
bash
# 设置只读
PUT /abc/_settings
{
"settings": {
"index.blocks.write": true
}
}
如果不设置为只读的话,就报错:
```json
{
"error": {
"root_cause": [
{
"type": "illegal_state_exception",
"reason": "index abc must be read-only to resize index. use \"index.blocks.write=true\""
}
],
"type": "illegal_state_exception",
"reason": "index abc must be read-only to resize index. use \"index.blocks.write=true\""
},
"status": 500
}拆分索引(1 → 4)
POST /abc/_split/abc_split_2shards
{
"settings": {
"index.number_of_shards": 2
}
}执行结果如下:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "abc_split_2shards"
}如果不是倍数也会报错:
json
{
"error": {
"root_cause": [
{
"type": "illegal_argument_exception",
"reason": "the number of source shards [13] must be a factor of [25]"
}
],
"type": "illegal_argument_exception",
"reason": "the number of source shards [13] must be a factor of [25]"
},
"status": 400
}查看索引的信息:
GET /abc_split_2shards/_settings?flat_settings=true/_settings:Elasticsearch 提供的 API 端点,用于查看索引设置。 ?flat_settings=true:查询参数,使返回结果以扁平化的键值对形式展示(而非嵌套结构)。
可以看到目标的索引也是只读的,这在 Easysearch 里是 ElasticSearch 不一样的地方。
json
{
"abc_split_2shards": {
"settings": {
"index.blocks.write": "true",
"index.creation_date": "1750747232004",
"index.number_of_replicas": "1",
"index.number_of_shards": "2",
"index.provided_name": "abc_split_2shards",
"index.resize.source.name": "abc",
"index.resize.source.uuid": "3NY_W5B_TzimoEGdoA74cg",
"index.routing.allocation.initial_recovery._id": null,
"index.routing_partition_size": "1",
"index.uuid": "e2BQiTRKTlaTS5OE8kmiXw",
"index.version.created": "1130099",
"index.version.upgraded": "1130099"
}
}
}然后使用这个来解锁 write block。
PUT /abc_split_2shards/_settings
{
"settings": {
"index.blocks.write": false
}
}如果你不想让目标索引变成只读。也可以在_split 的时候加上 "index.blocks.write": false。
json
POST /abc_split_2sharxds/_split/qwe
{
"settings": {
"index.blocks.write": false,
"index.number_of_shards": 26
}
}🔧 二、shrink:将分片数量整除压缩(如 8 → 4 → 1)
适用于: 历史归档数据压缩、节省内存、提升查询效率。
✅ 条件要求:
- 所有主分片必须集中在同一节点;
- 原索引必须只读;
- 新分片数必须是旧分片数的 因数;
- 同样不能保留原名,需新建索引名。
🛠 操作示例:
bash
# 强制所有主分片调度到 node-1
PUT /source_index/_settings
{
"settings": {
"index.blocks.write": true,
"index.routing.allocation.require._name": "node-1",
"index.number_of_replicas": 0
}
}
如果不是只读同样报错:
```json
{
"error": {
"root_cause": [
{
"type": "illegal_state_exception",
"reason": "index test1 must be read-only to resize index. use \"index.blocks.write=true\""
}
],
"type": "illegal_state_exception",
"reason": "index test1 must be read-only to resize index. use \"index.blocks.write=true\""
},
"status": 500
}合并为一个分片
POST /source_index/_shrink/source_index_1
{
"settings": {
"index.blocks.write": false,
"index.number_of_shards": 1
}
}解锁
PUT /source_index_1/_settings
{
"settings": {
"index.blocks.write": false
}
}🔧 三、reindex:拷贝数据 + 新建结构 + 替换旧索引
适用于: 任意修改分片数、字段结构、settings,或实现“看起来改了原索引”的效果。
✅ 优势:
- 唯一支持任意分片数修改;
- 可自由重构 mapping、settings;
- 可支持保留原名(删除旧索引 + 重新创建);
- 可带条件、分页、脚本拷贝数据;
- 是唯一可模拟“修改原索引分片”的方式。
🛠 操作步骤(保留原名但改变结构):
bash
# 1. 创建临时索引结构(你想要的新结构)
PUT /my_index_v2
{
"settings": {
"index.number_of_shards": 5,
"index.number_of_replicas": 1
},
"mappings": {
"properties": {
"user": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
# 2. 拷贝数据
POST /_reindex
{
"source": { "index": "my_index" },
"dest": { "index": "my_index_v2" }
}
# 3. 删除旧索引(谨慎)
DELETE /my_index
# 4. 创建同名索引(新结构)
PUT /my_index
{
"settings": {
"index.number_of_shards": 3,
"index.number_of_replicas": 1
},
"mappings": {
"properties": {
"user": { "type": "keyword" },
"message": { "type": "text" }
}
}
}
# 5. 再次拷贝数据(回填)
POST /_reindex
{
"source": { "index": "my_index_v2" },
"dest": { "index": "my_index" }
}
6. 查看索引,并且删除目标索引
GET _cat/indices/my_index*?v

最后删除my_index_v2 即可
DELETE /my_index_v2⚠️ 为什么 shrink + split 不能替代 reindex?
很多用户会问:能不能 shrink → split 或 split → shrink 拼接出任意分片数?
答案是:数学上不成立 + 实战限制太多。
| 操作 | 说明 |
|---|---|
| split 只能倍增 | 例如:1 → 2 → 4 → 8 ✅,但不能变成 3、5、6 ❌ |
| shrink 只能整除 | 例如:8 → 4 → 2 → 1 ✅,但不能变成 3、5 ❌ |
| 二者组合 | 受限于倍数 × 因数关系,不是万能变换(大多数目标分片数根本到不了) |
✅ 唯一万能方式:reindex
可以任意:
- 调整分片数 ✅
- 修改字段结构 ✅
- 改 settings ✅
- 保留索引名 ✅
✅ 最佳实践总结
| 场景 | 推荐方式 | 理由 |
|---|---|---|
| 写入并发不足(1 → 4) | split | 快速、低风险 |
| 存储/查询优化(8 → 1) | shrink | 节省资源、适合归档 |
| 修改索引结构、字段、settings | reindex | 最灵活、唯一支持任意结构 |
| 想保留原名但改分片数 | reindex(配合 delete/recreate) | 只有它能实现 |
| 不想中断服务 | reindex + alias 切换 | alias 实现无缝替换 |
🚀 附加建议
S* split/shrink 一般用于 线上小范围结构调整;
- reindex 用于 升级、清洗、结构优化等更大粒度的改造;
- 如果你不想中断服务,强烈建议使用 alias + reindex 做平滑切换;
- 不建议用 shrink + split 拼接方案,实际运维性差、数学关系苛刻。E

