6.x 及更早版本建索引不指定分片数,默认给 5 个;7.0 把这个默认值改成了 1。本文说说这条改动背后的"过度分片"问题,以及当年分片数定死之后到底有多难改。
说明
实测环境:本地起两个单节点实例——Elasticsearch 6.8.23 与 7.17.29(官方 tar 包直接跑,堆内存 512MB),curl 连本地 9200 端口,两个版本分两次启动。图 1 出自 6.8.23,图 2 出自 7.17.29。
建索引时啥也没配的后果
刚上手 ES 的人,大概率都干过一件事:建索引时啥也没配,直接用默认值。现在这么干问题不大——默认 1 个主分片,小索引不会无故多出几个空转的分片。但往前翻到 6.x 甚至更早,同样的操作,默认给你的是 5 个。
这是官方后来自己承认的,不是危言耸听的"坑"。7.0 的迁移文档里有一条专门的 breaking change,标题就是"建索引不再默认 5 个分片"(migrate_7_0/indices.asciidoc 第 7–9 行 @ v7.0.0):
Previous versions of Elasticsearch defaulted to creating five shards per index. Starting with 7.0.0, the default is now one shard per index.
翻译过来:以前的版本建索引默认给 5 个分片,从 7.0.0 起默认改成 1 个。
先看一眼现在的实际值,确认自己属于哪种情况:
# 建索引时不指定分片数,看拿到几个
curl -s -XPUT 'localhost:9200/test-default' | python3 -m json.tool
curl -s 'localhost:9200/test-default/_settings?pretty' | grep number_of_shards
# 集群里现有索引的分片分布
curl -s 'localhost:9200/_cat/indices?v&h=index,pri,rep,store.size' | head -20
第一条能直接看到默认值到底是 5 还是 1;第二条用来评估存量——如果 pri 那一列普遍是 5 而数据量不大,就是典型的过度分片。
5 这个数字,当年是怎么变成坑的
单看"默认 5"这件事本身不是问题,问题是它遇到了两个特性:
第一,分片数建索引时就定死,改不了。 6.1 之前,一个索引建好之后,分片数永远是那个数,没有任何办法调整。6.1 引入了 _split API,但用起来有个前置条件(split-index.asciidoc @ v6.1.0):
The
_splitAPI requires the source index to be created with a specificnumber_of_routing_shardsin order to be split in the future.
也就是说,想让一个索引以后能拆分,建索引那一刻就要提前配好 number_of_routing_shards。没提前配的索引,一样没法拆。这个前置条件到 7.0 才被去掉(migrate_7_0/indices.asciidoc 第 45–48 行 @ v7.0.0),文档原话是"This requirement has been removed in Elasticsearch 7.0"。
第二,分片不是免费的。 每个分片对应一个独立的 Lucene 实例,有自己的文件句柄、内存占用、后台线程。一个数据量不大的索引,配 5 个分片,相当于把本来 1 个分片能存下的数据拆成 5 份,每份都要单独付出一份分片的固定开销。单个索引看不出什么,但集群里索引一多(尤其是按天滚动建索引的日志场景),分片数会成倍累积,这就是后来官方和社区反复提醒的"过度分片"(oversharding)。
两条合在一起就是当年的典型情况:新手按默认值建索引,拿到 5 个分片;数据量其实用 1 个分片就够;索引建好之后想改,发现 6.1 之前根本改不了,6.1 之后也得当初就配好 number_of_routing_shards,而默认流程里没人会去配这个。
7.0 怎么处理的
7.0 做了两件配套的事,不是只改了个默认值数字:
- 默认分片数从 5 降到 1(create-index.asciidoc 对比:v6.8.0 第 51 行 写的是"Default for
number_of_shardsis 5",v7.0.0 同一行 变成了"is 1")。 _splitAPI 不再要求建索引时提前配number_of_routing_shards,拆分这条路彻底打开。
这两件事其实是一套:既然"拆"变得更容易了,默认值就可以往小处设——用不够再拆,比一开始就按可能用不到的上限铺好分片,更省资源。
分片数到底该定多少
既然默认值只是兜底,那具体该定多少?判断顺序是这样:
第一步,按数据量估。 业界常见的经验区间是单个分片 20GB 到 50GB——低于这个区间说明分片偏小、固定开销占比高;高于这个区间则分片太大,恢复和迁移都变慢。所以先用"这个索引最终会有多大"除以 30GB 左右,得到一个粗略值。
第二步,看写入和查询模式。 分片数是搜索并发的单位:更多分片意味着查询能同时在更多 Lucene 实例上跑。但这只在分片数和数据量匹配时才成立——给一个 5GB 的索引配 20 个分片,并发是上去了,每个分片却只有 250MB,还不如 1 个分片。
第三步,留出扩容的余量。 现在有了 _split,可以先把分片数定小,真需要时再拆。这比一开始就往大了设稳妥,因为拆比合容易——分片数只能增加不能减少(要减少只能 reindex)。
默认做法是:日志类按时间滚动的索引,一个分片起步,数据量涨了再调模板;业务索引按预估数据量算,取一个偏小的值。宁可后面拆,也别一开始就铺开。
6.x 上已经过度分片了怎么办
如果你现在还有一批 6.x 时期建的索引,每个都配了 5 个分片,改法取决于要不要保留数据:
能重建的,优先重建。 建一个新索引(分片数按上面的方法重新算),然后 reindex 过去,最后切别名。这条路最干净,新索引的格式、分片数都是对的。
不能重建的,看能不能拆。 注意方向反了——_split 是增加分片数,解决不了"分片太多"的问题。分片太多只能靠合并,而 ES 没有直接的"合并分片"操作,只有 shrink 能把分片数减少(要求是分片数的因数倍缩小)。5 个分片的情况下,shrink 只能缩到 1 个。
最省事的其实是等。 按时间滚动的日志索引,老索引会自然过期删掉。把索引模板改成分片数 1,新索引就是对的,老索引等它过期。这是很多场景下成本最低的做法。
判断标准:这个索引还要活多久。快过期的就等,长期存在的趁早重建。
还要提醒一句:改之前先统计一下现有分片总数(GET _cat/shards?v 或者看集群健康里的分片计数),心里有个数。批量重建索引是有成本的动作,先量化再动手,别一边改一边发现比预想的多十倍。分片数这种指标平时没人看,等要动它的时候才发现已经积累到这个量,是很常见的情况。分片数不像磁盘那样有直观的水位线,涨上去之后往往表现为集群整体变慢,而不是某个明确的告警。
现状
默认 1 个主分片的设定延续到了现在的版本,没有再变过。对今天的使用者来说,这条历史基本已经"隐身"了:建索引不指定分片数,拿到的就是 1,不会再有人无意中遇到"默认 5 个"这种事。
在 6.8.23 和 7.17.29 上各跑一遍
「默认 5 个分片」这句话最直接影响的是那些不写 settings 的建索引请求:模板里没写、脚本里也没写,全靠默认值。跑一遍能看清两个版本到底差在哪。
图 1:6.8.23 上不写任何分片设置建索引,_cat/indices 显示 sh1 5 1;单节点上 5 个副本分片无处可放,健康是 yellow,unassigned_shards 为 5。本地实测输出。
curl -s -XPUT 'http://localhost:9200/sh1' -H 'Content-Type: application/json' -d '{}'
curl -s 'http://localhost:9200/_cat/indices/sh1?h=index,pri,rep,status'
# sh1 5 1 open
图 2:同样一个空请求,7.17.29 上建出来是 sh1 1 1。本地实测输出。
结论是默认值确实改了,而且不需要任何额外设置。要注意范围:只有「新建、且没写 number_of_shards」的索引受影响,显式写了分片数的索引、从旧集群恢复的索引都保持原值。所以升级后同一个集群里两种默认值会同时存在——这也是为什么升级前后各建一个索引对比,比看文档更能确认自己这套模板到底落在哪一边。
我的建议是把分片数写进模板而不是依赖默认值:这两个值一旦变成隐式依赖,升级时会变成「一半索引 5 分片、一半 1 分片」的混合状态,容量规划、_cat/shards 的巡检口径全都要分两套。
边界:
- 图 1、图 2 是两个版本各起一次单节点实例得到的,yellow 来自单节点没有第二个副本位置,与本条改动无关;
- 6.x 上已经过度分片的索引要靠
_shrink/_split处理,这条没有实测,它需要构造多分片索引并满足路由前置条件。
写在最后
这条默认值变化,表面是改了一个数字,背后是"分片数到底该由谁、什么时候决定"这个问题的答案变了:以前是"建索引那一刻就定死,定错了很难办",现在是"先给一个保守的默认值,真要扩容再用 _split 加"。
对还在规划新集群、新索引模板的人来说,这段历史的实际价值是一句老生常谈但依然成立的提醒:分片数没有万能默认值,它取决于你这个索引最终要存多少数据。默认值只是帮你兜底别踩"5 个起步"那种坑,不代表默认值就适合你的场景。
具体到今天的判断,就一条:分片数往下设容易改(拆),往上设难改(要 reindex)。所以拿不准的时候取小值,比取大值稳妥。
参考
- 建索引不再默认 5 个分片(breaking change)@ v7.0.0
- Create index API,分片默认值 @ v6.8.0
- Create index API,分片默认值 @ v7.0.0
- Split index API,routing shards 前置条件 @ v6.1.0
本文对照 Elasticsearch
v6.1.0、v6.8.0、v7.0.0标签的官方文档核对,引文逐字取自原文。图 1、图 2 是本地 6.8.23 与 7.17.29 单节点实例的实测输出;_shrink/_split未实测,正文中已标注。
