Elasticsearchで全文検索サービスを作るときに最初に見るべきだったリンク集

## 結論

Elasticsearchをキャッチアップするときはまずは[最新版の公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)を読みましょう。

### やってしまったこと

* 業務利用(全文検索サービス作成)でElasticsearchを使う機会があり、Elasticsearchのキャッチアップが必要になった
* とりあえず「Elasticsearch 入門」でググると、**「この記事は1年前に作られました」の文字が気になりつつも**良さげな実用サンプル付きの日本語記事が複数ヒット
* 最低限の構成でクエリ検索できるところまでならスムーズに行けるだろうと思いながら記事の通りに手を動かしてみると、思いの外いろいろなところでハマってしまった
  - Dockerイメージが立ち上がらない、インデックスが作れない、日本語検索のスコアがおかしい(日本語の構文解析ができていない)、etc...

### ハマった原因

* Elasticsearchメジャーバージョン間では仕様が大きく異なる箇所が幾つかある
  - 例えばElasticsearch5系→6系に上がる際は[インデックス内複数タイプの使用が不可能](https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_indices_changes.html)になったため、それより前(~2017年8月)に書かれた記事通りに動かすとエラーになるケースがある
  - さらに直近の6系→7系アップデートでは更に[タイプ自体が廃止](https://www.elastic.co/jp/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0)(※7系は受け入れAPIのみ。全体廃止は8系予定とのこと)の方針となったため、インデックス作成時のマッピング定義でタイプフィールドが含まれるものはもれなくエラーとなってしまう
* 「Elasticsearch 入門」などで上位にヒットする日本語記事の幾つかは前バージョンのものを前提にしていることに加え、Elasticsearch自体も頻繁にメジャーバージョンアップデートを行っておりかつ仕様変更も多い
  - このような背景から、「とりあえず日本語の入門記事から取り掛かろう！」とする人たちはハマりやすいのでは？と思いました(私はハマりました…)

### この記事の目的

* そもそもハマらないようにするにはまず最初に[公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)を読むべき
  - とはいえ公式ドキュメントは全編英語(少なくともCurrentバージョンは)かつ分量が多く(チャプターだけでも29あり、全セクションだと1,000ほどあります)すべてを読み通してから手を付けるのはかなり大変
  - そこで、自分がハマったあとに改めて参照した公式ドキュメント内ページを**ピックアップ**し、「やりたいこととそれが載っているページ(+ハマった経験を基にしたコメント)の一覧」を作ってみました
  - 公式ドキュメントの読み進め方の1つとして、これからキャッチアップする人が少しでも参考になれば幸いです

### 注意事項

* この記事自体も2019年12月現在のバージョン`7.5`に依存して書かれているため、仕様変更等で将来的に遷移先URLがリンク切れになっている場合や、解決しない場合が起こりうるかもしれません
  - [Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)から各ページをそれぞれ参照していくのが確実

## 公式ドキュメントの基本の歩き方

### [Elastic Stack and Product Documentation](https://www.elastic.co/guide/index.html)
* Elastic社の提供する諸製品(Elasticsearch, Kibana, Beats, Logstash)が網羅されているページ
* 分量がとてつもなく多いので、とりあえずElasticsearchを使いたい場合は後回しで見たほうがいいかも

### [Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)

上記の [Elastic Stack and Product Documentation](https://www.elastic.co/guide/index.html) -> Elasticsearch: Store, Search, and Analyze -> Elasticsearch Reference [<カレントバージョン>] よりアクセス可能

* Elasticsearchで分からないことがあれば、このページから辿って探していくのが基本
  -  …が、やはり1からすべて読み通そうとなるとかなりの分量となるため、まずはやりたいことベースで掻い摘んで読めるようになっていくのが良さそう
* URIは全ページ `/guide/en/elasticsearch/reference/<参照バージョン>/xxx.html` という構成なっており、`参照バージョン`箇所を`current`にするといつでも最新版の情報のページにアクセスできる(はず)です
  - Google検索から参照した場合や外部記事の参考文献から飛んだ場合、旧バージョンのページが表示されることがありうるので注意
  - また各ページの冒頭には所謂パンくずリスト(下図)があり、チャプター・セクション・ページごとの位置関係を把握しておくと効率よくキャッチアップできると思います
  - <img width="1295" alt="Install_Elasticsearch_with_Docker___Elasticsearch_Reference__7_5____Elastic.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308138/2a02fed9-4832-f18f-efc0-6816e9fcf033.png">


## やりたいこと別、最初に見ておくと良さそうな公式ドキュメント内ページ一覧

### インストール編

#### docker-compose.ymlを書いて、最低限の構成で立ち上がるようにする

**[Install Elasticsearch with Docker | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html)**

* 最小構成の場合は「[Starting a single node cluster with Docker](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-cli-run-dev-mode)」に従う
  - 同ページ中程にいい感じの`docker-compose.yml`がありますが、そちらはmulti-node cluster(複数ノード構成)なのでキャッチアップとしては少し複雑になってしまう
  - 以下のように「Starting a single node cluster with Docker」記載の`docker run`コマンドを`docker-compose.yml`向けに書き換えるのが良さそう

```yml:docker-compose.yml
version: '2'
services:
  elasticesarch:
    build: docker/elasticsearch # 後述のDockerfileを参照
    container_name: elasticsearch
    restart: always
    ports:
      - "9200:9200"
      - "9300:9300"
    environment:
      - "discovery.type=single-node"
```

ただしドキュメント記載の通り、single node(単体ノード構成)はテストor開発環境のみの使用とすべきです。念の為。

### セットアップ編

#### 日本語で検索ができるようにする

**[Japanese (kuromoji) Analysis Plugin | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)**

[Elasticsearch Plugins and Integrations](https://www.elastic.co/guide/en/elasticsearch/plugins/current/index.html) -> Analysis Plugins -> Japanese (kuromoji) Analysis Plugin より参照可能

* Elasticsearchはデフォルトで日本語解析プラグインが未インストールのため、日本語解析・および検索をするためには上記ドキュメント記載のインストール手順を別途実行する必要あり
* Dockerの場合はDockerfileに以下のように書いておくと、立ち上げ時にインストール済み状態になってくれます
  - `<タグ名>`は前項のInstall Elasticsearch with Dockerからアクセス可能な[docker.elastic.co](https://www.docker.elastic.co/)の一覧から選択

```dockerfile:docker/elasticsearch/Dockerfile
FROM docker.elastic.co/elasticsearch/elasticsearch:<タグ名>
RUN elasticsearch-plugin install analysis-kuromoji
```

* 因みにAWSで提供されているマネージド型ElasticsearchServiceを使う場合は、予めkuromoji-analyzerがインストールされているためこの作業は不要です(参照: [AWS公式ドキュメント: サポートされるプラグイン一覧](https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-supported-plugins.html))

#### 日本語解析が可能なインデックスを作る

**[analyzer | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer.html)**

* Elasticsearchは動的マッピング機能があるため、そもそもインデックスを作る際のマッピング定義の指定は必須ではない
* しかしデフォルトの定義ではAnalyzer(構文解析)が[日本語に適していない](https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html)ため、そのまま日本語ドキュメントを投入すると**投入時点で日本語文字が1文字ずつ細切れで格納されてしまい正しい検索スコアが出せない**
* とりあえず最低限の日本語解析がやりたい場合は、マッピング定義内のproperties内オブジェクトごとにAnalyzerの指定をすればOK
  - フィルター等を組み合わせることでより高度な構文解析ができたり、properties内オブジェクトごとにAnalyzeの有効/無効を切り替えてパフォーマンスチューニングしたりできますが今回は割愛…
  - 公式ドキュメントにはすぐに使える日本語解析用のいい感じのサンプルがないため、このあたりはElasticsearchのバージョン違いによる仕様変更に注意(↓のJSON内コメント参照)しつつ「elasticsearch kuromoji mapping」等でググりつつ日本語記事を探すのが良いかなと思います

```bash
curl -X PUT http://localhost:9200/hoge-index -H 'Content-Type: application/json' -d @mapping.json
```

```json:mapping.json
{
  "mappings": { // 6系以前は直下の行にtypeフィールドがあったが7系以降は廃止された
    "properties": {
      "article": { 
        "type":     "text", // <- 6系以降は"string"を使うとエラーになる
        "analyzer": "kuromoji" // <- 日本語構文解析用のAnalyzerを指定
      }
    }
  }
}
```

実行後は、正しく生成できたかどうか確認しておくと良いです。

```bash
$ curl localhost:9200/hoge-index?pretty
{
  "hoge-index" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "article" : {
          "type" : "text",
          "analyzer" : "kuromoji"
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "xxx",
        "number_of_shards" : "1",
        "number_of_replicas" : "1",
        "uuid" : "xxx",
        "version" : {
          "created" : "xxx"
        },
        "provided_name" : "hoge-index"
      }
    }
  }
}
```

#### ドキュメントを投入する(バルクインサート)

**[Bulk API | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html)**

* [PUTで1レコードずつ挿入もできる](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html)が、上記内ページ記載の通りバルクインサートのほうがパフォーマンスが良くなるらしい
  - スクリプト書くにしてもBulk API使ったほうが勝手が良いかと
* バージョン違いによる仕様変更はあまり無いので、実用的なサンプルとして旧バージョンの日本語記事を参照しても大丈夫そう
  - 7系以降で`_type`指定が廃止された点のみ注意
* AWSのマネージド型Elasticsearchの場合は、[HTTPリクエストペイロードのサイズに制限がある](https://docs.aws.amazon.com/ja_jp/elasticsearch-service/latest/developerguide/aes-limits.html)ため、大量データのバルクインサート用スクリプトを作る際は分割しながら送るようにする必要があることに注意

### 検索編

**[Query DSL | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html)**

* インストールとセットアップの山さえ乗り越えてしまえば、検索クエリのキャッチアップはそこまで大変ではないと思うのでさっくりとリンクのみ紹介
  - インストールとセットアップはエラーの度にインデックス削除＆再生成が必要だが、検索クエリは再度投げるだけなのでトライ＆エラーのテンポが速くなる
* 基本的には上記のQuery DSLの必要そうな記事を読み進めればOK
  - 以下、やりたいこととマッチしそうな記事をピックアップしました

#### 完全一致で検索する(キーワード検索)

**[Term query | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-term-query.html)**

#### 日本語の表記揺れも含めて検索する(全文検索)

**[Match query | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html)**

#### 複数のフィールドや検索条件から検索する

**[Boolean query | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html)**

#### OR検索やAND検索を可能にする

**[Query string query | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html)**
**[Simple query string query | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html)**

* OR検索やAND検索のみであればSinmple query string queryでOK
* 外部公開する場合などはSimple query string queryを使い、かつ機能制限をかけたほうが安全
  - Query string queryは複雑なクエリを簡単に投げられるので、サーバリソース消費の攻撃を受けるリスクが高い

#### 検索結果にスニペットとハイライト表示を含める

**[Request Body Search#Highlighting | Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-highlighting)**

* スニペット？ハイライト？と最初は思っていたので、Google検索画面を例として説明してみると、下図のような検索ヒット対象の文章の一部と単語の箇所のハイライト表示(`<highlight>`タグで格納される)が取得できるイメージ
  - <img width="1222" alt="elasticsearch_-_Google_検索.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308138/88866b93-368c-9356-5993-dd170f20f127.png">
  - 公式ドキュメントのNOTE記載の通り、計算コストが高い機能のため、検索結果の全件に対して適応してしまうと途端にパフォーマンスが悪くなってしまうので注意。[ページネーション機能を利用](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-from-size)して、全件ではなくページ表示対象の件数分にのみ適応させる工夫が必要


## おわりに

とりあえず上記で挙げたあたりの記事を読めば、最低限動くものが作れるようになると思います。
これからのステップアップの目標としては「検索精度を上げたい」「パフォーマンスを向上させたい」などといったことを決めてから

* まずは現行バージョンの公式ドキュメントを読みながら
* 必要に応じて日本語検索用の実装サンプルを(記事を書いた人の想定バージョンに注意しながら)ググっていく

スタイルで進めていくのがいいかなと思います。