
! —title: “【DIY系列自己动手写分布式搜索引擎】第26篇索引优化与段合并为什么段越少越快”series: “DIY系列自己动手写分布式搜索引擎”episode: 026publish_date: “2026-10-17”author: “AI Coder”tags: [“段合并”, “MergePolicy”, “forceMerge”, “Lucene优化”, “TieredMergePolicy”, “索引性能”]description: “段只会越写越多小段泛滥会拖垮查询打开文件句柄多、每次查询要扫多个段。本文讲清 Lucene 的自动段合并策略 TieredMergePolicy手动 forceMerge 的利与弊以及为什么线上严禁随便 optimize。”prev_article: “文章025_创建与维护索引库增删改与提交.md”next_article: “文章027_灵活索引与定制存储结构.md”keywords: “段合并,MergePolicy,TieredMergePolicy,forceMerge,Lucene优化,索引性能”—上一篇【第25篇】创建与维护索引库增删改与提交下一篇【第27篇】灵活索引与定制存储结构摘要上篇说了Lucene 一提交就冒一个新段段只会越来越多。段一多查询就肉每次搜索要把所有段都扫一遍打开的文件句柄哗哗涨被删文档还占着空间不释放。Lucene 用段合并把一堆小段并成大段顺手把删除标记清掉、把空间腾出来。本文讲自动合并的策略 TieredMergePolicy手动 forceMerge 的甜头与雷区尤其是线上那条铁律别手贱去 optimize。一、段太多的代价肉眼可见想象一个极端情况你每 add 一篇就 commit 一次写了十万篇生成十万个段。现在来一个查询Lucene 要一次查询要扫的段 [_0] [_1] [_2] ... [_99999] ├─ 每个段都要打开一组文件.doc/.pos/.tim... ├─ 每个段都要独立查词典、取倒排、合并结果 └─ 系统文件句柄哗哗被占OS 都看不下去三个实打实的坏处查询变慢。段越多同样的词要在越多地方查一遍最后还要把各段的结果归并去重同一文档可能在多个段里以不同版本出现得合并。段数上去延迟线性涨。文件句柄爆炸。一个段不是单个文件是 .doc/.pos/.tim/.fdt 等一堆。上千段就是上万个文件描述符Linux 默认单进程上限很容易被吃满然后就开始报Too many open files。空间回收不了。被标记删除的文档占着老段段不合并就永远清不掉磁盘只增不减。所以段越少、越大在合理范围查询越快是个基本事实。但段也不能无限大不然合并一次代价惊人。平衡点靠合并策略拿捏。二、段合并在做什么把两个或几个相邻的小段读出里面活着的文档重写进一个更大的新段然后丢掉旧段。这个过程顺手完成了三件事【一次合并 三连击】 读旧段(只取活着且没被删的文档) │ ▼ 写成一个大新段 _NEW │ ├─ 旧段被删文档 → 彻底消失空间释放 ├─ 段数量减少 → 查询要扫的段变少 └─ 索引结构重排 → 更紧凑 删掉旧段文件注意合并是后台默默进行的不阻塞你的写入和查询。Lucene 有个独立的合并线程或线程池盯着达到策略条件就开干。三、自动合并策略TieredMergePolicyLucene 默认用的是 TieredMergePolicy分层合并策略。它的思路有点像收拾房间把大小相近的段归为一层优先合并同一层里的一小撮段既控制单次合并的量又让段大小呈阶梯分布避免一会合并俩小段、一会合并俩大段的混乱。几个关键旋钮都在 IndexWriterConfig / MergePolicy 上可调参数含义默认值附近maxMergeAtOnce一次最多合并几个段10segmentsPerTier每层允许的段数控制层数10maxMergedSegmentMB合并出来的段最大多大5 GBfloorSegmentMB小于它的段算底层合并更有利2 MB【分层合并的直觉】 段大小: 2M 2M 3M 9M 10M 40M 40M 分层: [小层] [中层] [大层] 合并: 先把小层几个并成 7M再和中层凑逐步长大 目标: 段大小阶梯分布单次合并量可控这套策略一般不用你动开箱即合理。真要调也是先盯着实际段数量和合并频率再有的放矢别上来就瞎拧参数。四、手动 forceMerge甜头与雷区有时候你想主动把索引压实比如一次性批量建完索引、后续只读不写这时候可以writer.forceMerge(1);// 强行合并到只剩 1 个段forceMerge老版本叫 optimize会把所有段最终并成 N 个段。只读场景比如离线建好一份索引分发到多台机器当字典这么干很香段少、查询快、文件少。但线上正在被高频写入的库严禁随便 forceMerge。原因很硬它和写入打架。forceMerge 一个大索引会大量读旧段、写新段占满 IO 和 CPU你的正常写入和查询被拖到怀疑人生。我见过一次线上手滑 optimize整节点 IO 打满搜索延迟从几十毫秒飙到几秒。大段合并本身极慢且耗资源。一个几十 GB 的段合并可能跑几十分钟期间临时空间翻倍新旧段共存磁盘直接被吃撑。合并完段变大后续小改又要重合并。你辛辛苦苦并成一个巨段结果来了点新数据又得把它拆进来重并纯属给自己找事。【forceMerge 决策表】 场景 建议 离线建好只读库 可以合并到 1 段查询最优 线上高频写库 千万别IO 打满 资源暴涨 删除量巨大后 谨慎可低峰期小批量别并到 1一句话forceMerge 是给终态用的不是给活跃态用的。五、被删文档与合并的纠缠回头接上篇的删除话题。你 delete 了一堆文档它们只占标记不占实际查询命中但占磁盘。只有合并才会把它们从物理上抹掉。所以如果你发现索引目录异常大、且删除操作很多本质问题是该合并了。Lucene 的合并策略其实会优先挑删除比例高的段来合并因为合并这种段收益最大能腾出最多空间。这也是为什么有时候你没手动干预删除后的空间也会慢慢回收全靠后台合并在干活。本篇小结段只增不减是 Lucene 的常态段泛滥会拖慢查询、吃光文件句柄、卡住空间回收。段合并在后台把小段并大段顺手清掉删除标记、释放空间靠 TieredMergePolicy 分层自动进行基本不用管。forceMerge 是把双刃剑只读库用它压实很香线上活跃库碰都别碰否则 IO 打满悔青肠子。下一篇我们换个角度看看 Lucene 怎么把索引存哪这件事也抽象出来让你能把索引塞进内存、HDFS 甚至自定义介质。上一篇【第25篇】创建与维护索引库增删改与提交下一篇【第27篇】灵活索引与定制存储结构