Open2

Elasticsearch

beatsbeats

Elasticsearch とは

  • Elasticsearchとは検索処理に特化したオープンソースのソフトウェアである。
  • Javaで記述されており、同じくJavaで記述された検索エンジンライブラリであるApache Luceneがベースとなっている。
  • データはJSON形式で格納され、デフォルトで全てのフィールドにインデックスが作成されることで柔軟な検索を実現する。
  • データを収集するBeats、収集したデータを変換処理して送信するLogstash、格納されたデータを可視化するKibanaと併せてElasticStackと呼ばれる。

メリット

  • WIP

デメリット

  • WIP

基礎用語

ドキュメント

  • Elasticsearchにおけるデータの単位を指す。
    • JSON形式で表現される。
  • RDBにおけるレコードに相当する。

フィールド

  • ドキュメント(JSON)のkeyとvalueの組み合わせを指す。

ドキュメントの例

{ 
  "name": "Beats", 
  "age" : 26, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
} 

インデックス

  • ドキュメントを格納する領域を指す。
  • RDBにおけるテーブルに相当する。

タイプ

  • インデックス内のドキュメントの分類を指す概念であったが廃止が決定している。
  • Elasticsearch 5.X までは1つのインデックスに複数のタイプを定義できたので、RDBでいうテーブルに相当する概念であった。(その場合インデックスがRDBでいうスキーマに相当する)

Elasticではバージョン5.0よりElasticsearchのタイプ廃止に向けて取り組んできました。そして7.0より、タイプは完全に廃止されます。

https://www.elastic.co/jp/blog/moving-from-types-to-typeless-apis-in-elasticsearch-7-0

ノード

  • Elasticsearchが動作するサーバを指す。

クラスタ

スクリーンショット 2021-02-07 0.36.51.png

  • 複数のノードによって構成される、ノードのグループを指す。

シャード

スクリーンショット 2021-02-07 0.37.30.png

  • インデックスを分割してドキュメントを保存する際の、分割されたデータの単位を指す。
    • 作成されたシャードは複数のノードに分散して配置され、それによって検索が並行処理で行われ検索パフォーマンスが向上する。
  • 各インデックスに対するシャードの数はインデックス作成時に指定しておく必要がある。
  • 各シャードに対してレプリカを作成することができ、データの可用性を高めることも可能である。

データ操作

  • REAT APIによるアクセスが可能であり、リソースをURLで指定して操作内容をHTTPメソッドで指定してCRUD処理を行う。
  • ドキュメントの取得・更新だけでなくクラスタの管理などもURLアクセスによって操作可能である。
  • 新たにインデックスを作成する際に作成するドキュメントのスキーマ定義をする必要はなく、データの値から自動的にスキーマを生成してくれます。
    • 明示的にスキーマを定義することも可能である。
  • Kibana の Dev Tools を用いることでAPI操作をコンソール上から行える。

Create

  • Documentの新規作成はPUTもしくはPOSTを利用する。

PUTの場合

/{index}/{type}/{id}
PUT /member/_doc/1
{ 
  "name": "Beats", 
  "age" : 26, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
} 
{
  "_index" : "member",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

/{index}/_create/{id}
PUT /member/_create/1
{ 
  "name": "Beats", 
  "age" : 26, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
}

POSTの場合

  • POSTを用いる場合はIDを指定しなくても良い。(指定がない場合は自動採番)
/{index}/{type}
POST /member/_doc/
{ 
  "name": "Beats", 
  "age" : 26, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
} 
{
  "_index" : "member",
  "_type" : "_doc",
  "_id" : "4_1FfHcBun3pb7RqaiAK",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}

/{index}/_create/{id}
  • POSTでIDを指定するのは違和感があるが動作はする。
POST /member/_create/1
{ 
  "name": "Beats", 
  "age" : 26, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
}

READ

/{index}/{type}/{id}
GET /member/_doc/1
{
  "_index" : "member",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "Beats",
    "age" : 26,
    "birthday" : "1994-10-29",
    "blog_url" : "https://beatsbeats.hatenablog.com/",
    "job" : [
      "monk",
      "developer"
    ],
    "favorite_song" : {
      "title" : "fav_song",
      "created_at" : "2000-01-01"
    }
  }
}
/{index}/_source/{id}
  • インデックスの名前などのメタデータを除いた形で取得が可能である。
GET /member/_source/1
{
  "name" : "Beats",
  "age" : 26,
  "birthday" : "1994-10-29",
  "blog_url" : "https://beatsbeats.hatenablog.com/",
  "job" : [
    "monk",
    "developer"
  ],
  "favorite_song" : {
    "title" : "fav_song",
    "created_at" : "2000-01-01"
  }
}

UPDATE

/{index}/_update/{id}
  • 特定のフィールドのみを指定して更新する。
POST /member/_update/1
{ 
  "doc": {
    "name": "Beeeats", 
    "age" : 27, 
    "birthday": "1995-10-29",
    "blog_url": "",
    "job": ["developer"], 
    "favorite_song": {"title":"fav_song","created_at":"2000-01-03"}
  }
}
{
  "_index" : "member",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}
/{index}/{type}/{id}
  • Document全体を新たなDocumentに更新する。
PUT /member/_doc/1
{ 
  "name": "Beats", 
  "age" : 27, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
}
POST /member/_doc/1
{ 
  "name": "Beats", 
  "age" : 27, 
  "birthday": "1994-10-29",
  "blog_url": "https://beatsbeats.hatenablog.com/",
  "job": ["monk","developer"], 
  "favorite_song": {"title":"fav_song","created_at":"2000-01-01"}
}

DELETE

DELETE /member/_doc/1
{
  "_index" : "membe",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 2,
  "_primary_term" : 1
}

検索(GET or POST)

  • 検索処理時にはGETメソッドもしくはPOSTメソッドを使用し、Bodyにクエリを埋め込む。
  • 検索用のクエリも他のデータ操作時と同じようにJSON形式で記述する。
    • 検索用のクエリのフォーマットをクエリDSLと呼ぶ。
  • URLの末尾には_searchをつける必要があり、パスパラメータにより検索対象範囲を指定する。
  • 全てのインデックスに対して検索をかけたい場合はインデックスやタイプはパスに含めず、/_searchのみを付ける。
    • 例えばmemberというインデックスに対して検索をかける場合は、パスはmember/_doc/_searchとなる。
  • 全てのインデックスに対して検索をかけたい場合はインデックスやタイプはパスに含めず、末に/_searchのみを付ける。
  • クエリDSLには複数種類のクエリが存在する(本ドキュメントではmatch_all,match,term/terms,from/size,sort,range,boolを記載)

match_all

  • 全件取得するクエリである。

Request

POST /post/_doc/_search
{
    "query": {
        "match_all": {}
    }
}

Response

{
  "took" : 10,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "o3XwfHcBFY01GkKP0sAj",
        "_score" : 1.0,
        "_source" : {
          "title" : "this is a pen",
          "body" : "this pen is cool",
          "created_at" : "2020-10-29"
        }
      },
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "pXXwfHcBFY01GkKP3MDy",
        "_score" : 1.0,
        "_source" : {
          "title" : "that is a pen",
          "body" : "that pen is cute",
          "created_at" : "2021-11-24"
        }
      },
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "LnX2fHcBFY01GkKPgsG5",
        "_score" : 1.0,
        "_source" : {
          "query" : {
            "match_all" : {
              "field" : ""
            }
          }
        }
      }
    ]
  }
}

  • hitsフィールド内が検索結果のメタデータであり、続いてヒットしたデータが並ぶ。
    • hits.total.valueはヒットした件数を示し、hits.hits._scoreはクエリ条件とデータの一致度を数値化したものである。

match

  • 特定のフィールドを対象として、キーワード検索を行うクエリです。
  • キーワードを空白文字で区切ることで複数キーワードを用いた検索が可能である。(OR条件)
    • ORからANDに変更することも可能である。
  • 検索結果はデフォルトではスコア順でソートされています。

Request(OR)

POST /post/_doc/_search/
{
    "query": {
        "match": {
          "title": "this pen"
        }
    }
}

Response

{
  "took" : 65,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 0.8754687,
    "hits" : [
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "o3XwfHcBFY01GkKP0sAj",
        "_score" : 0.8754687,
        "_source" : {
          "title" : "this is a pen",
          "body" : "this pen is cool",
          "created_at" : "2020-10-29"
        }
      },
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "pXXwfHcBFY01GkKP3MDy",
        "_score" : 0.18232156,
        "_source" : {
          "title" : "that is a pen",
          "body" : "that pen is cute",
          "created_at" : "2021-11-24"
        }
      }
    ]
  }
}

Request(AND)

POST /post/_doc/_search/
{
    "query": {
        "match": {
          "title": {
            "query": "this pen",
            "operator": "and" 
          }
        }
    }
}

Response

{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.8754687,
    "hits" : [
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "o3XwfHcBFY01GkKP0sAj",
        "_score" : 0.8754687,
        "_source" : {
          "title" : "this is a pen",
          "body" : "this pen is cool",
          "created_at" : "2020-10-29"
        }
      }
    ]
  }
}

range

  • 範囲を指定して検索を実行するクエリである。
  • 値の範囲指定には下記のオペレータを用いる。
get(~以上)
lte(~以下)
gt(より大きい)
lt(より小さい)

Request

POST /post/_doc/_search
{
    "query": {
        "range": {
            "created_at": {
              "gte": "2020-10-01",
              "lte": "2020-11-01"
            }
        }
    }
}

Response

{
  "took" : 9,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "o3XwfHcBFY01GkKP0sAj",
        "_score" : 1.0,
        "_source" : {
          "title" : "this is a pen",
          "body" : "this pen is cool",
          "created_at" : "2020-10-29"
        }
      }
    ]
  }
}

bool

  • 基本クエリを組み合わせたクエリである。
    • 複数条件を組み合わせた細かな検索をしたいときに用いる。
  • boolクエリの中でさらに4つのクエリが用いられる。
{
    "query": {
        "bool": {
            "must": {},
            "should": {},
            "must_not": {},
            "fileter": {}
        }
    }
}

must

  • 基本クエリのAND条件となるクエリである。

should

  • 基本クエリのOR条件となるクエリである。

must_not

  • 基本クエリのNOT条件となるクエリである。

filter

  • 基本クエリのAND条件となるクエリである。(スコア加算なし)
  • 完全一致検索時などスコアが必要ない場合はmustではなくfilterを使用する。

Request

POST /post/_doc/_search/
{
    "query": {
        "bool": {
            "must": [
              {"match": {"title": "is"}},
              {"match": {"title": "pen"}}
            ],
            "must_not": [
              {"match": {"body": "cute"}}
            ]
        }
    }
}

Response

{
  "took" : 15,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.36464313,
    "hits" : [
      {
        "_index" : "post",
        "_type" : "_doc",
        "_id" : "o3XwfHcBFY01GkKP0sAj",
        "_score" : 0.36464313,
        "_source" : {
          "title" : "this is a pen",
          "body" : "this pen is cool",
          "created_at" : "2020-10-29"
        }
      }
    ]
  }
}

参考

Elasticsearch実践ガイド
はじめての Elasticsearch
REST APIs
【基礎編】Elasticsearchの検索クエリを使いこなそう
初心者のためのElasticsearchその2 -いろいろな検索-
Elasticsearchの確認系コマンド6つ