【入門】Elasticsearch とは?インストールから query の使い方

Elasticsearch

Elasticsearch とは、複数のファイルから特定の文字列を分散検索するソフトウェアです。

具体的には google 検索や、github のソースコード検索のような全文検索 (後述) が行えます。

スポンサーリンク

全文検索とは

全文検索とは全文検索とは、複数のファイルから特定の文字列を検索することです。

全文検索には、主に以下の2つの手法があります。

grep 型

grep 型とはgrep 型とは、複数のファイルを上から順番に検索する方法です。
ファイルの数が増えると検索速度が大幅に低下する特徴があります。

UNIX の grep コマンドが、こちらに当たります。

索引 (インデックス) 型

索引 (インデックス) 型とは索引 (インデックス) 型とは、転置インデックス (ある単語を含むファイル一覧) を作成しておくことで、ファイルの検索速度を向上する方法です。

Elasticsearch の全文検索が、こちらに当たります。

インデックス型では、検索する単語を1行だけスキャンすれば良いので、高速に検索できます。
※ grep 型ではドキュメントをフルスキャンする必要があります

インデックス型の全文検索を利用した例は以下のとおりです。

全文検索の例説明
Elasticsearch の検索複数のファイル (ドキュメント) から一致する文字列を検索
Google 検索複数のファイル (Webページ) から一致する文字列を検索
GitHub のソースコード検索複数のファイル (ソースコード) から一致する文字列を検索
スポンサーリンク

Elasticsearch 用途/事例/できる事

Elasticsearch の主な利用用途は以下の2つです。

企業におけるドキュメント検索の利用例は以下のとおりです。

GitHub: ソースコード検索

ソフトウェア開発の加速化

日経新聞: 記事の検索、アクセスログの解析

日本経済新聞社: ドキュメント総数1.6億件、国内最大級のデータベース事業の検索を担う

異常検知

企業における異常検知の利用例は以下のとおりです。

■RICOH: セキュリティインシデントの検知

SOCにおけるリアル、CSIRT:株式会社リコーを実現”見える化、“タイムデータの活用を促進しすることでリコーグループのセキュリティを強化
1日2TBにおよぶITデバイスのログを35ノードのElasticsearchで瞬時に検索し、セキュリティ上の脅威をプロアクティブに排除

Netflix: セキュリティログの監視

スポンサーリンク

Elastic Stack (ELK Stack) とは

Elastic Stack (ELK Stack) とは、以下の4つのソフトウェアのことです。

ソフトウェア説明
Elasticsearchファイル (ドキュメント) を保存・検索
KibanaElasticsearch 内のドキュメントを可視化する BI ツール
LogstashElasticsearch に送信するデータの形式を変換
Beatsサーバーから Elasticsearch や Logstash にデータを転送

Kibana については、公式で紹介されている以下の GIF 画像を見るのがわかりやすいです。

https://www.elastic.co/jp/what-is/kibana-reporting

Logstash や Beats の代わりに、Fluentd でデータを収集する構成も一般的です。

こちらの構成は EFK (Elasticsearch, Fluentd, Kibana) と呼ばれます。

Elasticsearch のアーキテクチャ

Elasticsearch のアーキテクチャについて次の2つに分けて説明します。

論理的なアーキテクチャと用語

Elasticsearch の論理的な概念で登場する用語は以下のとおりです。

論理的な概念

フィールド

フィールドとは、JSON オブジェクトの Key-Value (名前と値の組) に相当します。
各フィールドはデータ型 (text, number 等) を持ちます (一覧はこちら)。

ドキュメント

ドキュメントとは、フィールドの集合です。JSON オブジェクトに相当します。

インデックス

インデックスとは、ドキュメントの集合 (日/週/月ごとのログなど) です。
インデックスにドキュメントを格納する時は転置インデックスを作成します。

マッピング

マッピングとは、フィールドの設定です。マッピングは1つのインデックスに1つです。

物理的なアーキテクチャと用語

Elasticsearch の物理的な概念の全体図は以下のとおりです。

なお、赤枠は Elasticsearch の論理的な概念である [インデックス] です。

物理的な概念と論理的な概念の関連

シャード

シャードとは、インデックスを分割したデータ (Lucene インデックスファイル) です。

Elasticsearch では、シャード単位で並列して検索処理が可能です。

シャードは以下の2種類が存在します。

用語説明
プライマリーシャード書き込み処理を行うシャード
レプリカシャードプライマリーシャードのコピー (レプリケーション)
検索処理の負荷分散や、データのバックアップとして利用

ノード

ノードとは、インデックスを分割したデータ (Lucene インデックスファイル) です。

ノードには次の4種類の属性が存在し、1つのノードが複数の属性を持つことも可能です。

ノードの種類役割
Master ノードクラスターのメタデータなどを管理するノード
・Master ノードはクラスターに1台のみ
・それ以外のノードは Master-eligible ノードと呼ぶ
・Master-eligible ノードが昇格すると Master ノードになる
Data ノードデータを格納・リクエストの処理(検索や集計など)するノード
Ingest ノードデータの変換や加工し、Data ノードに格納するノード
Logstash と同じ役割
Coordinating ノードリクエストを適切なシャードにルーティングするノード
・Data ノードでもリクエストはルーティング可能
・Data ノードにルーティング処理の負荷を掛けたくない場合に設定

クラスター

クラスターとは、複数のノードの集合です。

Elasticsearch のインストール

Elasticsearch のインストール方法を紹介します。

Docker を使わないインストール方法はこちら

ここでは Amazon Linux 2 でインストールする方法を紹介します。

その他の OS については以下のドキュメントをご覧ください。

■Elasticsearch のインストール

Installing Elasticsearch | Elasticsearch Guide [7.15] | Elastic

Kibana のインストール

Kibanaのインストール | Kibanaユーザーガイド [5.4] | Elastic

Java のインストール

Elasticsearch を実行するには Java 8 が必要なので、まずは Java 8 をインストールします。

sudo yum install java-1.8.0-openjdk -y
sudo yum install java-1.8.0-openjdk-devel -y
java -version
openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)

openjdk version "1.8****" 以上であれば OK です。

Elasticsearch のインストール

次に Elasticsearch をインストールします。

sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
sudo vim /etc/yum.repos.d/elasticsearch.repo
[elasticsearch]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=0
autorefresh=1
type=rpm-md
sudo yum install --enablerepo=elasticsearch elasticsearch -y
sudo systemctl start elasticsearch
curl localhost:9200
{
  "name" : "***",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "***",
  "version" : {
    "number" : "7.10.0",
    "build_flavor" : "default",
    "build_type" : "rpm",
    "build_hash" : "***",
    "build_date" : "2020-11-09T21:30:33.964949Z",
    "build_snapshot" : false,
    "lucene_version" : "8.7.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Kibana のインストール

Kibana をインストールします。

sudo vim /etc/yum.repos.d/kibana.repo
[kibana-7.x]
name=Kibana repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
sudo yum install kibana -y
sudo vim /etc/kibana/kibana.yml
server.host: "0.0.0.0"
sudo systemctl start kibana

ブラウザから Kibana(localhost:5601) にアクセスします。

以下のページが表示されれば成功です。(この記事は Kibana バージョン 7.10.0です)

アクセスできない場合

Elastic Kibana v7.3.0でSystemctrlを用いた起動ができず再起動を繰り返す。 - szkhaven.com
自分的メモです。 v6.7を使用してたのですが、そろそろv7に上げよう 続きを読む

ここからは、Docker を使った Elasticsearch のインストール方法を説明します。

なお、docker については以下の記事をご覧ください。

docker コンテナを作成

version: '3'
services:
  elasticsearch:
    image: elasticsearch:7.12.1
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
  kibana:
    image: kibana:7.12.1
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

これでシングルノードの Elasticsearch コンテナが立ち上がります。

Elasticsearch コンテナにアクセス

curl localhost:9200
{
  "name" : "123456789abc",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "1234567890abcdefghijkl",
  "version" : {
    "number" : "7.12.1",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "1234567891456789123456789",
    "build_date" : "2021-04-20T20:56:39.040728659Z",
    "build_snapshot" : false,
    "lucene_version" : "8.8.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

起動に成功している場合は、上記のような結果が返ってきます。

※ 「curl: (56) Recv failure: Connection reset by peer」が返ってくる場合は初期化処理が完了するまでお待ちください。

Kibana コンテナへのアクセス

Kibana コンテナにアクセスするために、ブラウザで http://localhost:5601 を開きます。

Elasticsearch の使い方

Elasticsearch は以下のようなツールを使い、REST API で操作します。

なお、Kibana の Dev Tools は以下の手順で開けます。

Dev Tools の開き方(kibana 7.10.0 の場合)

今回は現時点で最新のメジャーバージョンである Elasticsearch 7.x の使い方を説明します。(Elsticsearch はメジャーバージョンが変化すると、後方互換性がなくなります。)

CRUD 操作 + Bulk API

Elasticsearch の REST API では、ドキュメントに対して以下のような CRUD 操作が可能です。

REST API説明対応する CRUD
Index APIインデックスにドキュメントを追加Create
Get APIインデックスのドキュメントを取得Read
Update APIインデックスのドキュメントを更新Update
Delete APIインデックスのドキュメントを削除Delete

Index API (Create)

Index API とは、インデックスにドキュメントを追加する API です。
PUT /2020-11-tweet/_doc/1
{
  "date":"2020/11/01 09:00 JST",
  "Tweet":"ツイッターをはじめました。",
  "User ID":"hoge"
}

右上の三角ボタンで実行します。

curl の場合

curl localhost:9200/2020-11-tweet/_doc/1?pretty -XPUT -H "Content-Type: application/json" -d '
{
  "date":"2020/11/01 09:00 JST",
  "Tweet":"ツイッターをはじめました。",
  "User ID":"hoge"
}'

補足 データストリームへドキュメントを追加する場合

データストリームとは、複数のインデックスを1つにまとめたものです。

データストリームの詳細は以下のドキュメントに記載がございます。

Data streams | Elasticsearch Guide [8.3] | Elastic

データストリームにドキュメントを追加するためには、以下の REST API を利用します。

PUT /tweet/_create/1
{
  "date":"2020/11/01 09:00 JST",
  "Tweet":"ツイッターをはじめました。",
  "User ID":"hoge"
}

Get API (Read)

Get API とは、インデックスからドキュメントを取得する API です。
GET /2020-11-tweet/_doc/1
{
  (中略)
  "_source" : {
    "date" : "2020/11/01 09:00 JST",
    "Tweet" : "ツイッターをはじめました。",
    "User ID" : "hoge"
  }
}

curl の場合

curl localhost:9200/2020-11-tweet/_doc/1?pretty
{
  (中略)
  "_source" : {
    "date" : "2020/11/01 09:00 JST",
    "Tweet" : "ツイッターをはじめました。",
    "User ID" : "hoge"
  }
}

Update API (Update)

Update API とは、インデックスのドキュメントを更新する API です。
POST /2020-11-tweet/_doc/1
{
  "date":"2020/11/01 09:00 JST",
  "Tweet":"ドキュメントを更新したよ。",
  "User ID":"hoge"
}
GET /2020-11-tweet/_doc/1
{
(中略)
  "_version" : 2,
  "_source" : {
    "date" : "2020/11/01 09:00 JST",
    "Tweet" : "ドキュメントを更新したよ。",
    "User ID" : "hoge"
  }

"Tweet" フィールドの値が更新され、_version が2に変化していることがわかります。

curl の場合

curl localhost:9200/2020-11-tweet/_doc/1?pretty -XPOST -H "Content-Type: application/json" -d '
{
  "date":"2020/11/01 09:00 JST",
  "Tweet":"ドキュメントを更新したよ。",
  "User ID":"hoge"
}'
curl localhost:9200/2020-11-tweet/_doc/1?pretty
{
(中略)
  "_version" : 2,
  "_source" : {
    "date" : "2020/11/01 09:00 JST",
    "Tweet" : "ドキュメントを更新したよ。",
    "User ID" : "hoge"
  }

"Tweet" フィールドの値が更新され、_version が2に変化していることがわかります。

POST <index>/_update/<_id> を使う場合

POST /2020-11-tweet/_update/1
{
  "doc": {
    "Tweet":"ドキュメントを一部更新したよ。"
  }
}
GET /2020-11-tweet/_doc/1
{
(中略)
  "_source" : {
    "date" : "2020/11/01 09:00 JST",
    "Tweet" : "ドキュメントを一部更新したよ。",
    "User ID" : "hoge"
  }

Delete API

Delete API とは、インデックスのドキュメントを削除する API です。
DELETE /2020-11-tweet/_doc/1
GET /2020-11-tweet/_doc/1
{
  "_index" : "2020-11-tweet",
  "_type" : "_doc",
  "_id" : "1",
  "found" : false
}

found" が false となりました。("_source" のフィールドが削除されました。)

curl の場合

curl localhost:9200/2020-11-tweet/_doc/1?pretty -XDELETE
curl localhost:9200/2020-11-tweet/_doc/1?pretty
{
  "_index" : "2020-11-tweet",
  "_type" : "_doc",
  "_id" : "1",
  "found" : false
}

"found" が false となりました。("_source" のフィールドが削除されました。)

Bulk API (一括処理)

Bulk API とは、1回の API 呼び出しで、複数回ドキュメントを操作可能な API です。

なお、Bulk API で利用可能な操作は以下のとおりです。

Bulk API の操作説明
Createインデックスが存在しない場合、ドキュメントを作成
Indexインデックスのドキュメントを全て更新 (置き換え)
Updateインデックスのドキュメントを一部更新
Deleteインデックスのドキュメントを削除
POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

curl の場合

Bulk API で操作する内容を記載した json ファイルを作成します。

{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
curl localhost:9200/_bulk?pretty -XPOST --data-binary @bulk.json -H "Content-Type: application/json"

マッピング管理 + テンプレート

マッピングとはフィールドのデータ型を定義したものです。

マッピングを作成、追加する方法は以下の4つです。

なお、一度作成したマッピングは修正できません。(追加はできます)

マッピングを修正するには、インデックスを作成し直す必要があります。

自動でマッピングを作成

Elasticsearch は、インデックスにドキュメントを作成する時、自動でマッピングを作成します。

PUT /mapping_test/_doc/1
{
  "date":"2020/11/01 09:00:00+0900"
}
GET /mapping_test
   "mappings" : {
      "properties" : {
        "date" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256

結果を見ると、次のマッピングが定義されていることがわかります。

キーデータ型
datetext
date.keywordkeyword
text = 単語ごとに分けた転置インデックスを持つ全文検索用
keyword = 完全一致検索用

"date" キーのデータ型は text 型で登録されていますが、date 型で登録したい場合もあります。

この場合は、手動でマッピングを作成する必要があります。

手動でマッピングを作成

手動でマッピングを作成する方法は以下のとおりです。

PUT /mapping_test2
{
  "mappings": {
    "properties": {
      "date":    { 
        "type": "date",
        "format": "yyyy/MM/dd HH:mm:ssZ"
      }
    }
  }
}
GET /mapping_test2
    "mappings" : {
      "properties" : {
        "date" : {
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ssZ"

無事 date キーが date 型でマッピングされていることがわかります。

マッピングで指定したフォーマットと異なるドキュメントを作成するとどうなる?

マッピングで指定したフォーマットと異なるドキュメントを作成するにエラーとなります。

試しに設定したフォーマットと異なるフォーマットでドキュメントを作成します。

  • 設定したフォーマット = yyyy/MM/dd HH:mm:ssZ
  • 検証するフォーマット = yyyy/MM/dd HH:mm:Z
PUT /mapping_test2/_doc/1
{
  "date":"2020/11/01 09:00+0900"
}
      "reason" : "failed to parse date field [2020/11/01 09:00+0900] with format [yyyy/MM/dd HH:mm:ssZ]",

設定したフォーマットと異なるので、パースエラーが発生します。

次にマッピングで指定したフォーマットに従ってドキュメントを作成

PUT /mapping_test2/_doc/2
{
  "date":"2020/11/01 09:00:00+0900"
}

ドキュメントの作成に成功しました。

インデックステンプレート

インデックステンプレートとは、インデックスの設定 (マッピングなど) の雛形です。
これにより、複数のインデックスで何度もマッピングを設定せずに済みます。

では "test*" インデックスの作成時に利用するテンプレートを作成してみます。

PUT /_template/test_template
{
  "index_patterns": "test*",
  "mappings": {
    "properties": {
      "date":    { 
        "type": "date",
        "format": "yyyy/MM/dd HH:mm:ssZ"
      }
    }
  }
}

上記のテンプレートでは、"date" キーのデータ型を定義しています。

それでは、テンプレートの動作を確認してみます。

PUT /test1
GET /test1
    "mappings" : {
      "properties" : {
        "date" : {
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ssZ"
        }
      }
    }

テンプレートで指定した format で date フィールドが登録されています。

このインデックステンプレートは test* (test1、test2、test3....) の全てのインデックスに対して、自動でマッピングを設定します。

既存のマッピングに追加

既存のマッピングに新しいフィールドを追加したい場合は mapping API を利用します。

test1 インデックスのマッピングに additional_field フィールドを追加します。

GET /test1
    "mappings" : {
      "properties" : {
        "date" : {
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ssZ"
        }
      }
    }
PUT /test1/_mapping
{
  "properties":{
    "additional_field":{
      "type":"text"
    }
  }
}
GET /test1
    "mappings" : {
      "properties" : {
        "additional_field" : {
          "type" : "text"
        },
        "date" : {
          "type" : "date",
          "format" : "yyyy/MM/dd HH:mm:ssZ"

additional_field フィールドが追加されています。

ドキュメントの検索 (search API)

Elasticsearch では search API を利用してインデックス内にあるドキュメントを検索します。

search API では、クエリ DSL (Domain Specific Language) を利用して、「4種類の検索条件」と「ソート」を指定可能です。

以降では、以下のドキュメントを用いて search API の検索方法を紹介します。

POST /_bulk
{ "index" : { "_index" : "demo_search", "_id" : "1" } }
{ "text" : "This is Elasticsearch test." }
{ "index" : { "_index" : "demo_search", "_id" : "2" } }
{ "text" : "Elasticsearch is God." }
{ "index" : { "_index" : "demo_search", "_id" : "3" } }
{ "text" : "This is a pen." }

条件無しで検索

search API を利用して作成した demo_search インデックスのドキュメントを検索します。

GET /demo_search/_search
    "hits" : [
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "text" : "Elasticsearch is God."
        }
      },
      {
        "_index" : "demo_search",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "text" : "This is a pen."
        }
      }
    ]

作成した3つのドキュメントがすべて検索できています。

match クエリ (全文検索)

match クエリは、転置インデックスを利用して全文検索を行うクエリです。

match クエリでは、データ型は「text」のフィールドに対して全文検索を行います。

これは、「text」が単語ごとに転置インデックスを作成しているためです。

These fields are analyzed, that is they are passed through an analyzer to convert the string into a list of individual terms before being indexed.

https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html

match クエリを利用して全文検索をしてみます。

GET /demo_search/_search
{
  "query":{
    "match": {
      "text": "Elasticsearch"
    } 
  }
}
    "hits" : [
        (省略)
        "_source" : {
          "text" : "Elasticsearch is God."
        }
        (省略)
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
match クエリで AND 検索

AND 検索で、"Elasticsearch" と "this" の両方が含まれるドキュメントを検索します。

GET /demo_search/_search
{
  "query":{
    "match": {
      "text": {
        "query": "Elasticsearch this",
        "operator":"AND"
      }
    } 
  }
}
    "max_score" : 1.3988109,
    "hits" : [
        (省略)
        "_source" : {
          "text" : "This is Elasticsearch test."
match_phrase クエリ

match_phrase クエリとは、単語の順序が一致するドキュメントだけを出力するクエリです。

GET /demo_search/_search
{
  "query":{
    "match_phrase": {
      "text": "Elasticsearch test"
    } 
  }
}
    "hits" : [
        (省略)
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }
GET /demo_search/_search
{
  "query":{
    "match_phrase": {
      "text":{
        "query": "test Elasticsearch"
      }
    } 
  }
}
"hits" : [ ]

Term クエリ (完全一致検索)

Term クエリとは、完全一致検索を行うクエリです。

Term クエリでは、データ型が「keyword」のフィールドに対して完全一致検索を行います。
(text 型は、単語ごとに転置インデックスを作成しているので、完全一致検索ができない)

Keyword fields are often used in sortingaggregations, and term-level queries, such as term.

https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html
GET /demo_search/_search
{
  "query":{
    "term": {
      "text.keyword": "This is Elasticsearch test."
    } 
  }
}
      {
        "_source" : {
          "text" : "This is Elasticsearch test."
        }
      }

完全一致検索の失敗例

GET /demo_search/_search
{
  "query":{
    "term": {
      "text.keyword": "Elasticsearch"
    } 
  }
}
"hits" : [ ]

"Elasticsearch" は "This is Elasticsearch test." と異なるので、完全一致検索にヒットしないです。

Terms クエリ

Terms とは Term の複数形、つまり複数の完全一致検索をするクエリです。

GET /demo_search/_search
{
  "query":{
    "terms": {
      "text.keyword":["This is Elasticsearch test.","This is a pen."]
    } 
  }
}
"_source" : {
          "text" : "This is Elasticsearch test."
        }
        (中略)
        "_source" : {
          "text" : "This is a pen."
        }

Range クエリ (範囲検索)

Range クエリとは、指定した値を元に範囲検索するクエリです。

検証用の test_range インデックスを作成します。

POST /_bulk
{ "index" : { "_index" : "test_range", "_id" : "1" } }
{ "size" : -1 }
{ "index" : { "_index" : "test_range", "_id" : "2" } }
{ "size" : 5 }
{ "index" : { "_index" : "test_range", "_id" : "3" } }
{ "size" : 10 }

準備ができたので、Range クエリを紹介します。

GET /test_range/_search
{
  "query":{
    "range": {
      "size":{
        "gte": "0",
        "lte": "9"
      }
    } 
  }
}
    "hits" : [
      {
        "_index" : "test_range",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "size" : 5
        }
      }

0以上、9以下の size フィールドの値を持つドキュメントを検索できました。

bool クエリ (複合検索)

bool クエリとは、match クエリ、term クエリ、range クエリに対して AND, OR, NOT を実行するクエリです。

なお、bool クエリには、以下の4種類のクエリが存在します。

まずは検証のため、検証用の demo_bool インデックスを作成します。

POST /_bulk
{ "index" : { "_index" : "demo_bool", "_id" : "1" } }
{ "text" : "This is Elasticsearch test.","id":1 }
{ "index" : { "_index" : "demo_bool", "_id" : "2" } }
{ "text" : "Elasticsearch is God.","id":2 }
{ "index" : { "_index" : "demo_bool", "_id" : "3" } }
{ "text" : "This is a pen.","id":3 }

準備ができたので、ここから bool クエリを紹介していきます。

must クエリ

must クエリとは、AND 条件を表すクエリです。

GET /demo_bool/_search
{
  "query":{
    "bool": {
      "must":[
        {"match":{"text":"Elasticsearch"}},
        {"range":{"id":{"lte":"1"}}}
      ]
    } 
  }
}
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }

「text = "Elasticsearch" を含む」AND「id = 1以下」のドキュメントを検索できています。

should クエリ

should クエリとは、OR 条件を表すクエリです。

GET /demo_bool/_search
{
  "query":{
    "bool": {
      "should":[
        {"match":{"text":"Elasticsearch"}},
        {"range":{"id":{"lte":"1"}}}
      ]
    } 
  }
}
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.453151,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }
      },
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 0.5077718,
        "_source" : {
          "text" : "Elasticsearch is God.",
          "id" : 2
        }
      }

「text = "Elasticsearch" を含む」OR「id = 1以下」のドキュメントを検索できています。

なお、"text", "id" の両方の条件を満たす上のほうが "_score" の値が高くなります。

must_not クエリ

must_not クエリとは、NOT 条件を表すクエリです。

GET /demo_bool/_search
{
  "query":{
    "bool": {
      "must_not":[
        {"match":{"text":"Elasticsearch"}},
        {"range":{"id":{"lte":"1"}}}
      ]
    } 
  }
}
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.0,
        "_source" : {
          "text" : "This is a pen.",
          "id" : 3
        }
      }

「text = "Elasticsearch" を含む」ではない AND「id = 1以下」ではないドキュメントを検索できました。

つまり、"_score" が 0 のドキュメントです。

filter クエリ

fileter クエリとは、指定したドキュメント以外を検索対象から外すクエリです。

GET /demo_bool/_search
{
  "query":{
    "bool": {
      "must": [
        {"match":{"text":"Elasticsearch"}}
      ], 
      "filter":[
        {"range":{"id":{"lte":"1"}}}
      ]
    } 
  }
}
    "hits" : [
      {
        "_index" : "demo_bool",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.45315093,
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        }
      }

"text" = "Elasticsearch is God."、 "id" = 2 が検索結果から除外されています。

sort クエリ

sort クエリは、クエリの結果を指定したフィールドでソートします。

GET /demo_bool/_search
{
  "sort": [
    {
      "id": {
        "order": "desc"
      }
    }
  ]
}
"_source" : {
          "text" : "This is a pen.",
          "id" : 3
        },
        "sort" : [
          3
        ]
        (中略)
        "_source" : {
          "text" : "Elasticsearch is God.",
          "id" : 2
        },
        "sort" : [
          2
        ]
        (中略)
        "_source" : {
          "text" : "This is Elasticsearch test.",
          "id" : 1
        },
        "sort" : [
          1
        ]

id フィールドの値を用いて、降順 (desc) となっています。

日本語検索 (Analyzer の設定)

Analyzer とは、Text Analysis (後述) を実行するためのルールです。

Text Analysis とは、トークナイズ (文章を単語に分解) したり、正規化 (Dog, dogs, doggy 等を dog に置き換え) たりする処理です。

デフォルトの Analyzer では、日本語をトークナイズできないため、日本語をうまく検索できません。そのため、Analyzer を適切に設定する必要があります。

デフォルトの Analyzer を使用して日本語を検索した場合どうなるか

POST demo_standard_analyzer/_doc
{
  "text":"今年の東京都の予算が決まる。"
}

上記のドキュメントに対して「京都」という単語で検索してみます。

GET demo_standard_analyzer/_search
{
  "query": {
    "match": {
      "text": "京都"
    }
  }
}

残念ながらヒットしてしまいました。

「京都」という単語で「東京都」のドキュメントがヒットするのは使いにくいですね。「東京都」の「京都」の部分にヒットしてしまったのでしょうか?

いいえ。現実はもっと酷いことになっています。

GET demo_standard_analyzer/_search
{
  "query": {
    "match": {
      "text": "まほうつかい"
    }
  }
}
      {
        "_index" : "demo_standard_analyzer",
        "_type" : "_doc",
        "_id" : "jRQ06nUBRDMYXy6O16gl",
        "_score" : 0.2876821,
        "_source" : {
          "text" : "今年の東京都の予算が決まる。"
        }
      }

なんと「まほうつかい」で「今年の東京都の予算が決まる。」がヒットします。

この原因を探るため、デフォルトの Analyzer のトークナイズ結果を確認します。

POST demo_standard_analyzer/_analyze
{
  "text":"今年の東京都の予算が決まる"
}
    {
      "token" : "今",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "年",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "の",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<HIRAGANA>",
      "position" : 2
    },
    {
      "token" : "東",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    },
    {
      "token" : "京",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<IDEOGRAPHIC>",
      "position" : 4
    },
    {
      "token" : "都",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "<IDEOGRAPHIC>",
      "position" : 5
    },
    {
      "token" : "の",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "<HIRAGANA>",
      "position" : 6
    },
    {
      "token" : "予",
      "start_offset" : 7,
      "end_offset" : 8,
      "type" : "<IDEOGRAPHIC>",
      "position" : 7
    },
    {
      "token" : "算",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "<IDEOGRAPHIC>",
      "position" : 8
    },
    {
      "token" : "が",
      "start_offset" : 9,
      "end_offset" : 10,
      "type" : "<HIRAGANA>",
      "position" : 9
    },
    {
      "token" : "決",
      "start_offset" : 10,
      "end_offset" : 11,
      "type" : "<IDEOGRAPHIC>",
      "position" : 10
    },
    {
      "token" : "ま",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<HIRAGANA>",
      "position" : 11
    },
    {
      "token" : "る",
      "start_offset" : 12,
      "end_offset" : 13,
      "type" : "<HIRAGANA>",
      "position" : 12
    }

Standard Analyzer は文章を1文字ずつに区切っているだけです。

そのため、以下の文字が一致しています。

  • 「決まる」の「ま」
  • 「まほうつかい」の「ま」

そのため、「まほうつかい」という単語で「今年の東京都の予算が決まる」がヒットしました。

これでは流石に使い物にならないのでちゃんと日本語に対応した Analyzer で文章を単語に分解しましょう。

Analyzer のルールは、次の3つの要素から構成されます。

Analyzer の要素説明
Char Filter文章を変換
Tokenizer文章を Token(単語)に分割
Token filterToken (単語) を変換

Analyzer の構成要素

Analyzer は次の3つの要素から構成されます。

Analyzer の要素説明
Char Filter文章をトークナイズしやすいように変換
Tokenizer文章を Token(単語)に分割
Token filterToken (単語) を正規化等のために変換
Char Filter --> Tokenizer --> Token filter の順

Analyzer は3つの要素を上から順番に処理します。

Char Filter (文章を変換)
Char Filter説明
HTML Strip Character FilterHTML のタグを除去
Pattern replace character filter正規表現で置換
Tokenizer (文章を Token [= 単語] に分割)
Tokenizer説明
Standard Tokenizerデフォルトのトークナイザー
kuromoji_tokenizer日本語用のトークナイザー
Token filter (Token [= 単語] を変換)
Token Filter説明
Lower case Token Filterトークンをすべて小文字に変換"Google" で "google" も検索
Stop Token Filterストップワードを設定助詞を検索から除外
Stemmer Token Filterステミングを設定「swims」と「swimming」を「swim」に変換
Synonym Token Filterシノニムを設定「ググる」と「検索する」を「検索」に変換

Kuromoji Analyzer (日本語検索)

ここからは、形態素解析を利用して日本語をトークナイズするプラグインである kuromoji Analysis Plugin を利用して、日本語を検索をする方法を紹介します。

なお、kuromoji Analysis Pluginkuromoji analyzer は以下の要素から構成されます。

Analyzerkuromoji analyzer の構成要素
Char Filter無し
Tokenizerkuromoji_tokenizer
Token Filterkuromoji_baseform token filter 
kuromoji_part_of_speech token filter
cjk_width token filter
ja_stop token filter
kuromoji_stemmer token filter
lowercase token filter
kuromoji Analysis Plugin をインストール
FROM docker.elastic.co/elasticsearch/elasticsearch:7.12.1
RUN /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
version: '3'
services:
  elasticsearch:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
    ports:
      - 9200:9200
  kibana:
    image: kibana:7.12.1
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200

※既に Elasticsearch, kibana コンテナを起動してる場合は、docker-compose down を実行してください。

docker を利用しない方法

sudo /usr/share/elasticsearch/bin/elasticsearch-plugin install analysis-kuromoji
sudo systemctl restart elasticsearch
Kuromoji Analyzer の利用方法
PUT demo_analyzer
{
  "mappings": {
    "properties": {
      "text":{
        "type": "text",
        "analyzer": "kuromoji"
      }
    }
  }
}

次に demo_analyzer インデックスでドキュメントを作成し、日本語検索を行います。

POST demo_analyzer/_doc
{
  "text":"東京都の予算が決まる。"
}
GET demo_analyzer/_search
{
  "query": {
    "match": {
      "text": "東京都"
    }
  }
}
    "hits" : [
      {
        "_score" : 0.5753642,
        "_source" : {
          "text" : "東京都の予算が決まる。"
        }

無事に日本語で検索できるようになりました。

形態素解析の利点

「京都」を検索してみます。

通常の部分一致検索であれば「東京都」が引っかかり、検索のノイズとなります。

GET demo_analyzer/_search
{
  "query": {
    "match": {
      "text": "京都"
    }
  }
}

京都」という単語で「東京都」がヒットしません。正しく別の単語と認識してくれています。

どのように日本語が形態素解析されているか確認します。

GET demo_analyzer/_analyze
{
  "analyzer": "kuromoji", 
  "text":"今年の東京都の予算が決まる。"
}
{
  "tokens" : [
    {
      "token" : "今年",
      "type" : "word",
    },
    {
      "token" : "東京",
      "type" : "word",
    },
    {
      "token" : "都",
      "type" : "word",
    },
    {
      "token" : "予算",
      "type" : "word",
    },
    {
      "token" : "決まる",
      "type" : "word",
    }
  ]
}

「東京」と「都」で分割しているため、「東京都」が「京都」に引っかかりません。

Custom Analyzer を作成

Custom Analyzer は、自分で以下の3つの要素を指定して Analyzer を作成できます。
Char Filter
Tokenizer
Token filter

例えば、以下のような Custom Analyzer を作成します。

Analyzer の要素利用する要素やりたいこと
Char Filterhtml_stripHTML の <p> タグを除去
Tokenizerkuromoji_tokenizer日本語の文章を Token (単語) に変換
Token filterfilter (stopwords)"今年"、"の"、"が" を除去
PUT custome_analyze
{
  "settings": {
    "analysis": {
      "analyzer": {
        "original_analyze":{
          "char_filter":["html_strip"],
          "tokenizer":"kuromoji_tokenizer",
          "filter":["my_stop"]
        }
      },
      "filter":{
        "my_stop":{
          "type":"stop",
          "stopwords":["今年","の","が"]  
        }
      }
    }
  }
}

作成した Analyzer を使用してみます。

POST  custome_analyze/_analyze
{
  "analyzer": "original_analyze",
  "text": "<p>今年の東京都の予算が決まる。</p>"
}
{
  "tokens" : [
    {
      "token" : "東京",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "word",
      "position" : 2
    },
    {
      "token" : "都",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "word",
      "position" : 3
    },
    {
      "token" : "予算",
      "start_offset" : 10,
      "end_offset" : 12,
      "type" : "word",
      "position" : 5
    },
    {
      "token" : "決まる",
      "start_offset" : 13,
      "end_offset" : 16,
      "type" : "word",
      "position" : 7
    }
  ]
}

カスタム Analyzer "original_analyze" で定義したとおりの動作をしています。

集計・分類 (Aggregations)

Aggregations には次の2種類が存在します。

以下の "demo_agg" インデックスを利用して、Aggregations の動きを確認します。

POST /_bulk
{ "index" : { "_index" : "demo_agg", "_id" : "1" } }
{ "text" : "This is Elasticsearch test.","num":1 }
{ "index" : { "_index" : "demo_agg", "_id" : "2" } }
{ "text" : "Elasticsearch is God.","num":2 }
{ "index" : { "_index" : "demo_agg", "_id" : "3" } }
{ "text" : "This is a pen.","num":3 }
{ "index" : { "_index" : "demo_agg", "_id" : "4" } }
{ "text" : "I have a pen.","num":2 }

集計 (Metrics Aggregations)

Metrics Aggregations は、集計したドキュメントから指定の方法でメトリクスを計算します

メトリクスの指定できる計算方法の例は以下です。

ここでは、Avg Aggregation (平均) を利用してみます。

GET demo_agg/_search
{
  "size": 0,
  "aggs": {
    "hoge_name": {
      "avg": {
        "field":"num"
      }
    }
  }
}
  "aggregations" : {
    "hoge_name" : {
      "value" : 2.0
    }
  }

"num" フィールドの値は [1, 2, 3, 2] なので、平均の 2.0 を正しく計算できています。

分類 (Buckets Aggregations)

Buckets Aggregations とは、フィールドの値を分類し、各バケットに格納します。

Buckets Aggregations として、次の2種類を紹介します。

Range aggregation

Range aggregation とは、指定した範囲の値を格納するバケットを作成する方法です。

例として、次の2つのバケットを作成します。

  • num フォールドの値が [0 ~ 2] のドキュメントを格納するバケット
  • num フォールドの値が [2 以上] のドキュメントを格納するバケット
GET demo_agg/_search
{
  "size": 0,
  "aggs": {
    "bucket_name": {
      "range": {
        "field":"num",
        "ranges": [
          {
            "from": 0, 
            "to":2
          },
          {
            "from": 2
          }
        ]
      }
    }
  }
}
  "aggregations" : {
    "bucket_name" : {
      "buckets" : [
        {
          "key" : "0.0-2.0",
          "from" : 0.0,
          "to" : 2.0,
          "doc_count" : 1
        },
        {
          "key" : "2.0-*",
          "from" : 2.0,
          "doc_count" : 3
        }
      ]
    }

次の結果が確認できます。

  • num フォールドの値が [0 ~ 2] を格納するバケット: 1つのドキュメント (num = [1])
  • num フォールドの値が [2 以上] を格納するバケット: 3つのドキュメント (num = [2, 3, 2])
Histogram aggregation

Histogram aggregation とは、等間隔でバケットを作成する方法です。

1000 間隔でバケットを分けたヒストグラムの例

"num" フィールドを、1間隔でバケットを作成した例は、以下のとおりです。

GET demo_agg/_search
{
  "size": 0,
  "aggs": {
    "bucket_name": {
      "histogram": {
        "field":"num",
        "interval": 1
      }
    }
  }
}
  "aggregations" : {
    "bucket_name" : {
      "buckets" : [
        {
          "key" : 1.0,
          "doc_count" : 1
        },
        {
          "key" : 2.0,
          "doc_count" : 2
        },
        {
          "key" : 3.0,
          "doc_count" : 1
        }
      ]

num の値が1ごとにバケットが作成されていることがわかります。

Dynamic index settings の変更

Dynamic index settings とは、Elasticsearch が稼働中に変更可能な設定です。

今回は、index.number_of_replicas を Update index settings API で変更してみます。

index.number_of_replicas

The number of replicas each primary shard has. Defaults to 1.

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings
PUT /my-index-000001
GET /my-index-000001
    "settings" : {
      "index" : {
        (中略)
        "number_of_replicas" : "1",
PUT /my-index-000001/_settings
{
  "index" : {
    "number_of_replicas" : 2
  }
}

上記のように、ピリオドごとに { } でネストします。

GET /my-index-000001
    "settings" : {
      "index" : {
        (中略)
        "number_of_replicas" : "2",

index.number_of_replicas が2に変更できています。

関連記事

ビッグデータ分析基盤入門シリーズの続きは以下です。


参考文献

公式ドキュメント

Elasticsearch Guide [7.15] | Elastic