Sudachi同義語辞書によるElasticsearch検索を行えるようにする

# 前提
- macOS 13.0（22A380）
- Docker version 24.0.6, build ed223bc
- Elasticsearch 7.17.10
- elasticsearch-model (7.2.1)
- elasticsearch-sudachi 7.17.10

# 手順
## DockerでElasticsearch（Sudachi）を動かす
tokenizerはSudachiを使う（プラグインがあるので使う）

https://github.com/WorksApplications/elasticsearch-sudachi

基本的な流れは、[README](https://github.com/WorksApplications/elasticsearch-sudachi#installation)

作業に入る前に確認しておくことがある
プラグインとESでバージョンを揃える必要があるので、先にプラグインのバージョンを確認する
[プラグインの一覧](https://github.com/WorksApplications/elasticsearch-sudachi/releases)を確認して、今回は7系の最新版である elasticsearch-7.17.10-analysis-sudachi-3.1.0.zip を使う

まずは、同バージョンのESを用意する（[DockerでESを使う手順](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docker.html)）
```sh
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.10

docker run --detach --name es7-sudachi --net elastic -p 127.0.0.1:9200:9200 -p 127.0.0.1:9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.17.10
```

次に、プラグインを導入する
```sh
docker exec -it [コンテナID] /bin/bash

> bin/elasticsearch-plugin install https://github.com/WorksApplications/elasticsearch-sudachi/releases/download/v3.1.0/elasticsearch-7.17.10-analysis-sudachi-3.1.0.zip
```

辞書は別途用意する必要がある
[辞書はいくつか種類あって](https://github.com/WorksApplications/SudachiDict#dictionary-types)、今回はFull辞書を用意する（前段としてCore辞書を用意する必要がある）
[それぞれの辞書のリンク先](http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/)

```sh
# Core辞書を入れるディレクトリを用意
> mkdir /usr/share/elasticsearch/config/sudachi

> cd /tmp/

# Core辞書
> curl -O http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20230927-core.zip
> unzip sudachi-dictionary-20230927-core.zip
> cp sudachi-dictionary-20230927/system_core.dic /usr/share/elasticsearch/config/sudachi/

# Full辞書
> curl -O http://sudachi.s3-website-ap-northeast-1.amazonaws.com/sudachidict/sudachi-dictionary-20230927-full.zip
> unzip sudachi-dictionary-20230927-full.zip
> cp sudachi-dictionary-20230927/system_full.dic /usr/share/elasticsearch/plugins/analysis-sudachi/
```

最後に再起動すると、ひとまずSudachiを使ってのElasticsearch検索はできるようになる

```
docker restart
```

## Sudachi同義語辞書を用意する
[Sudachi 同義語辞書](https://github.com/WorksApplications/SudachiDict/blob/develop/docs/synonyms.md)が提供されているが、そのままのフォーマットだとESで利用できない

以下を使って、フォーマットを変換する

https://zenn.dev/sorami/articles/d19afb40fbb838#%E3%80%8Csolr-synonyms%E5%BD%A2%E5%BC%8F%E3%80%8D%E3%81%B8%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B

変換結果をコンテナ内に持って行く

```
docker cp synonym.txt [コンテナID]:/usr/share/elasticsearch/data/
```

## indexを作成する
[elasticsearch-model](https://github.com/elastic/elasticsearch-rails/tree/main/elasticsearch-model)を使っている前提

該当モデルに↓の設定を追加して、create_index!

```ruby
...
  settings index: { number_of_shards: 1 } do
    mappings dynamic: 'false' do
      indexes :value, type: 'text', analyzer: 'sudachi_analyzer'
    end
  end

  settings analysis: {
    "tokenizer": {
      "sudachi_tokenizer": {
        "type": "sudachi_tokenizer",
        "discard_punctuation": true,
        "resources_path": "/usr/share/elasticsearch/plugins/analysis-sudachi/",
        "additional_settings": "{\"systemDict\":\"system_full.dic\"}"
      }
    },
    "filter": {
      "sudachi_synonym": {
        "type": "synonym_graph",
        "lenient": true,
        "synonyms_path": "/usr/share/elasticsearch/data/synonym.txt"
      }
    },
    "analyzer": {
      "sudachi_analyzer": {
        "tokenizer": "sudachi_tokenizer",
        "type": "custom",
        "char_filter": [],
        "filter": [
          "sudachi_part_of_speech",
          "sudachi_ja_stop",
          "sudachi_baseform",
          "sudachi_synonym"
        ]
      }
    }
  }
...
```

ポイントとしては、
- Full辞書を利用するために、 resources_path、additional_settings を設定する
    - https://github.com/WorksApplications/elasticsearch-sudachi#configuration
- 同義語辞書内の単語を解析できなくて失敗した際にスルーするため、lenient を設定する
    - https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-graph-tokenfilter.html#_solr_synonyms_2:~:text=to%20true).-,lenient,-(defaults%20to%20false

## 動作確認する
Kibanaから見れるようにする
```
docker run --detach --name kib7-sudachi --net elastic -p 127.0.0.1:5601:5601 -e "ELASTICSEARCH_HOSTS=http://es-sudachi:9200" docker.elastic.co/kibana/kibana:7.17.6
```

http://127.0.0.1:5601/app/dev_tools#/console にアクセスして、

```
GET [index名]/_analyze
{
  "analyzer": "sudachi_analyzer",
  "text" : "アイスクリーム"
}

=>

{
  "tokens" : [
    {
      "token" : "アイスクリーム",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "ice cream",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "アイス",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "SYNONYM",
      "position" : 0
    },
    {
      "token" : "ICE",
      "start_offset" : 0,
      "end_offset" : 7,
      "type" : "SYNONYM",
      "position" : 0
    }
  ]
}
```

おー、同義語でに解析されてる🎉