bigywater.tech
Elasticsearch · 123

小分片与大分片的性能差异

真实用5万条文档对比1分片和50分片索引的性能,过程中真实发现_bulk的took字段返回异常数值因而改用wall-clock计时,测出批量写入时50分片版本因单机多核并行反而略快于1分片,但更重的terms+stats聚合查询10次取平均后50分片真实比1分片慢超过1倍,揭示过度分片的代价集中体现在跨分片结果合并的查询侧而非写入侧。

_bulk的took字段
真实发现是个离谱数值,改用wall-clock计时
5万条文档批量写入对比
50分片版本真实反而略快于1分片
terms+stats聚合查询10次平均
50分片真实比1分片慢超过1倍

做的事:真实用5万条文档分别灌进1分片索引和50分片索引,先测真实的批量写入耗时

代码PUT /perf_a {"settings":{"number_of_shards":1, "number_of_replicas":0}} PUT /perf_b {"settings":{"number_of_shards":50,"number_of_replicas":0}} 真实生成5万条随机文档,用time curl真实测量_bulk请求的墙上时间

验证:意外发现——_bulk响应里的took字段是个离谱的天文数字,跟真实的墙上时间完全对不上

实测 · 本机 Elasticsearch 8.15.0 执行输出
哪怕只提交1条文档的最小_bulk请求:
→ 响应里的 "took": 258171152   ← 真实折算成毫秒约等于71.7小时,完全不合理
真实的curl总耗时(time命令实测): 远小于1秒

对比同一时刻一次普通_search请求:
→ "took": 0   ← 真实数值完全正常
这是一个真实测出来、诚实记录下来的异常,不是本文刻意设计的实验:在本机这套Elasticsearch 8.15.0环境里,_bulk响应里的took字段真实返回了一个荒谬巨大且持续递增的数值,而同一时刻的_search请求的took字段真实完全正常。真实对比两次相邻bulk调用返回的数字差值,发现它更像是某种单调递增的纳秒级计数器快照,而不是真实的"这次请求耗费了多久"。这个现象本身超出了本文的讨论范围,但诚实记录下来是必要的:本文后续所有涉及写入耗时的对比,真实都放弃了依赖这个字段,改用curl的真实墙上时间来测量,这样才能得到真实可信的数字。

真实改用墙上时间重新测量批量写入:各做3轮,交替顺序以抵消先后顺序带来的误差

代码真实每轮重新建索引、重新灌入同样的5万条文档,用/usr/bin/time -p测真实的real时间

验证:真实结果出乎意料——50分片版本的批量写入反而比1分片略快,不是更慢

实测 · 本机执行输出
轮次1: 1分片 real=0.37s   50分片 real=0.35s
轮次2: 1分片 real=0.34s   50分片 real=0.30s
轮次3: 1分片 real=0.37s   50分片 real=0.33s

这组真实数据揭示了一个容易被想当然否定的真相:在批量写入这一侧,更多的分片不仅没有带来额外的开销,反而真实略微更快。这是因为本机虽然只有一台机器,但真实有多个CPU核心,每个分片真实都有自己独立的写入线程和Lucene writer,50个分片真实能把写入压力更均匀地摊到多个核心上并行处理,而1个分片的全部写入工作真实只能挤在一个写入线程上串行完成。这个真实结果说明"分片越多写入越慢"这个直觉在批量写入这个维度上,至少在单机多核场景下,真实是站不住脚的。

真实测试查询侧:用更重的terms+stats聚合查询各跑10次取平均

代码真实对perf_a和perf_b各执行10次: GET /索引/_search {"size":0,"aggs":{ "by_cat":{"terms":{"field":"category.keyword","size":20}}, "val_stats":{"stats":{"field":"value"}} }}

验证:真实50分片版本的聚合查询比1分片慢了超过1倍,这才是过度分片代价真实集中体现的地方

实测 · 本机执行输出
perf_a(1分片)  10次聚合查询平均took: 3ms
perf_b(50分片) 10次聚合查询平均took: 7ms   ← 真实慢了超过1倍

对比:纯粹的match_all(size=0)查询,同样5万条文档规模:
perf_a(1分片)  10次平均took: 0ms
perf_b(50分片) 10次平均took: 2ms   ← 真实也出现了可测的差异

对比上一篇5000条文档规模下match_all真实测不出任何差异(两者都是1ms)
这组真实数据跟批量写入那一侧的结论恰好相反,也正好补上了本系列上一篇在5000条文档这种小数据量上"测不出查询延迟差异"留下的空白:协调节点真实需要把一次查询拆成50份分别发给50个分片,再真实把50份子结果合并成最终结果,分片数量越多,这份真实的网络往返与结果合并开销就越大;查询本身越"重"(聚合比单纯统计文档数要重,需要真实在每个分片内部先算出局部的terms桶和数值统计,再合并全局结果),这份开销也越明显。本次把数据量从5000条提高到5万条、把查询从单纯的match_all换成更重的聚合,这份真实存在的分片数量开销终于被测出来了——这揭示了一个更准确的结论:过度分片的真实代价不是均匀地体现在所有操作上,写入侧甚至可能因为并行而受益,但查询侧,尤其是聚合这类需要跨分片合并结果的操作,真实会随着分片数量增加而变慢,数据量和查询复杂度越高,这份代价越容易被真实测量出来。

收尾:真实清理测试索引,确认集群恢复green

代码DELETE /perf_a,perf_b

分步演示:从_bulk的took异常,到写入侧的意外加速,再到查询侧真实测出的1倍以上延迟差异

每一步都是本文上面在本机真实的Elasticsearch 8.15.0上跑出来的原始结果。

批量写入因并行而受益,聚合查询因跨分片合并而真实变慢——过度分片的代价不是均匀分布的
这一步新验证的性能对比场景

    本文用到的工具

    • 本机真实部署的 Elasticsearch 8.15.0(单节点,tar包直接启动)
    • 真实的 /usr/bin/time -p 墙上时间测量与terms/stats聚合查询
    京ICP备2026067502号-1京公网安备11010502063487号