【入門】Elasticsearchをざっくり解説してみた  #1 概要編

プロジェクトでElasticsearchに触ることになったので勉強したことを備忘録としてまとめていきます。
試行錯誤しながら勉強しているのでもしかしたら間違っているところもあるかもしれませんがその時は指摘していただけると嬉しいです。

# そもそもElasticsearchって何？
まずここからでした。
聞いたことはあるし"search"って入っているから検索するためのものではあるんだろうけど何ができるのだろうか。。。
ということで色々調べてみると、Elasticsearchはどうやら**全文検索エンジン**であることが分かりました。

## 全文検索とは
全文検索とは複数の文書から特定の文字列を検索することであり、grep型とインデックス型の2種類が存在します。

grep型は馴染みのある方も多いかと思いますが、そう、Linuxのgrepコマンドが代表例です。
grep型は複数のテキストファイルの内容を順次走査していくことで検索対象となる文字列を探し出します。
しかし、ファイルを順次走査していくため、検索対象が増えれば増えるほど検索速度が低下していきます。

そこで登場するのがインデックス型であり、Elasticsearchもこのインデックス型に該当します。
インデックス型ではあらかじめ検索対象となる文書群を走査して転置インデックスデータを準備し、
それにアクセスすることで高速検索を可能にします。

## 転置インデックスとは
転置インデックスとは、ある文字列が検索対象となるドキュメント群のどの位置に存在するかを示す索引構造を指します。
分かりやすい例として、辞書の索引をイメージしていただくとよいと思います。
辞書の索引には各単語が何ページ目に載っているかが一覧で記載されているので、私たちは分厚いページを１ページずつ確認することなく目的の単語がどのページに載っているかを調べることができます。これは本における転置インデックスです。

辞書の例だけでは具体的なイメージが難しいと思いますので、実際にElasticsearchで作成されるインデックスのイメージを示します。

Elasticsearchを使用しているあるSNSアプリがあり、そこに2つの文書Doc1とDoc2を投稿するとします。
`Doc1: “ 今日のサッカーの試合は日本とブラジルだから絶対に見る ”`
`Doc2: ” 今年のブラジル代表は面白いサッカーをしているなと思う “`

Elasticsearchにこれらを保存した際、文字列を分割するルール（重要なポイントですが説明簡略化のために一旦割愛）に則って下記のようなインデックスが作成されます。

```
"今日": Doc1
"サッカー": Doc1, Doc2
"試合": Doc1
"日本": Doc1
"ブラジル": Doc1, Doc2
"絶対": Doc1
"見る": Doc1
"今年": Doc2
"代表": Doc2
"面白い": Doc2
"思う": Doc2
※実際は文字列が文書中のどの位置にあるかなどの情報も格納されます。
```
この転置インデックスにより、SNSアプリで私たちが"ブラジル"と検索すればDoc1とDoc2が、
"日本"と検索すればDoc1のみが検索にhitすることとなります。

## Elasticsearchの用途
Elasticsearchが転置インデックスを利用した検索エンジンだという説明をざっくりしたところで、具体的にどのような用途で利用されているのかを[公式サイト](https://www.elastic.co/jp/what-is/elasticsearch)から引用しました。

- アプリ検索
- Webサイト検索
- エンタープライズサーチ
- ロギングとログ分析
- インフラメトリックとコンテナー監視
- アプリケーションパフォーマンス監視（APM）
- 地理空間データ分析と可視化
- セキュリティ分析
- ビジネス分析

私が参画しているプロジェクトではアプリ検索として活用しています。
Twitterのようなサービスで他の人の投稿を検索する使い方をイメージすると分かりやすいと思います。
あとはLogstashやKibanaと併せてLog解析基盤を構築する使用例もよくネット上で見かけます。
色々な用途がありますが、大量データの検索を高速に行いたい場合に適しているということですね。

## まずは触ってみる
何はともあれ実際に触るのが理解するのには一番です。
セットアップはDockerを使います。公式リファレンスの通りに[docker imageをpull](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#_pull_the_elasticsearch_docker_image)したら、[Elasticsearchをシングルノードでセットアップ](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html#docker-cli-run-dev-mode)してください。基本的な動作を検証するだけであればこれで十分です。
そのあとは可視化ツールの[Kibana](https://www.elastic.co/guide/en/kibana/8.6/docker.html#run-kibana-on-docker-for-dev)をDockerで起動できるようにします。
ブラウザでKibanaにアクセスできるようになれば準備OKです。

Kibanaにログインしたら、まずは Dev Tools を開きます。
Dev Toolsはコンソール左上の ハンバーガーメニュー→Managementセクション の中にあります。

では、基本的な操作としてデータの作成と検索を行います。

コンソールを開いたら、画像のように下のテキストを貼り付けてから黒塗りの右矢印ボタンを押して実行してください
![スクリーンショット 2023-03-04 20.45.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3194146/658bbfae-4394-8c68-3456-818224da60db.png)

```
POST sample/_doc
{
  "user_name": "tanaka_taro123",
  "user_id": 1,
  "crated_at": "2023-03-01T10:04:40.241",
  "tweet": "今日観たスラムダンクの映画は今までに観たどの映画よりも感動したなぁ",
  "likes": [
    {
      "likedBy": 2,
      "likedAt": "2023-03-04T12:03:10.109"
    },
    {
      "likedBy": 3,
      "likedAt": "2023-03-04T12:06:15.111"
    }
  ]
}
```

下のような実行結果がDev Tools右側に表示されれば処理は成功です。
```
{
  "_index": "sample",
  "_id": "2bHPrIYBMaUGLwUHbxju",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}
```

今の操作でElasticsearchにデータを一件登録できました。
では、今行った内容を簡単に解説していきます。

まず、目につくのは１行目の"POST sample/_doc"です。
ElasticsearchはRESTfulなエンジンのためHTTPメソッドとリクエストURIを指定し、必要に応じてJSONでデータも渡します。
ここでは、POSTメソッドでsampleインデックスにドキュメントと呼ばれるデータを作成するようリクエストしています。

ドキュメントはRDBでいうところのレコードのようなものです。
ドキュメントには文字列や数値、真偽値や日付項目の他、配列やオブジェクトも格納することができます。

次に"sampleインデックス"についてです。
ここで言うインデックスとは上で述べた転置インデックスそのもの。。ではなく、ドキュメントの集合を指します。RDBでいうところのテーブルをイメージいただくと分かりやすいです。
ちなみに、転置インデックスは各ドキュメントの文字列項目（一部種類除く）に対して作成されています。
インデックスは項目定義情報やその他様々な設定をすることができます。
ちなみに、RDBの場合はテーブルを作成してからINSERTしますが、Elasticsearchは事前にインデックスが作成されていなければドキュメント作成時に自動的にインデックスと項目定義を作成してからドキュメントを作成してくれます（項目定義以外にも設定したいことは多いので大体は事前に定義すると思います）。


続いて、今作成したデータを検索します。
こちらをDev Toolsにコピペして実行してください。
GETメソッドで検索クエリをsampleインデックスに投げます。
```
GET sample/_search
{
  "query": {
    "match": {
      "tweet": {
        "query": "スラムダンクは面白い"
      }
    }
  }
}
```
完全一致しているわけではないのに先ほど作成したドキュメントがヒットします。
```
{
  "took": 1,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 0.5753642,
    "hits": [
      {
        "_index": "sample",
        "_id": "2LHCrIYBMaUGLwUHnxhP",
        "_score": 0.5753642,
        "_source": {
          "user_name": "tanaka_taro123",
          "user_id": 1,
          "crated_at": "2023-03-01T10:04:40.241",
          "tweet": "今日観たスラムダンクの映画は今までに観たどの映画よりも感動したなぁ",
          "likes": [
            {
              "likedBy": 2,
              "likedAt": "2023-03-04T12:03:10.109"
            },
            {
              "likedBy": 3,
              "likedAt": "2023-03-04T12:06:15.111"
            }
          ]
        }
      }
    ]
  }
}
```
検索クエリには様々な種類があり、ここではmatchクエリと呼ばれるクエリを使用しています。
matchクエリを使用すると、検索ワードに対して字句解析を実施した上で検索をかけます。
実際にどのような解析をしてから検索をしているのか確認します。

Elasticsearchでは字句解析が必要な場面でAnalyzerと呼ばれる機能を用います。
下記のリクエストでAnalyzerを指定して検索ワードの解析をシミュレートすることができます。
現在はAnalyzerの設定を特にしておらずデフォルトのstandardアナライザーが設定されていますのでそれを指定します。
```
POST sample/_analyze
{
  "analyzer": "standard",
  "text": "スラムダンクは面白い"
}
```
下記の結果が返ってきました。
```
{
  "tokens": [
    {
      "token": "スラムダンク",
      "start_offset": 0,
      "end_offset": 6,
      "type": "<KATAKANA>",
      "position": 0
    },
    {
      "token": "は",
      "start_offset": 6,
      "end_offset": 7,
      "type": "<HIRAGANA>",
      "position": 1
    },
    {
      "token": "面",
      "start_offset": 7,
      "end_offset": 8,
      "type": "<IDEOGRAPHIC>",
      "position": 2
    },
    {
      "token": "白",
      "start_offset": 8,
      "end_offset": 9,
      "type": "<IDEOGRAPHIC>",
      "position": 3
    },
    {
      "token": "い",
      "start_offset": 9,
      "end_offset": 10,
      "type": "<HIRAGANA>",
      "position": 4
    }
  ]
}
```
standardアナライザーが固有名詞"スラムダンク"を上手く抽出して転置インデックスを作成してくれているのが分かります。
ドキュメント作成時にもtweet項目の値に対してstandardアナライザーで転置インデックスが作成されているので、"スラムダンク"というキーワードで検索にヒットしたというわけです。

他にも、完全一致で検索できたり、ANDとORで複数のクエリを組み合わせたり、painless scriptと呼ばれるJavaに似たスクリプトで検索条件を記載できたりと様々な検索クエリがありますので、これらも今後まとめるつもりです。

今回はElasticsearchがどんなものかをざっくりと分かっていただけるようにまとめてみました。
次回以降はより詳細な機能に一つずつフォーカスしていきたいと思います。

## 補足: OpenSearchについて
Elasticsearch関連の検索しているとOpenSearchという全文検索エンジンを目にすることもあるかと思います。
OpenSearchはElasticsearchの兄弟分の全文検索エンジンです。
Elasticsearchは元はオープンソースであり、AWSでもElasticsearchのフルマネージドサービスが展開されていましたが、2021年2月のライセンス変更でElasticsearchがオープンソースでなくなりました。
平たく言うとElasticsearchを開発するElastic社はOSSであるElasticsearchを使ってお金儲けをするAmazonを快く思わなかったようです。
そこでAmazonはElasticsearchをforkしてOSSとして開発を進められるようにすると同時に、サービス名もAmazon OpenSearch Serviceに改名しました。
そういった背景からElasticsearchとOpenSearchは機能的に似た部分も多いため、
OpenSearchユーザはElasticsearchのドキュメントも参考にすると良いと思います（歴史が長いだけあってElasticsearchの方がドキュメントがしっかりしてる）。

## 参考サイト
- https://www.elastic.co/guide/en/elasticsearch/reference/current/elasticsearch-intro.html
- https://hub.docker.com/_/elasticsearch
- https://gihyo.jp/dev/serial/01/search-engine/0003