Solr8系のセグメントのマージの動作とsolrconfig.xmlの設定について(SolrCloud)

# 検証した環境

* Solr:8.6（Cloud シャード:1,NRT:2)

 [環境構築編](https://qiita.com/su_mi/items/7dc0c3c0b5f8ece98739)

# セグメント(Segment)とは

Solrはコミットをするとインデックスに書き込みをします、内部的にはセグメントと呼ばれる単位で分割されています。
Segmentはインデックスを更新してコミットを実行するたびに新しいセグメントが作成されます。

## 例1

### 100件のドキュメントを登録してコミットする

![スクリーンショット 2020-11-08 14.24.35（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/76a660d2-8d37-ca9c-9056-246caa810e60.png)

| パラメータ | 数値 |
|:-----------|------------:|
|Num Docs|100|
|MaxDoc|100|
|Deleted Docs|0|
|Segment Count|1|

_0 -> docs:100, dels:0

### 更に30件新規のドキュメントを登録してコミットする

![スクリーンショット 2020-11-08 14.35.22（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/88100260-08a3-757b-c1dd-238156af0552.png)

| パラメータ | 数値 |
|:-----------|------------:|
|Num Docs|130|
|MaxDoc|130|
|Deleted Docs|0|
|Segment Count|2|

_0 -> docs:100, dels:0
_1 -> docs:30, dels:0

あとから登録した30件は_1のセグメントに含まれます。

## 例2

次は100件登録してコミットしたあとに30件のドキュメントを更新してコミットしてみます。

![スクリーンショット 2020-11-08 14.42.58（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/431ccfc5-9b7e-5979-0281-f8db46438afe.png)

| パラメータ | 数値 |
|:-----------|------------:|
|Num Docs|100|
|MaxDoc|130|
|Deleted Docs|30|
|Segment Count|2|

_0 -> docs:100, dels:30
_1 -> docs:30, dels:0

ドキュメントの更新の場合は0のセグメントのうち、1のセグメントに含まれるドキュメントは削除済みとして扱われます。

# セグメントのマージ

ドキュメントの追加、更新、削除を繰り返すとセグメント数が増えていきます。
セグメント数が多くなると検索性能が劣化していきます。
また例２の場合のように削除済みとしてマークされているものは検索時には使用されないですがディスク上に残ってしまうので無駄になってしまいます。
なのでsolrではセグメントをマージする機能があります。

## セグメントのマージ方法

1. optimizeによる強制マージ
2. コミット時にMergePolicyによるマージ
3. expungeDeletesによるマージ

### optimizeによる強制マージ

※ optimizeによる強制マージはあまり推奨されていないようです。  MergePolicyを利用したマージを使うほうが主流のようです。

```
curl -X POST -H 'Content-Type: application/json' 'http://localhost:8981/solr/{collection}/update' --data-binary '
{
  "optimize": {maxSegments:1}
}'
```

### MergePolicyによるマージ
今回はデフォルトのマージポリシーのTieredMergePolicyについてまとめます。

#### マージに関連するsolrconfig.xmlの設定

```solrconfig.xml
<config>
  <indexConfig>
        <mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
          <int name="segmentsPerTier">10</int>
          <int name="floorSegmentMB">2</int>
          <int name="deletesPctAllowed">33</int>
          <int name="maxMergedSegmentMB">5000</int>
          <int name="maxMergeAtOnce">10</int>
          <int name="forceMergeDeletesPctAllowed">10</int>
        </mergePolicyFactory>
  </indexConfig>
</config>
```

#### マージのしきい値となる設定

##### segmentsPerTier(デフォルト値:10)

階層のSegment Countがしきい値(allowedSegCount)の設定値を超えると、次回のコミット時にマージが実行されます

```math
SegmentCount > allowedSegCount
```

![スクリーンショット 2020-11-08 15.17.50（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/fb09a116-0738-0716-6eb0-05c1abc0c71a.png)

デフォルトの10だとSegment Countが11になるとしきい値を超えるので次のコミット時にマージされます。
（※ドキュメントのトータルのサイズが小さい時）

![スクリーンショット 2020-11-08 15.18.26（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/b8cc0a8c-c7a0-ab27-0b82-3c3a8af9b72f.png)

「0」から「9」がマージされbになる

###### allowedSegCountの算出方法

TieredMergePolicyでは以下のロジックでallowedSegCountが決定します。

```java
    final int mergeFactor = (int) Math.min(maxMergeAtOnce, segsPerTier);
    long levelSize = Math.max(minSegmentBytes, floorSegmentBytes);
    long bytesLeft = totIndexBytes;
    double allowedSegCount = 0;
    while (true) {
      final double segCountLevel = bytesLeft / (double) levelSize;
      if (segCountLevel < segsPerTier || levelSize == maxMergedSegmentBytes) {
        allowedSegCount += Math.ceil(segCountLevel);
        break;
      }
      allowedSegCount += segsPerTier;
      bytesLeft -= segsPerTier * levelSize;
      levelSize = Math.min(maxMergedSegmentBytes, levelSize * mergeFactor);
    }
    allowedSegCount = Math.max(allowedSegCount, segsPerTier);
```

```math
floorSegmentBytes = floorSegmentMB * 1024 * 1024
```
solrconfig.xmlで指定できる、floorSegmentMBの値を上げるとallowedSegCountの値も上がりにくくなります。


floorSegmentMB=1と設定した場合

![スクリーンショット 2020-11-09 11.54.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/ab1181e1-9d50-f67c-dd3e-b7f16c97f871.png)

ドキュメントのトータルサイズが、10MBを超えるとallowedSegCount=12に変化します。


##### deletesPctAllowed(デフォルト値:33)

Deletionsの割合がdeletesPctAllowedを超えると、次回のコミット時にマージが実行されます

```math
Deletions = \frac{Deleted Docs}{MaxDoc} * 100 > deletesPctAllowed
```
![スクリーンショット 2020-11-08 15.28.11（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/e6993298-5a69-34bd-022c-37eae5ed515d.png)



![スクリーンショット 2020-11-08 15.29.25（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/8ea15e7b-7c28-d5d0-6956-8cf3b096aa2f.png)

33%超えるとピンク色になるので次回のコミット時にマージされるのがわかります。

#### マージするセグメント数の設定

##### maxMergeAtOnce(デフォルト値:10)

マージが実行されるときにmaxMergeAtOnceの値までのセグメントがマージの対象となります

maxMergeAtOnce=2,maxMergedSegmentMB=5000の場合

![スクリーンショット 2020-11-08 15.39.31（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/bb86e7d8-7c54-e580-32d4-fad83ee003b8.png)

マージ対象がmaxMergeAtOnceの0,1だけが対象となります。

#### maxMergedSegmentMB(デフォルト値:5000)
マージが実行されるときにマージするセグメントの合計サイズがmaxMergedSegmentMBを超えない範囲が対象となります。

maxMergeAtOnce=10,maxMergedSegmentMB=1の場合

![スクリーンショット 2020-11-08 15.50.40（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/0ad90c15-1320-48e3-7686-5dbf5c8b82af.png)

この場合は0から4までがmaxMergedSegmentMBの値に収まるのでマージの対象となっていることがわかります。

### expungeDeletes

コミット時にexpungeDeletesというのを指定することができます。

```
curl -X POST -H 'Content-Type: application/json' 'http://localhost:8981/solr/{collection}/update' --data-binary '
{
  "commit": {expungeDeletes: true}
}'
```

expungeDeletesを指定すると削除済みが含まれるセグメントがマージされます。
このときセグメント単体でみた削除のドキュメントの割合がforceMergeDeletesPctAllowed以下の場合は対象外となります。

![スクリーンショット 2020-11-08 15.57.33（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/587793f8-2633-cc36-6bc0-33a5c30018e1.png)

expungeDeletesを実行

![スクリーンショット 2020-11-08 15.58.03（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/70971/226845b9-6d29-0ca1-e68e-1b1f5177731f.png)

削除を含むセグメント0と1がマージされ4になりました。