https://db-engines.com/en/ranking によると、サーチエンジンのElasticsearchはかなりいいところ(2019年12月付で全体の8位、サーチエンジンではトップ)につけています。
自前の全文検索システムを作りたくて、インストールしてみました。
作業記録をほぼリアルタイムでここに残しておきます。
年末年始の空き時間で作業を行いますので、作業と作業の間隔が空くことになるとは思います。
(環境)
プロセッサ:Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz 1.80GHz
実装RAM:16.0GB
システムの種類:64ビットオペレーティングシステム、x64 ベースプロセッサ
Windowsの仕様:Xindows 10 Home
(今回の目標とする着地点)
・1ノード、1クラスタ (つまり no operation)
・最もシンプルな形でCRUDができる
elastic社サイトの Elasticsearchのデータ操作入門 を見ながらやってみます。
作業を開始します。
#インストール
https://www.elastic.co/jp/downloads/elasticsearch
からWindows を選択してダウンロードします。
ダウンロードフォルダに
elasticsearch-7.5.1-windows-x86_64.zip
が格納されました。
デスクトップだと後々面倒なことが起こりそうなのでCドライブに移動させる。
jdkが既に組み込まれているようなので有難いです。
binフォルダに移動します。
なんか起動したみたいです。Security is desabled というのはとりあえず置いておいて、
ポート9200で待ち受け状態になっているか確認してみます。
PS C:\Users\xxx> curl http://localhost:9200
StatusCode : 200
StatusDescription : OK
Content : {
"name" : "LAPTOP-XXXX9X99",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XXXXxxXXXxX9XxX9xxxxXX",
"version" : {
"number" : "7.5.1",
"build_flavor" : "default",
"build_type...
RawContent : HTTP/1.1 200 OK
Content-Length: 540
Content-Type: application/json; charset=UTF-8
{
"name" : "LAPTOP-XXXX9X99",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "XXXXxxXXXxX9XxX9xxxxXX",
...
Forms : {}
Headers : {[Content-Length, 540], [Content-Type, application/json; charset=UTF-8]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 540
大丈夫なようです。
次は https://www.elastic.co/jp/downloads/kibana からWindoswを選択してKibanaをダウンロードしてみます。
kibana.yml を見てみると、Elasticsearchの接続初期値は http://localhost:9200 となっているようなのであらためての設定変更は必要なさそうです。
それじゃあ、binフォルダ内のkibana.bat を起動してみます。
ログの最後に
http server running at http://localhost:5601
と出力されるのでブラウザから表示させてみます。
事前に Elastic Security の設定をしておくとIDとパスワードを聞いてくるようですが、ここは端折ります。
Elasticsearchが用意しているREST APIはHTTPプロトコルを利用した仕組みなので、検証のためのパケットキャプチャツールとしてFeddlerを利用しようと思っていました。
でも当面それは必要なくて、Kibanaで用意されているDev Tools というのを使うとかなりのことができそうです。
Submit Request のためのショートカット Ctrl + Enter を実行すると画面の右側にGETコマンドの応答が表示されます。
どうやら格納済のデータがあるようです。
ヒットしたのは
"_index" : ".kibana_1" 6件
"_index" : ".kibana_task_manager_1" 2件
の8件。
"_type" : "_doc" は Elasticsearch 7では固定のようです。
日付時刻はUTC(協定世界時)で表現されているようです。
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 8,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "space:default",
"_score" : 1.0,
"_source" : {
"space" : {
"name" : "Default",
"description" : "This is your default space!",
"color" : "#00bfb3",
"disabledFeatures" : [ ],
"_reserved" : true
},
"type" : "space",
"migrationVersion" : {
"space" : "6.6.0"
},
"updated_at" : "2019-12-31T02:06:06.431Z"
}
},
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "config:7.5.1",
"_score" : 1.0,
"_source" : {
"config" : {
"buildNum" : 27610
},
"type" : "config",
"updated_at" : "2019-12-31T02:09:03.897Z"
}
},
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "telemetry:telemetry",
"_score" : 1.0,
"_source" : {
"telemetry" : {
"userHasSeenNotice" : true
},
"type" : "telemetry",
"updated_at" : "2019-12-31T02:09:09.681Z"
}
},
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "ui-metric:kibana-user_agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36",
"_score" : 1.0,
"_source" : {
"ui-metric" : {
"count" : 1
},
"type" : "ui-metric",
"updated_at" : "2019-12-31T02:11:32.153Z"
}
},
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "ui-metric:Kibana_home:welcomeScreenMount",
"_score" : 1.0,
"_source" : {
"ui-metric" : {
"count" : 1
},
"type" : "ui-metric",
"updated_at" : "2019-12-31T02:11:32.153Z"
}
},
{
"_index" : ".kibana_1",
"_type" : "_doc",
"_id" : "ui-metric:Kibana_home:sampleDataDecline",
"_score" : 1.0,
"_source" : {
"ui-metric" : {
"count" : 1
},
"type" : "ui-metric",
"updated_at" : "2019-12-31T02:20:37.151Z"
}
},
{
"_index" : ".kibana_task_manager_1",
"_type" : "_doc",
"_id" : "task:oss_telemetry-vis_telemetry",
"_score" : 1.0,
"_source" : {
"migrationVersion" : {
"task" : "7.4.0"
},
"task" : {
"taskType" : "vis_telemetry",
"retryAt" : null,
"runAt" : "2020-01-01T15:00:00.000Z",
"startedAt" : null,
"state" : """{"runs":3}""",
"params" : "{}",
"ownerId" : null,
"scheduledAt" : "2019-12-31T02:06:09.626Z",
"attempts" : 3,
"status" : "failed"
},
"updated_at" : "2020-01-01T15:00:01.353Z",
"type" : "task"
}
},
{
"_index" : ".kibana_task_manager_1",
"_type" : "_doc",
"_id" : "task:Lens-lens_telemetry",
"_score" : 1.0,
"_source" : {
"migrationVersion" : {
"task" : "7.4.0"
},
"task" : {
"taskType" : "lens_telemetry",
"retryAt" : null,
"runAt" : "2020-01-02T15:00:00.000Z",
"startedAt" : null,
"state" : """{"runs":3,"byDate":{},"suggestionsByDate":{},"saved":{"saved_30_days":{},"saved_overall_total":0,"saved_30_days_total":0,"saved_90_days_total":0}}""",
"params" : "{}",
"ownerId" : null,
"scheduledAt" : "2019-12-31T02:06:09.625Z",
"attempts" : 0,
"status" : "idle"
},
"updated_at" : "2020-01-01T15:00:02.522Z",
"type" : "task"
}
}
]
}
}
#CREATE
インデックスについては、1件目のデータを登録すると勝手に作られるようです。
新規登録用にこんなのを作ってみました。
ちゃんとエラーを指摘してくれます。これは有難い。
エラーを訂正してもう一度やってみます。
POST /j_literature/_doc
{
"title": "走れメロス",
"authors": ["太宰治"],
"sentences": [
"メロスは激怒した。",
"必ず、かの邪智暴虐の王を除かなければならぬと決意した。",
"メロスには政治がわからぬ。",
"メロスは、村の牧人である。",
"メロスには政治がわからぬ。",
"笛を吹き、羊と遊んで暮して来た。",
"けれども邪悪に対しては、人一倍に敏感であった。"
]
}
"_seq_no" : 0
から始まるのでしょうか。
リクエストは1つなのに
"total" : 2
というのはどういうことでしょうか。
もしかしたら インデックス作成とデータ登録で 2 なのかな。
一応できたので、?のまま先に進みます。
{
"_index" : "j_literature",
"_type" : "_doc",
"_id" : "65XhaG8B71ysKG-2olXg",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
この調子であといくつか登録してみます。
"total" については?のままです。
ここまでに10件を登録しました。
なお、Elasticsearchのデータ操作入門 の 0:44:00 から bulk API の解説がありました。
本番で大量のデータをバッチ的に登録するにはその方式がいいかと思います。
#READ
では登録した10件のデータを使って、READの実験をしてみます。
GET j_literature/_doc/_search
のコマンドで、登録されているデータを確認できました。
"value" : 10,
とあるので10件がヒットしたことがわかります。
応答の先頭に
#! Deprecation: [types removal] Specifying types in search requests is deprecated.
があります。
今後廃止する機能を使っている場合にこのようなワーニングが出力されるようです。
GET j_literature/_search
としてもう一度やっておきます。
これでいいみたいです。
"title": "少年"
で絞って検索してみます。
GET j_literature/_search
{
"query" : {
"match": {
"title": "少年"
}
}
}
"少年探偵団" と "不良少年とキリスト" がヒットしました。OKです。
"authors": "太宰"
で絞って検索してみます。
GET j_literature/_search
{
"query" : {
"match": {
"authors": "太宰"
}
}
}
"太宰治" の ”走れメロス" がヒットしました。OKです。
今度は
"title": "少年"
"sentences": "太宰"
で絞った検索をしてみます。
GET j_literature/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "少年"
}
},
{
"match_phrase": {
"sentences": "太宰"
}
}
]
}
}
}
今度は
"authors": "太宰"
"sentences": "太宰"
どちらかが該当する作品を検索してみます。
GET j_literature/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"authors": "太宰"
}
},
{
"match_phrase": {
"sentences": "太宰"
}
}
]
}
}
}
”走れメロス" と "不良少年とキリスト" がヒットしました。OKです。
こんなふうに SQL も使えるようですが、
POST _sql?format=txt
{
"query" : "SELECT title, authors FROM j_literature WHERE authors like '%太宰%' OR sentences like '%太宰%' ORDER BY title"
}
さすがに SELECT句に配列 sentences を指定することはできないようです。
POST _sql?format=txt
{
"query" : "SELECT title, authors, sentences FROM j_literature WHERE authors like '%太宰%' OR sentences like '%太宰%' ORDER BY title"
}
でも"query"を同じにしてエンドポイントを
POST _sql/translate
に変えると SQL から Elasticsearch の query に変換をかけた json の値が返ってきます。
Elasticsearch の query に慣れないうちはこれで確認しながらやると良さそうです。
POST _sql/translate
{
"query" : "SELECT title, authors, sentences FROM j_literature WHERE authors like '%太宰%' OR sentences like '%太宰%' ORDER BY title"
}
#UPDATE
"走れメロス" の sentences に
"メロスには政治がわからぬ。"
が重複して存在していました。
このデータを UPDATE するにはIDが必要みたいなので、
GET j_literature/_search
{
"query" : {
"match": {
"title": "走れメロス"
}
}
}
取得した
"_id" : "65XhaG8B71ysKG-2olXg",
を使って、queryを投げます。
POST j_literature/_update/65XhaG8B71ysKG-2olXg
{
"doc": {
"sentences": [
"メロスは激怒した。",
"必ず、かの邪智暴虐の王を除かなければならぬと決意した。",
"メロスには政治がわからぬ。",
"メロスは、村の牧人である。",
"笛を吹き、羊と遊んで暮して来た。",
"けれども邪悪に対しては、人一倍に敏感であった。"
]
}
}
#DELETE
sentences に "猫" の文字列があるものを検索してみると
POST j_literature/_search
{
"query" : {
"match": {
"sentences": "猫"
}
}
}
これを削除してみることにします。
POST j_literature/_delete_by_query
{
"query" : {
"match": {
"sentences": "猫"
}
}
}
これで、今回目標とする
・最もシンプルな形でCRUDができる
に着地することができました。