--- title: "ES 调优帖:关于索引合并参数 index.merge.policy.deletePctAllowed 的取值优化" date: 2025-05-14 lastmod: 2025-05-14 description: "ES 索引性能调优,降低索引大小,提高写入更新效率。" tags: ["Elasticsearch"] summary: "最近发现了 lucene 9.5 版本把 merge 策略的默认参数改了。 * GITHUB#11761: TieredMergePolicy now allowed a maximum allowable deletes percentage of down to 5%, and the default maximum allowable deletes percentage is changed from 33% to 20%. (Marc D'Mello) 也就是 index.merge.policy.deletePctAllowed 最小值可以取 5%(原来是 20%),而默认值为 20%(原来是 33%)。 这是一个控制索引中已删除文档的占比的参数,简单来说,调低这个参数能够降低存储大小,同时也需要更多的 cpu 和内存资源来完成这个调优。 通过这个 帖子的讨论,大家可以发现,“实践出真知”,这次的参数调整是 lucene 社区对于用户积极反馈的采纳。因此,对于老版本的用户,也可以在 deletepct 比较高的场景下,调优这个参数,当然一切生产调整都需要经过测试。 对于 ES 的新用户来说,这时候可能冒出了下面这些问题 这个参数反馈的已删除文档占比 deletepct 是什么? 它怎么计算的呢?较高的 deletepct 会有什么影响? 较低的 deletepct 为什么会有更多的资源消耗? 除了调优这个参数还有什么优化办法么? 伴随这些问题,来探讨一下这个参数的来源和作用。 deletePctAllowed:软删除的遗留 # 在 Lucene 中,软删除是一种标记文档以便后续逻辑删除的机制,而不是立即从索引中物理删除文档。" --- 最近发现了 lucene 9.5 版本把 merge 策略的默认参数改了。 ``` * GITHUB#11761: TieredMergePolicy now allowed a maximum allowable deletes percentage of down to 5%, and the default maximum allowable deletes percentage is changed from 33% to 20%. (Marc D'Mello) ``` 也就是 `index.merge.policy.deletePctAllowed` 最小值可以取 5%(原来是 20%),而默认值为 20%(原来是 33%)。 这是一个控制索引中已删除文档的占比的参数,简单来说,调低这个参数能够降低存储大小,同时也需要更多的 cpu 和内存资源来完成这个调优。 通过这个[帖子](https://github.com/apache/lucene/issues/11761)的讨论,大家可以发现,“实践出真知”,这次的参数调整是 lucene 社区对于用户积极反馈的采纳。因此,对于老版本的用户,也可以在 deletepct 比较高的场景下,调优这个参数,当然**一切生产调整都需要经过测试**。 对于 ES 的新用户来说,这时候可能冒出了下面这些问题 - 这个参数反馈的已删除文档占比 deletepct 是什么? - 它怎么计算的呢?较高的 deletepct 会有什么影响? - 较低的 deletepct 为什么会有更多的资源消耗? - 除了调优这个参数还有什么优化办法么? 伴随这些问题,来探讨一下这个参数的来源和作用。 ## deletePctAllowed:软删除的遗留 在 Lucene 中,软删除是一种标记文档以便后续逻辑删除的机制,而不是立即从索引中物理删除文档。 但是这些软删除文档又不是永久存在的,deletePctAllowed 表示索引中允许存在的软删除文档占总文档数的最大百分比。 当软删除文档的比例达到或超过 deletePctAllowed 所设定的阈值时,Lucene 会触发索引合并操作。这是因为在合并过程中,那些被软删除的文档会被物理地从索引中移除,从而减少索引的存储空间占用。 当 deletePctAllowed 设置过低时,会频繁触发索引合并,因合并操作需大量磁盘 I/O、CPU 和内存资源,会使写入性能显著下降,磁盘 I/O 压力增大。假设 deletePctAllowed 为 0,则每次**写入都需要消耗额外的资源**来做 segment 的合并。 deletePctAllowed 过高,索引会容纳大量软删除文档,占用过多磁盘空间,增加存储成本且可能导致磁盘空间不足。**查询时要过滤大量软删除文档**,使查询响应时间变长、性能下降。同时也观察到,在使用 soft-deleted 特性后,**文档更新和 refresh 也会受到影响**,deletePctAllowed 过高,文档更新/refresh 操作耗时也会明显上升。 ### deletePctAllowed 的实际效果 从上面的解释看,`index.merge.policy.deletePctAllowed` 这个参数仿佛并不难理解,但实际上**这个参数是应用到各个 segment 级别的**,并且 segment 对这个参数的触发条件也是有限制(过小的 segment 并不会因为这个参数触发合并操作)。在多分片多 segment 的条件下,索引对 deletePctAllowed 参数实际的应用效果并不完全一致。因此,可以做个实际测试来看 deletePctAllowed 对索引产生的效果。 这里创建一个一千万文档的索引,然后全量更新一遍,看最后 deletePctAllowed 会保留多少的被删除文档。 ``` GET test_del/_count { "count": 10000000, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 } } # 查看 delete 文档数量 GET test_del/_stats ··· "primaries": { "docs": { "count": 10000000, "deleted": 0 }, ··· ``` 这里的 deletePctAllowed 还是使用的 33%。 {{% load-img "/img/blog/2025/index-merge-policy-deletepctallowed/delpct-1.png" "" %}} 更新任务命令: ``` POST test_del/_update_by_query?wait_for_completion=false { "query": { "match_all": {} }, "script": { "source": "ctx._source.field_name = 'new_value'", "lang": "painless" } } ``` 完成后, ``` # 任务状态 ··· "task": { "node": "28HymM3xTESGMPRD3LvtCg", "id": 10385666, "type": "transport", "action": "indices:data/write/update/byquery", "status": { "total": 10000000, "updated": 10000000,# 这里可以看到全量更新 "created": 0, "deleted": 0, "batches": 10000, "version_conflicts": 0, "noops": 0, "retries": { "bulk": 0, "search": 0 }, "throttled_millis": 0, "requests_per_second": -1, "throttled_until_millis": 0 } ··· # 索引的状态 GET test_del/_stats ··· "_all": { "primaries": { "docs": { "count": 10000000, "deleted": 809782 }, ··· ``` 实际删除文档与非删除文档的比例为 8.09%。 现在尝试调低 `index.merge.policy.deletes_pct_allowed`到 20%。 ``` PUT test_del/_settings {"index.merge.policy.deletes_pct_allowed":20} ``` 由于之前删除文档占比过低,调整参数并不会触发新的 merge,因此需要重新全量更新数据查看一下是否有改变。 最终得到的索引状态如下: ``` GET test_del/_stats ··· "primaries": { "docs": { "count": 10000000, "deleted": 190458 } ··· ``` 这次得到的实际删除文档与非删除文档的比例为 1.9% ## deletes_pct_allowed 默认值的调整 上面提到 deletePctAllowed 设置过低时,会频繁触发索引合并,而合并任务的线程使用线程类型是 SCALING 的,是一种动态扩展使用 cpu 的策略。 {{% load-img "/img/blog/2025/index-merge-policy-deletepctallowed/delpct-2.png" "" %}} 那么,当 deletePctAllowed 设置过低时,merge 任务增加,cpu 线程使用增加。集群的 cpu 和磁盘的使用会随着写入增加,deletePctAllowed 降低产生了放大效果。 所以,在没有大量数据支撑的条件下,ES 的使用者们往往会选择业务低峰期使用 forcemerge 来降低文档删除比,因为 forcemerge 的线程类型是 fixed,并且为 1,**对 cpu 和磁盘的压力更加可控**,同时 forcemerge 的 deletePctAllowed 默认阈值是 10%,更加低。 而社区中,大家的实际反馈则更倾向使用较低的 deletePctAllowed 阈值,特别是小索引小写入的情况下。 {{% load-img "/img/blog/2025/index-merge-policy-deletepctallowed/delpct-3.png" "" %}} 并且提供了相应的[测试结果](https://github.com/opensearch-project/OpenSearch/issues/7360) ``` #### RUN 1 Test config: Single node domain Instance type: EC2 m5.4xlarge Updates: 50% of the total request Baseline: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "33.0" Target: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "20.0" | Metrics | Baseline | Target | ------------------------------------ | Store size | 39gb | 37gb | | Deleted docs percent | 22% | 18% | | Avg. CPU | (42 - 53)% | (43 - 55)% | | Write throughput | 11 - 15 mbps | 11 - 17 mbps | | Indexing latency | 0.15 - 0.36 ms | 0.15 - 0.39 ms | | P90 search latency | 14.9 ms | 13.2 ms | | P90 term query latency | 13.7 ms | 13.5 ms | #### RUN 2 Test config: Single node domain Instance type: EC2 m5.4xlarge Updates: 75% of the total request Baseline: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "33.0" Target: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "20.0" | Metrics | Baseline | Target | ------------------------------------ | Store size | 19.4gb | 17.7gb | | Deleted docs percent | 22.8% | 15% | | Avg. CPU | (43 - 53)% | (46 - 53)% | | Write throughput | 9 - 14.5 mbps | 10 - 15.9 mbps | | Indexing latency | 0.14 - 0.33 ms | 0.14 - 0.28 ms | | P90 search latency | 15.9 ms | 13.5 ms | | P90 term query latency | 15.7 ms | 13.9 ms | #### RUN 3 Test config: Single node domain Instance type: EC2 m5.4xlarge Updates: 80% of the total request Baseline: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "33.0" Target: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "20.0" | Metrics | Baseline | Target | ------------------------------------ | Store size | 15.9gb | 14.6gb | | Deleted docs percent | 24% | 18% | | Avg. CPU | (46 - 52)% | (47 - 52)% | | Write throughput | 9 - 13 mbps | 10 - 15 mbps | | Indexing latency | 0.14 - 0.28 ms | 0.13 - 0.26 ms | | P90 search latency | 15.3 ms | 13.6 ms | | P90 term query latency | 15.2 ms | 13.4 ms | #### RUN 4 Test config: Single node domain Instance type: EC2 m5.2xlarge Updates: 80% of the total request Baseline: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "33.0" Target: OS_2.3 "index.merge.policy.deletes_pct_allowed" : "20.0" | Metrics | Baseline | Target | ------------------------------------ | Store size | 21.6gb | 17.8gb | | Deleted docs percent | 30% | 18% | | Avg. CPU | (71 - 89)% | (83 - 90)% | | Write throughput | 6 - 12 mbps | 10 - 15 mbps | | indexing latency | 0.21 - 0.30 ms | 0.20 - 0.31 ms | | P90 search latency | 15.4 ms | 16.3 ms | | P90 term query latency | 15.4 ms | 14.8 ms | ``` 在测试中给出的结论是: 1. CPU 和 IO 吞吐量**没有明显增加**。 2. 由于索引中删除的文档数量较少,搜索延迟更少。 3. 减少被删除文档占用的磁盘空间浪费 但是也需要注意,这里的**测试索引和消耗资源并不大**,有些*业务量较大的索引还是需要重新做相关压力测试*。 ### 另一种调优思路 那除了降低 deletePctAllowed 和使用 forcemerge,还有其他方法么? 这里一个[pr](https://github.com/apache/lucene/pull/92),提供一个综合性的解决方案,作者把两个 merge 策略进行了合并,在主动合并的间隙添加 forcemerge 检测方法,遇到可执行的时间段(资源使用率低),主动发起对单个 segment 的 forcemerge,这里 segment 得删选大小更加低,这样对 forcemerge 的任务耗时也更低,最终减少索引的删除文档占比。 简单的理解就是,利用了集群资源的“碎片时间”去完成主动的 forcemerge。也是一种可控且优质的调优方式。