Elasticsearch とは、複数のファイルから特定の文字列を検索する分散型エンジンです。
具体的には google 検索や、github のソースコード検索のような全文検索 (後述) が行えます。
初めに
本記事は、以下のビッグデータ分析基盤シリーズの Elasticsearch 編です。
- 【ビッグデータ入門1】ビッグデータ分析基盤
- 【ビッグデータ入門2】ストリーム処理
- 【ビッグデータ入門3】fluentd
- 【ビッグデータ入門4】Elasticsearch
- 【ビッグデータ入門5】Apache Kafka
- 【ビッグデータ入門6】Apache Hadoop
- 【ビッグデータ入門7】Apache Spark
- 【ビッグデータ入門8】Apache Hive
全文検索とは
全文検索とは、複数のファイルから特定の文字列を検索することです。
全文検索には、主に以下の2つの手法があります。
grep 型
grep 型とは、複数のファイルを上から順番に検索する方法です。
ファイルの数が増えると検索速度が大幅に低下する特徴があります。
UNIX の文字列検索コマンド grep がこれに当たります。
索引 (インデックス) 型
索引 (インデックス) とは、「ある単語を含むファイル一覧」です。

例えば、本の巻末には「ある単語含むページ一覧」を表す索引 (インデックス) があります。
同様に、全文検索には「ある単語を含むファイル一覧」を表す索引 (インデックス) があります。
索引 (インデックス) 型とは、あらかじめ単語に対して索引 (インデックス) を作成しておくことで、ファイルの検索速度を向上する方法です。
索引型の全文検索を利用した例は以下のとおりです。
- Elasticsearch 検索:複数のファイル (ドキュメント) から一致する文字列を検索
- Google 検索:複数のファイル (Webページ) から一致する文字列を検索
- GitHub のソースコード検索:複数のファイル (ソースコード) から一致する文字列を検索
インデックスの例
以下の2つのドキュメントを例に、インデックスを作成してみます。
- ドキュメント1:I Like search engine.
- ドキュメント2:I search keywords by google.
ドキュメントに単語が含まれる場合1、単語が含まれない場合0とすると、インデックスは以下のとおりです。
I | like | search | engine | keywords | by | ||
---|---|---|---|---|---|---|---|
ドキュメント1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
ドキュメント2 | 1 | 0 | 1 | 0 | 1 | 1 | 1 |
なお、一般的なデータベースでは行単位で検索します。(列単位で検索できません)
そのため "google" を含むドキュメントを検索する時、全ての行をスキャンする必要があります。
つまりドキュメントの数が増えるほど、検索速度が低下します。
この問題を解決するために、転置インデックスを利用します。
転置インデックスの例
転置インデックスは、次のようにインデックスの転置行列(行と列を入れ替えたもの)です。
ドキュメント1 | ドキュメント2 | |
---|---|---|
I | 1 | 1 |
like | 1 | 0 |
search | 1 | 1 |
engine | 1 | 0 |
keywords | 0 | 1 |
by | 0 | 1 |
0 | 1 |
転置インデックスから "google" を含むドキュメントを検索する場合、ドキュメントの数に関わらず1行スキャンするだけです。
用途 (できること)・利用例
Elasticsearch の主な利用用途は以下の2つです。
ドキュメント検索
サービスのドキュメントを検索するために利用できます。企業での利用例は以下のとおりです。
- GitHub: ソースコードの検索 ソフトウェア開発の加速化
- 日経新聞: 記事の検索、アクセスログの解析 日経電子版の記事検索および ログ解析の両方を1つの仕組みで実現
異常検知
アクセスログなどから異常を検知します。企業での利用例は以下のとおりです。
- リコー: セキュリティインシデントの検知 SOCにおけるリアル、CSIRT:株式会社リコーを実現”見える化、“タイムデータの活用を促進しすることでリコーグループのセキュリティを強化1日2TBにおよぶITデバイスのログを35ノードのElasticsearchで瞬時に検索し、セキュリティ上の脅威をプロアクティブに排除
- Netflix: セキュリティログの監視
Elastic Stack とは
Elastic Stack (ELK) は Elasticsearch と以下の3つのオープンソースプロジェクトのことです。
Kibana とは、Elasticsearch 内のドキュメントを可視化する BI ツールです。
以下の公式で紹介されている GIF 画像を見るのがわかりやすいです。

Logstash とは、Elasticsearch に送信するデータの形式を変換するツールです。
ETL の「Transform」、「 Load」(データを変換し Elasticsearch にロード) する役割です。
ちなみに、Logstash の代わりに Fluentd を利用する構成を EFK と言います。
( [ELK= Elasticsearch, Logstash, Kibana], [EFK = Elasticsearch, Fluentd, Kibana])
Fluentd の詳細については、以下の記事をご覧ください。
Beats とは、サーバーから Elasticsearch や Logstash にデータを転送するシッパーです。
ETL の「Extract」(サーバーからデータの抽出) する役割です。
Elasticsearch のアーキテクチャ
Elasticsearch のアーキテクチャについて次の2つに分けて説明します。
論理的なアーキテクチャと用語
Elasticsearch の論理的な概念の全体図は以下のとおりです。

各用語の意味は以下のとおりです。
用語 | 説明 |
---|---|
フィールド | Key-Value (名前と値の組) のこと 各フィールドはデータの種類を表す [データ型] を持つ (一覧はこちら) |
ドキュメント | フィールドの集合のこと ドキュメントの実態は JSON オブジェクト |
インデックス | ドキュメントの集合のこと ドキュメントをインデックスに格納する時、転置インデックスを作成 |
マッピング | フィールドの [データ型] や転置インデックスの設定 (一覧はこちら) マッピングは1つのインデックスに1つ |
物理的なアーキテクチャと用語
Elasticsearch の物理的な概念の全体図は以下のとおりです。
なお、赤枠は Elasticsearch の論理的な概念である [インデックス] です。

用語 | 説明 |
---|---|
シャード | インデックスを分割したデータ ・実体は Lucene インデックスファイル ・検索処理はシャード単位で分散/並列化 |
プライマリーシャード | 書き込み処理を行うシャード |
レプリカシャード | プライマリーシャードのコピー (レプリケーション) 検索処理の負荷分散や、データのバックアップとして利用 |
ノード | 1台の Elasticsearch サーバーのこと ノードは複数のシャードを持つ |
クラスター | 複数のノードの集合のこと |
なお、ノードには次の4種類の属性が存在し、1つのノードが複数の属性を持つことも可能です。
ノードの種類 | 役割 |
---|---|
Master ノード | クラスターのメタデータなどを管理するノード ・Master ノードはクラスターに1台のみ ・マスターノードに昇格可能なノードは Master-eligible と呼ばれるが、Master ノードでは無い |
Data ノード | データを格納・リクエストの処理(検索や集計など)をするノード |
Ingest ノード | データの変換や加工し、Data ノードに格納するノード ・Logstash と同じ役割 |
Coordinating ノード | リクエストをルーティングするノード ・Data ノードもリクエストはルーティング可能 ・Data ノードにルーティング作業の負荷を掛けたくない場合に、ルーティング処理専用のノードとして設定 |
Elasticsearch のインストール
Elasticsearch のインストール方法について、次の2つを紹介します。
なお、docker については以下の記事をご覧ください。
docker でインストール
docker を利用して、Elasticsearch をインストールする方法を紹介します。
Elasticsearch コンテナを作成
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 が立ち上がります。
{ "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 コンテナへのアクセス
先ほどの docker-compose で Kibana コンテナも合わせて起動しています。
Kibana コンテナにアクセスするために、ブラウザで http://localhost:5601 を開きます。

docker 無しでインストール
docker を利用せずに、Elasticsearch をインストールする方法を紹介します。
本記事では Amazon Linux 2 でインストールする方法を紹介します。
その他の OS については以下のドキュメントをご覧ください。
Elasticsearch インストール

Kibana インストール

Javaのインストール
Elasticsearch を実行するには Java 8 が必要なので、Java 8 をインストールします。
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 をインストールします。
[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
{ "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 も合わせてインストールします。(kibana を利用しない場合は飛ばしてOKです。)
[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
Kibana が稼働していることを確認するために、以下のどちらかの URL にブラウザからアクセスします。
■Elasticsearch が稼働するサーバーのブラウザからアクセスする場合
localhost:5601
■リモートホストのブラウザからアクセスする場合
<Elasticsearch サーバーの IP アドレス>:5601
以下のページが表示されれば成功です。(この記事は Kibana バージョン 7.10.0です)

アクセスできない場合

Elasticsearch の使い方
Elasticsearch は以下のようなツールを使い、REST API で操作します。
REST API って何?という方は、以下の記事をご覧ください。
また、Kibana の Dev Tools は以下の手順で開けます。

今回は現時点で最新のメジャーバージョンである 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 では、インデックスにドキュメントを追加します。
curl の場合
{ "date":"2020/11/01 09:00 JST", "Tweet":"ツイッターをはじめました。", "User ID":"hoge" }'
kibana の DevTools の場合
PUT /2020-11-tweet/_doc/1 { "date":"2020/11/01 09:00 JST", "Tweet":"ツイッターをはじめました。", "User ID":"hoge" }

右上の三角ボタンで実行します。
補足 データストリームへドキュメントを追加する場合
データストリームとは、複数のインデックスを1つにまとめたものです。
データストリームの詳細は以下のドキュメントに記載がございます。

データストリームにドキュメントを追加するためには、以下の REST API を利用します。
PUT /tweet/_create/1 { "date":"2020/11/01 09:00 JST", "Tweet":"ツイッターをはじめました。", "User ID":"hoge" }
Get API (Read)
Get API では、インデックスからドキュメントを取得します。
curl の場合
{ (中略) "_source" : { "date" : "2020/11/01 09:00 JST", "Tweet" : "ツイッターをはじめました。", "User ID" : "hoge" } }
kibana の DevTools の場合
{ (中略) "_source" : { "date" : "2020/11/01 09:00 JST", "Tweet" : "ツイッターをはじめました。", "User ID" : "hoge" } }
Update API (Update)
Update API では、インデックスのドキュメントを更新します。
curl の場合
{ "date":"2020/11/01 09:00 JST", "Tweet":"ドキュメントを更新したよ。", "User ID":"hoge" }'
{ (中略) "_version" : 2, "_source" : { "date" : "2020/11/01 09:00 JST", "Tweet" : "ドキュメントを更新したよ。", "User ID" : "hoge" }
"Tweet" フィールドの値が更新され、_version が2に変化していることがわかります。
kibana の DevTools の場合
POST /2020-11-tweet/_doc/1 { "date":"2020/11/01 09:00 JST", "Tweet":"ドキュメントを更新したよ。", "User ID":"hoge" }
{ (中略) "_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":"ドキュメントを一部更新したよ。" } }
{ (中略) "_source" : { "date" : "2020/11/01 09:00 JST", "Tweet" : "ドキュメントを一部更新したよ。", "User ID" : "hoge" }
Delete API (Delete)
Delete API では、インデックスのドキュメントを削除します。
curl の場合
{ "_index" : "2020-11-tweet", "_type" : "_doc", "_id" : "1", "found" : false }
"found" が false となりました。("_source" のフィールドが削除されました。)
kibana の DevTools の場合
{ "_index" : "2020-11-tweet", "_type" : "_doc", "_id" : "1", "found" : false }
found" が false となりました。("_source" のフィールドが削除されました。)
Bulk API (一括処理)
Bulk API では、1回の API 呼び出しでインデックスのドキュメントを複数回操作できます。
なお、Bulk API で利用可能な操作は以下のとおりです。
Bulk API の操作 | 説明 |
---|---|
Create | インデックスが存在しない場合、ドキュメントを作成 |
Index | インデックスのドキュメントを全て更新 (置き換え) |
Update | インデックスのドキュメントを一部更新 |
Delete | インデックスのドキュメントを削除 |
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"} }
kibana の DevTools の場合
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"} }
マッピング管理 + テンプレート
マッピングとはフィールドのデータ型を定義したものです。
マッピングを作成、追加する方法は以下の4つです。
なお、一度作成したマッピングは修正できません。(追加はできます)
マッピングを修正するには、インデックスを作成し直す必要があります。
自動でマッピングを作成
Elasticsearch は、インデックスにドキュメントを作成する時、自動でマッピングを作成します。
PUT /mapping_test/_doc/1 { "date":"2020/11/01 09:00:00+0900" }
"mappings" : { "properties" : { "date" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256
結果を見ると、次のマッピングが定義されていることがわかります。
キー | データ型 |
---|---|
date | text |
date.keyword | keyword |
keyword = 完全一致検索用
"date" キーのデータ型は text 型で登録されていますが、date 型で登録したい場合もあります。
この場合は、手動でマッピングを作成する必要があります。
手動でマッピングを作成
手動でマッピングを作成する方法は以下のとおりです。
PUT /mapping_test2 { "mappings": { "properties": { "date": { "type": "date", "format": "yyyy/MM/dd HH:mm:ssZ" } } } }
"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
"mappings" : { "properties" : { "date" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ssZ" } } }
テンプレートで指定した format で date フィールドが登録されています。
このインデックステンプレートは test* (test1、test2、test3....) の全てのインデックスに対して、自動でマッピングを設定します。
既存のマッピングに追加
既存のマッピングに新しいフィールドを追加したい場合は mapping API を利用します。
test1 インデックスのマッピングに additional_field フィールドを追加します。
"mappings" : { "properties" : { "date" : { "type" : "date", "format" : "yyyy/MM/dd HH:mm:ssZ" } } }
PUT /test1/_mapping { "properties":{ "additional_field":{ "type":"text" } } }
"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 インデックスのドキュメントを検索します。
"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 sorting, aggregations, and term-level queries, such as
https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.htmlterm
.
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 filter | Token (単語) を変換 |
Analyzer の構成要素
Analyzer は次の3つの要素から構成されます。
Analyzer の要素 | 説明 |
---|---|
Char Filter | 文章をトークナイズしやすいように変換 |
Tokenizer | 文章を Token(単語)に分割 |
Token filter | Token (単語) を正規化等のために変換 |
Analyzer は3つの要素を上から順番に処理します。
Char Filter (文章を変換)
Char Filter | 説明 |
---|---|
HTML Strip Character Filter | HTML のタグを除去 |
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 Plugin の kuromoji analyzer は以下の要素から構成されます。
Analyzer | kuromoji analyzer の構成要素 |
---|---|
Char Filter | 無し |
Tokenizer | kuromoji_tokenizer |
Token Filter | kuromoji_baseform token filter kuromoji_part_of_speech token filtercjk_width token filterja_stop token filterkuromoji_stemmer token filterlowercase 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 を利用しない方法
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 Filter | html_strip | HTML の <p> タグを除去 |
Tokenizer | kuromoji_tokenizer | 日本語の文章を Token (単語) に変換 |
Token filter | filter (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: 平均を取得する
- sum: 合計値を取得する
- max: 最大値を取得する
- min: 最小値を取得する
- stats: 上記全部の値を取得する
- cardinary: 異なる値の数を取得する ※[1,2,3,2]の場合は1,2,3 の3種類
ここでは、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 とは、等間隔でバケットを作成する方法です。

"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
"settings" : { "index" : { (中略) "number_of_replicas" : "1",
PUT /my-index-000001/_settings { "index" : { "number_of_replicas" : 2 } }
上記のように、ピリオドごとに { } でネストします。
"settings" : { "index" : { (中略) "number_of_replicas" : "2",
index.number_of_replicas が2に変更できています。
関連記事
ビッグデータ分析基盤入門シリーズの続きは以下です。
- 【ビッグデータ入門1】ビッグデータ分析基盤
- 【ビッグデータ入門2】ストリーム処理
- 【ビッグデータ入門3】fluentd
- 【ビッグデータ入門4】Elasticsearch
- 【ビッグデータ入門5】Apache Kafka
- 【ビッグデータ入門6】Apache Hadoop
- 【ビッグデータ入門7】Apache Spark
- 【ビッグデータ入門8】Apache Hive
参考文献
公式ドキュメント
