🕌

ハンズオンで理解するElasticsearchの用語

2023/11/20に公開

自己紹介

Twitter(X): @tk_hirom
GitHub: tk_hirom
Web系4年目。サーバーサイド中心でコード品質を高め、より価値の高いサービスをデリバリーすることに関心があります。
今回は、現場で触ることになったElasticsearchについて、1から学び直したのでその記録として記事を書くこととしました。RDBしか触ったことがなく、分散型のデータベースは初めてなので右も左もわからん状態です

Elasticsearchとは

様々なユースケースを解決する分散型RESTful検索/分析エンジンです。データを一元的に格納することで、超高速検索や、関連性の細かな調整、パワフルな分析が大規模に、手軽に実行可能になります

要は、APIでアクセスできる高速な検索エンジンということのようですね

特徴

  • HTTPのRESTful APIを通じてデータのCRUD操作ができる
  • JSON形式でデータを保持する
  • 複数のサーバーにデータを分散させ、並列で検索を行えるので早い
  • スキーマレスで動的マッピングを行う(←これは後ほど触れます)

関連単語

ドキュメント

検索可能な最小単位。RDBの行に該当。JSON形式のデータ

インデックス

ドキュメントを保持するもの
RDBのテーブルに該当

シャード

インデックスを分割したもの
各サーバーはそれぞれこのシャードを持っている

プライマリシャード

ドキュメントがまず書き込まれるシャード
各インデックスはデフォルトでは一つもつ

レプリカシャード

プライマリシャードのコピー。一つのプライマリシャードに対して複数あることも
異なるノードに配置されることでバックアップの役割と、検索性能向上の役割を果たす

インバースインデックス

検索を早くするためのデータ構造
RDBのインデックスに該当

ノード

サーバーorインスタンスのこと

クラスタ

ノードのグループ。グループ内のノードは一緒に起動し、検索等を行う。
同じクラスタ内のノードが同じドキュメントを保持するかはシャード、レプリカの設定によって決まる

実際に使ってみた

大体のイメージがついたので触ってみました

今回はシンプルに一つのクラスタに一つのノードがあるもので試してみた。
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/docker.html#_pulling_the_image
公式がイメージを提供してくれているので、それをrunしていく
https://www.elastic.co/guide/en/elasticsearch/reference/7.9/docker.html#docker-cli-run-dev-mode

これで9200ポートでHTTP通信が可能なコンテナが立ち上がりました。
(9300ポートはノード間の内部通信に使用されます)

動作確認

GETリクエスを投げて起動確認してみましょう。

curl -X GET "localhost:9200/"

{
  "name" : "66e0d9dd2bb8", 
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "A1nHS3REQk6uajCxvcVbDQ",
  "version" : {
    "number" : "7.9.3",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "c4138e51121ef06a6404866cddc601906fe5c868",
    "build_date" : "2020-10-16T13:34:25.304557Z",
    "build_snapshot" : false,
    "lucene_version" : "8.6.2",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

正常に起動していれば、このようなjsonを返してくれるはずです。
これはノードと所属先のクラスタの情報です。
(他は見たまんまの内容ですが、taglineがよくわからなかったので調べたらElasticsearchのスローガンらしいです。)

インデックス作成

RDBでテーブルがないとデータが追加できないように、Elasticsearchもインデックスがないとドキュメントが追加できません

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html

curl -X PUT "localhost:9200/books" -H 'Content-Type: application/json' -d'
{
  "settings": {
    "number_of_shards": 2,
    "number_of_replicas": 1
  }
}'

ここではbooksインデックスを作ってみようと思います。
作成したいインデックス名に対してPUTすると作れます。
リクエストパラメータの説明をすると

number_of_shards: 今回作るインデックスのプライマリシャードの数。インデックスを幾つに分割(シャード)するかの設定値。今回は2にしてみる

number_ot_replicas: 各プライマリシャードに対するレプリカ(コピー)の数。今回は2つプライマリシャードを作るので、レプリカも2個つくられる

ドキュメントの追加

インデックスは作れたので、データの追加をしていきます

curl -X POST "localhost:9200/books/_doc" -H 'Content-Type: application/json' -d'
{
  "title": "Elasticsearchの用語と使い方が分かる君",
  "author": "tk-hirom",
  "price": 1000,
  "genre": ["Elasticsearch", "backend"]
}'

追加先のインデックスの後ろに _doc を指定し、リクエストを投げます。
jsonに追加したいドキュメントのフィールドを指定できます。

curl -X GET "localhost:9200/books/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match_all": {}
  }
}'

追加が正常にできたか確認するために、上記コマンドでbooksインデックス内のドキュメントを検索してみましょう。

データの検索

正常にデータが追加できていたら下記のようなレスポンスが返ってくるはずです。

{"took":58,"timed_out":false,"_shards":{"total":2,"successful":2,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.0,"hits":[{"_index":"books","_type":"_doc","_id":"bS9y5YsBv1MBEUijvkyR","_score":1.0,"_source":
{
  "title": "Elasticsearchの用語と使い方が分かる君",
  "author": "tk-hirom",
  "price": 1000,
  "genre": ["Elasticsearch", "backend"]
}}]}}%

ちなみに、

curl -X GET "localhost:9200/books/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
	    "title": "Elasticsearchの用語と使い方が分かる君"
    }
  }
}'

とmatchを使用すると、フィールドの値を指定して検索することができます。

titleはTEXT型なので、全文一致しなくてもヒットします

curl -X GET "localhost:9200/books/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
            "title": "Elasticsearchの使い方"
    }
  }
}'

-- 結果
{"took":5,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":1.4384104,"hits":[{"_index":"books","_type":"_doc","_id":"S_XUYIwBYz2W_BfYpX3n","_score":1.4384104,"_source":
{
  "title": "Elasticsearchの用語と使い方が分かる君",
  "author": "tk-hirom",
  "price": 1000,
  "genre": ["Elasticsearch", "backend"]
}}]}}%

-- keyword型だと完全一致しないと取れない
curl -X GET "localhost:9200/books/_search" -H 'Content-Type: application/json' -d'
{
  "query": {
    "match": {
            "genre": "Ela"
    }
  }
}'
-- 結果
{"took":5,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":0,"relation":"eq"},"max_score":null,"hits":[]}}%

一旦ストップ

疑問「え? インデックス作るときにテーブル定義とか指定しないで、ドキュメント追加の時に指定したということは一つのインデックスの中に異なるフィールドを持つドキュメントが入るということ??」

=> Exactly(その通りでございます。)

先ほどのbooksインデックスに下記のドキュメントを追加してみましょう。

curl -X POST "localhost:9200/books/_doc" -H 'Content-Type: application/json' -d'
{
  "title": "みんなと違うフィールドを持つ本",
  "author": "tk-hirom",
  "price": 1000,
  "genre": ["Elasticsearch", "backend"],
  "publication_years": "2023-01-01"
}'

なんと追加できるのです。
そう、これが上記で後ほど触れますと話していたElasticsearchの特徴スキーマレスです。
ドキュメントをインデックスに追加するとき、新しいフィールドがあればマッピング(インデックスのテーブル定義のようなもの)にそのフィールドを追加します。

ただ、あらかじめ明示的に宣言した方が実務では何かといい気がします
そんな時は下記のようにmappingsという形で指定することが可能です

curl -X PUT "localhost:9200/books" -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "author": {
        "type": "text"
      },
      "publication_date": {
        "type": "date",
        "format": "yyyy-MM-dd"
      },
      "price": {
        "type": "integer"
      }
    }
  },
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  }
}'

まとめ

今回はElasticsearchの単語の概念をコマンドを叩きながら理解していきました。
次回は、実際のプロダクトに埋め込んでみたり、大量のデータをRDBから引っ張ってくる時と比べてどのくらい早いのかなどもやってみたいと思います。

Discussion