SpringData Elasticsearchでインデックスにエイリアスを貼る

# はじめに

`SpringData Elasticsearch` を利用していて、日毎にドキュメントの全件洗い替えを行いたかった。
その手段を考えた時に、エイリアス機能が使えないかと目星をつけ、実際に洗い替えが実現できたので作業ログを公開します。

# 概要

インデックスの洗い替えを考える。
現在日付を `01/11` とする時を想定。

- A. Index-01/10 (バックアップ)
- B. Index-01/11 (利用中)
- C. Index-01/12 (今から作成する)

のように考える。
`01/12 00:00` 以降に定時バッチを起動する。

1. `C. Index-01/12` の名称でインデックスを生成する
1. RDBからインデックスしたいレコードを検索する
  論理削除された情報は絞り込みで除外する
1. `C.` にインデックスしたいドキュメント(2.の内容)を追加する

正常系: 
  1. エイリアスに `C.` を追加する  
  1. エイリアスから `B.` を削除する  
  1. `A.` を物理削除する  

異常系: 
  1. エラーを出力する  
  1. `C. Index-01/12` を削除する  

# 試した環境

| ツール/ライブラリ        | バージョン     | 
| ------------------------ | -------------- | 
| Elasticsearch            | 7.16.2         | 
| SpringData Elasticsearch | 4.4.0-SNAPSHOT | 
| SpringBoot               | 2.7.0-SNAPSHOT | 

# 実装

## DTO

```java:LocationDocument
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.TypeAlias;
import org.springframework.data.elasticsearch.annotations.Document;

@TypeAlias("location")
@Document(indexName = "location-#{T(java.time.LocalDate).now().toString()}", createIndex = false)
public class LocationDocument extends BaseDocument<String> {

  public static final String ALIAS = "location";
  public static final String INDEX_NAME = "location-{yyyy-MM-dd}";

  @Id
  private String id;
}  
```

`@TypeAlias` のおかげで、 `location` という名前でアクセスできるインデックスが作成される。実態は `location-2022-01-10` のような形式である。

## エイリアス貼り替え

```java:LocationRepositoryImpl
import lombok.RequiredArgsConstructor;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.index.AliasAction;
import org.springframework.data.elasticsearch.core.index.AliasActionParameters;
import org.springframework.data.elasticsearch.core.index.AliasActions;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class LocationRepositoryImpl implements LocationRepository {

  private final LocationDocumentSource locationDocumentSource;
  private final LocationTableSource locationTableSource;
  private final ReactiveElasticsearchOperations elasticsearchOperations;

  @Override
  public void replacement(Instant receptionTime) {
    var today = LocalDate.now();
    var newIndexName = LocationDocument.INDEX_NAME
        .replace("{yyyy-MM-dd}", today.toString());
    var oldIndexName = LocationDocument.INDEX_NAME
        .replace("{yyyy-MM-dd}", today.minusDays(1L).toString());

    // index作成
    var indexOperations = this.elasticsearchOperations.indexOps(LocationDocument.class);

    indexOperations.create().block();

    // 直す
    var count = this.locationTableSource.findAll()
        .filter(BaseTable::isExists)
        .map(LocationTable::toEntity)
        .map(new LocationDocument()::attach)
        .flatMap(dto -> this.locationDocumentSource.insert(dto, receptionTime))
        .count()
        .block();

    indexOperations.alias(
        new AliasActions()
            .add(
                // Aliasに新しいindexを追加する
                new AliasAction.Add(
                    AliasActionParameters.builder()
                        .withIndices(newIndexName)
                        .withAliases(LocationDocument.ALIAS)
                        .build()
                )
            )
           .add(
               // Aliasから古いindexを削除する
               new AliasAction.Remove(
                   AliasActionParameters.builder()
                       .withIndices(oldIndexName)
                       .withAliases(LocationDocument.ALIAS)
                       .build()
               )
           )
        )
        .block();
  }

}
```

# 説明

1. `ReactiveElasticsearchOperations` or `ElasticsearchOperations` をDIしておく
1. `AliasActions` でエイリアスの操作を指定する
    エイリアスに `C.` を追加する  
    エイリアスから `B.` を削除する  
1. `ReactiveElasticsearchOperations` or `ElasticsearchOperations` から `ReactiveIndexOperations` or `IndexOperations` を生成する
1. `ReactiveIndexOperations.alias()` or `IndexOperations.alias()` に `AliasActions` を指定する

日毎に洗い替えを行いたい場合、日付が変わった後にバッチなどで洗い替え処理を行えばよい。