翻译自:https://lucidworks.com/2018/06/20/solr-and-optimizing-your-index-take-ii/
优化 (optimize) 与清理(expungeDeletes)不再跟之前一样糟糕。但是也不应该随便使用,因为它所需要的成本依然很高。也就是说,这些操作不再那么容易受这篇文章中列出的问题的影响。如果你对 Solr/Lucene 中的段合并操作不那么熟悉,那篇文章可以给你一些背景知识。
我注:之所以将 expungeDeletes 翻译成清理,是因为根据上下文我判断这个词的意思应该是清理掉已经删除的文档
- 从 Solr 7.5 开始,清理与优化/强制合并的默认实现
TieredMergePolicy (TMP)
将有完全不同的表现 TieredMergePolicy
将有一些额外的选项用来控制在一个索引中删除文档的百分比。 见 LUCENE-8263TMP
对于强制合并与清理有一个默认的参数 maxMergedSegmentMB- 如果想要执行以前强制合并 (优化) 的操作,可以通过命令指定
maxSegments
- 删除不能超过
maxMergedSegmentMB
指定的值 - 如果创建了非常大的段,随着删除的文档在段中的堆积。这个段将会"单独合并",用于清理掉那些已经被删除的文档。注意:只有当索引中有接近 50% 的文档被删除时,这种情况才会发生。有可能后续会对这种情况进行调节
不久之前,我写了一篇关于使用 Solr 的 优化以及清理的提交选项 的文章。随着 Solr 7.5 的到来,文章中提到的最坏情况将不会出现。如果你想要知道更多的细节,可以参考 LUCENE-7976 以及相关的 JIRA。
在 Solr 7.5 中,优化(又名强制合并 forceMerge)以及清理使用 TieredMergePolicy
作为默认以及推荐的合并策略,并且遵循 maxMergedSegmentMB 参数的配置。
这样一个简短的介绍,涉及到一些重要的方面,因此将这篇文章发了出来。
首先快速回顾一下,通过命令去执行优化或者清理操作,默认的行为是任何合并的段将会被合并成一个段,而不管最终的结果段会变成多大。
- 对于优化操作,整个索引将会被合并到
maxSegments
参数 (默认为 1) 指定的个数的段中。 - 对于清理操作,所有删除的文档超过 10% 的段会被合并到一个段中。
对于"自然"合并,当索引被更新时,每次硬提交都会进行如下的初始化操作:
- 所有"活着"的文档小于
maxMergedSegmentMB
50% 的段都会被检查,被选中的段会进行合并。 - "被选中的段"意味着使用启发式算法来尝试选择最少工作量的合并,并且依然会遵循
maxMergedSegmentMB
。
关键的不同在于优化/强制合并与清理并不遵循 maxMergedSegmentMB。
关于这点,有过很长的讨论,但是我并不想谈论这个。因为保持索引更新需要去解决一些相互竞争,而 maxMergedSegmentMB
是解决这些问题的一部分。需要权衡的点包括:
- 控制 I/O,因为索引以及搜索对 I/O 瓶颈敏感
- 控制段数,防止耗尽文件句柄数等
- 控制内存消耗,仅仅为了索引而要求分配 5G 的内存在堆上是不可接受的
- 最开始的时候,通过合并到一个段,可以显著的提高速度,但是最新版本的 Solr 没有同等程度的提升
在 Solr 7.5 中,优化 (又名强制合并) 以及清理使用跟"自然合并"一样的算法。"自然合并","强制合并/优化","清理" 之间的差别在于选择什么样的段去进行合并。有下面三种情况:
- 自然合并:所有的段都会被考虑合并。这是将文档索引到 Solr/Lucene 的常规操作。通过对各种可能性进行评分,通过大概的计算以及对 I/O 的估计来选择消耗最少的可能性。因为删除大的段不会被认为消耗较少,所以很少被合并。
- 清理:删除文档数大于 10% 的段,不管段多大,都会被考虑进行合并。
- 优化:又分两种情况,是否指定了
maxSegments
:- 是:所有的段都会被考虑进去
- 否:所有"活着"的文档小于
maxMergedSegmentMB
的段以及所有包含删除文档的段会被考虑进去。所以,没有文档被删除且大于maxMergedSegmentMB
的段不会被考虑。
(等一下!你哭着对我说😂)你之前告诉我们清理以及优化/强制合并遵循 maxMergedSegmentMB
,但是现在你又说可以通过指定 maxSegments = 1
并且段数可以大于 maxMergedSegmentMB
。这到底是怎么肥事?我很开心你可以这样问 (作者在自问自答😂,后面还有一大句是作者用来解释为什么喜欢自问自答的,懒得翻了)。
在 Solr 7.5 中,TMP 引入了"单独合并"。当一个段可以进行合并时,如果它太大了,它将会被重新写入到一个新段中,并移除掉被删除的文档。
这里有一些有意思的结果。假设你已经优化到了 1 个段,开始索引更多的文档,将会触发删除。这篇文章顶部链接里的文章详细说明了它的缺点。也就是说单个大的段不会被合并直到它的主要组成部分为已删除的文档。但是现在不再是这样的了。当满足其他的条件时,"单独合并"将会在大段上执行,本质上会重新写入到 1 个新的段,并且移除掉被删除的文档。它的大小将会被逐渐收缩到 maxMergedSegmentMB
一下,在这点上跟其它的段类似。
注意:这会增加 I/O 的成本。假设有一个段的大小为 200GB,包含了 20% 已经被删除的文档,并且被选中进行单独合并。在由合并算法决定的某个点,需要重写 160GB,并提供了一种从本篇文章顶部链接中的文章列出的状况中去恢复的方法,该方法不需要重新索引,但是最好一开始就不要陷入到这种状况当中。
我已经重复说明过。不要意淫优化/强制合并以及清理是一件好事,要先去测试。如果你可以证明在目前你所处的情况下做这些操作是有价值的,那么可以在条件可控的情况下去做这些操作,因为这些操作的代价很大。
我很高兴你再次这样问。
在 JIRA LUCENE-8263 下面有关于控制这种情况的方法讨论。我更新这篇博客的时候,代码已经提交到 Solr 中去了。你可以指定索引中最多包含百分之多少的被删除文档。
注意:天下没有免费的午餐。删除文档数的减少是以增加 I/O 以及 CPU 利用率为代价。如果删除文档的百分比很重要,那么在非访问高峰期进行清理会更加可取。
为什么是清理而不是强制合并/优化?这个点在于你是否愿意消耗 4.999G 的资源去重写一个段来换取 1 个文档的资源。
按照优先顺序排序:
- 除非你有充分的理由去清理掉删除的文档,否则不要担心这个,让默认的设置去控制就好了
- 当 LUCENE-8263 开发完时(可能是 Solr 7.5),在 TMP (solrconfig.xml)中分配一个删除数的百分比,然后测试,测试,再测试。定期索引时会增加 I/O 以及 CPU 利用率。特别是当你在测试环境测试时,负载的增加可能不是特别重要,但是一到了生产环境,就会变得特别重要。
- 周期性的执行 commit 来进行清理。不要瞎改 10% 这个默认值,它是在空间与 I/O 之间的一个权衡。Lucene 会跳过已经删除的文档,主要的开销是在磁盘空间以及内存。如果这些资源都不短缺,那么就随它去吧。
- 通过 maxSegments = 1 来进行优化/强制合并。如果(且仅当)你能够接受周期性的运行这个命令。一个典型的模式是在非高峰期一天更新一次索引,你可以使用优化/强制合并。
优化/强制合并的性能很好,但仍然是一个开销很大的操作。我强烈建议你不要在没有慎重考虑会带来什么后果的前提下不要做这些操作。更可怕的事是客户端每次提交都去做这些操作。事实上,我们不鼓励从客户端进行基本的提交操作。
如果你在测试而不是意淫之后发现优化/强制合并以及清理是有有益的,可以通过一个定时任务在非高峰期来做这些操作。