Elasticsearch 先輩で価格周りを触りたい

やりたいこと

1000 JPY みたいな文字列をいい感じに数値検索の対象にできないかなという実験。

今回の実験は以下のデータに対して、実験方法の curl を実行し、正しく指定した範囲のデータが返ってくることができるか?ということを調べる。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"PRICE1":"3000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"PRICE1":"5000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"3"}}
{"PRICE1":"100 JPY"}
$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 10,
                "lte": 3000
             }
        }
    }
}'

# id 1, 2 のみ検索結果にヒットすることを期待している。

自動マッピングに任せる

$ curl -X POST -H 'Content-Type: application/x-ndjson' 'http://localhost:9200/_bulk?pretty&refresh' --data-binary "@price.json"
$ curl -XGET 'localhost:9200/sample_index?pretty'
{
  "sample_index" : {
    "aliases" : { },
    "mappings" : {
      "sample_type" : {
        "properties" : {
          "PRICE1" : {
            "type" : "text",
            "fields" : {
              "keyword" : {
                "type" : "keyword",
                "ignore_above" : 256
              }
            }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "1500889167417",
        "number_of_shards" : "5",
        "number_of_replicas" : "1",
        "uuid" : "eZdsR51MRkeqfem48th0Rw",
        "version" : {
          "created" : "6000002"
        },
        "provided_name" : "sample_index"
      }
    }
  }
}

text 型で入ってるため、上手く数値の範囲検索を行うことが出来ない。

Numeric Detection

Dynamic field mapping | Elasticsearch Reference [5.5] | Elastic

text 型で入ってきた数値に関して、検索できるようにするぜという設定。 これによって、 以下のような text の中身が数値というデータに関しては、数値検索可能となる。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"PRICE1":"1000"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"PRICE1":"10"}
$ curl -XPUT 'localhost:9200/sample_index?pretty' -H 'Content-Type: application/json' -d'
{
  "mappings": {
    "sample_type": {
      "numeric_detection": true
    }
  }
}'
{
  "acknowledged" : true,
  "shards_acknowledged" : true
}
$ curl -X POST -H 'Content-Type: application/x-ndjson' 'http://localhost:9200/_bulk?pretty&refresh' --data-binary "@test-price.json"
$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 100,
                "lte": 1000
             }
        }
    }
}'
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "PRICE1" : "1000"
        }
      }
    ]
  }
}

Tokenize

あとは、 100 JPY となっている部分を 100JPY に分けたら 100 に対する検索にヒットするようになりそうなので、やってみる。

$ curl -XPOST 'localhost:9200/_analyze?pretty' -H 'Content-Type: application/json' -d'
{
  "tokenizer": "whitespace",
  "text": "100 JPY"
}
'

{
  "tokens" : [
    {
      "token" : "100",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "JPY",
      "start_offset" : 4,
      "end_offset" : 7,
      "type" : "word",
      "position" : 1
    }
  ]
}

空白で、トークンを区切る whitespace が使えそうなので、これでトークンを 100JPY に分けることはできそう。

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"analyzer": "whitespace","PRICE1":"3000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"analyzer": "whitespace","PRICE1":"5000 JPY"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"3"}}
{"analyzer": "whitespace","PRICE1":"100 JPY"}

というわけで、実験データに analyzer を足した物を実験データとして再度やり直した。

しかし、挙動が安定しない 意図通りの挙動になってくれない感じバグっぽい気もしている。 またフォーラム行きかしら。。。

ちなみにこんな感じ↓ 100000 以上について指定している。

$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
        "range" : {
            "PRICE1": {
                "gte": 100000
             }
        }
    }
}'

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "5000 JPY"
        }
      },
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "3000 JPY"
        }
      },
      {
        "_index" : "sample_index",
        "_type" : "sample_type",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "analyzer" : "whitespace",
          "PRICE1" : "100 JPY"
        }
      }
    ]
  }
}

そもそもトークンが上手くいっていない感じだな。。。うーん。。。わからん。。。。

$ curl -XGET 'localhost:9200/sample_index/_search?pretty' -H 'Content-Type: application/json' -d'
{
   "query" : {
         "term": { "PRICE1": "JPY"}
        }
    }
}'

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 0,
    "max_score" : null,
    "hits" : [ ]
  }
}

【追記】

johtani さんからアドバイスを頂いた。

アプローチの仕方が間違っていた。 やっぱり、数値にアプリ側で変換してインデックス貼ってあげるのが楽な気がする。

Elasticsearch 先輩で日付を触りたい

やりたかったこと

日付の取り回しについて調べてみた。 具体的には、以下のようなデータを想定する。

  • 20170713
  • 2017/07/13
  • 2017-07-13
  • 2017年07月13日
  • 2017713
  • 2017/7/13
  • 2017-7-13
  • 2017年7月13日

これらが不特定なカラムに入っている時に、 Elasticsearch で、いい感じに受け取るにはどうすればいいか?を調べてみた。

自動的にやってくれるもの

Elasticsearch は自動的に型を判断していい感じにマッピングしてくれる機能がある。 そこで使われる日付のフォーマットはこちらに一覧されている。

format | Elasticsearch Reference [5.5] | Elastic

というわけで、以下の実験データを何もマッピングされていない index に突っ込んでみる

{"index":{"_index":"sample_index","_type":"sample_type","_id":"1"}}
{"DATE1":"2017年07月13日"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"2"}}
{"DATE2":"2017/07/13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"3"}}
{"DATE3":"2017-07-13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"4"}}
{"DATE4":"2017.07.13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"5"}}
{"DATE5":"20170713"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"6"}}
{"DATE6":"2017年7月13日"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"7"}}
{"DATE7":"2017/7/13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"8"}}
{"DATE8":"2017-7-13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"9"}}
{"DATE9":"2017.7.13"}
{"index":{"_index":"sample_index","_type":"sample_type","_id":"10"}}
{"DATE10":"2017713"}
$ curl -s -X GET 'http://localhost:9200/sample_index/' | jq .
{
  "sample_index": {
    "aliases": {},
    "mappings": {
      "sample_type": {
        "properties": {
          "DATE10": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE2": {
            "type": "date",
            "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
          },
          "DATE3": {
            "type": "date"
          },
          "DATE4": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE5": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE6": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE7": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE8": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "DATE9": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          }
        }
      }
    },
    "settings": {
      "index": {
        "creation_date": "1499935832320",
        "number_of_shards": "5",
        "number_of_replicas": "1",
        "uuid": "Tf1HqJSfQzmoIPVKrMgnQQ",
        "version": {
          "created": "5040299"
        },
        "provided_name": "sample_index"
      }
    }
  }
}

結果から分かる通り、YYYY/MM/DD の形のみ受け付けている

そして、 format | Elasticsearch Reference [5.5] | Elastic にある通り、07月のように0埋めしないと自動ではマッピングしてもらえない。

なので、 DATE5~10 のフォーマットは、事前に何かをしてやるとかしないと、難しそう。

フォーマットを自分で定義してみる

次に自分で mapping を指定してデータの登録を行ってみる。

{
  "mappings": {
    "sample_type": {
      "properties": {
        "DATE1": {
          "type":   "date",
          "format": "yyyy年MM月dd日"
        },
        "DATE3": {
          "type":   "date",
          "format": "yyyy-MM-dd"
        },
        "DATE4": {
          "type":   "date",
          "format": "yyyy.MM.dd"
        },
        "DATE5": {
          "type":   "date",
          "format": "yyyy年M月dd日"
        }
      }
    }
  }
}

登録は次の用に行う。

curl -X PUT 'http://localhost:9200/sample_index' --data-binary @mapping.json | jq .

これを行った後、 DATE1~5について再度データの挿入をしてみた。

$curl -X GET 'http://localhost:9200/sample_index/' | jq .
{
  "sample_index": {
    "aliases": {},
    "mappings": {
      "sample": {
        "properties": {
          "DATE1": {
            "type": "date",
            "format": "yyyy年MM月dd日"
          },
          "DATE3": {
            "type": "date",
            "format": "yyyy-MM-dd"
          },
          "DATE4": {
            "type": "date",
            "format": "yyyy.MM.dd"
          },
          "DATE5": {
            "type": "date",
            "format": "yyyy年M月dd日"
          }
        }
      },
      "sample_type": {
        "properties": {
          "DATE1": {
            "type": "date",
            "format": "yyyy年MM月dd日"
          },
          "DATE2": {
            "type": "date",
            "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
          },
          "DATE3": {
            "type": "date",
            "format": "yyyy-MM-dd"
          },
          "DATE4": {
            "type": "date",
            "format": "yyyy.MM.dd"
          }
        }
      }
    },
    "settings": {
      "index": {
        "creation_date": "1500369904641",
        "number_of_shards": "5",
        "number_of_replicas": "1",
        "uuid": "EN99Zmc7TuK-7ePO1S9T6A",
        "version": {
          "created": "5040299"
        },
        "provided_name": "sample_index"
      }
    }
  }
}

これで、データ型での登録ができるようになった。 しかし、これでは、予め決められているカラムにしかデータの取込が出来ないため、デフォルトとして登録することを考えるもやり方が分からない。。。

Dynamic template は index の正規表現に則って、決まった index に対してマッピングを当てはめるみたいなことはできるが、不特定のカラム名に対して割り当てるのは難しい。

Dynamic templates | Elasticsearch Reference [5.5] | Elastic

うーん。。。

フォーラムで質問して見るかしら。。。

(追記 7月20日) フォーラムで質問したら、大谷さんから回答が返ってきた。

不特定カラムに対して日付のフォーマットを指定する - Elastic In Your Native Tongue / 日本語による質問・議論はこちら - Discuss the Elastic Stack

6.0.0-alpha2 だと試せるそうなので、試し見た。

$ curl -X PUT -H 'Content-Type: application/json' http://localhost:9200/my_index -d '
{
  "mappings": {
    "my_type": {
      "dynamic_date_formats": ["yyyy年MM月dd日", "yyyy/MM/dd", "yyyy.MM.dd", "yyyy-MM-dd"]
    }
  }
}' | jq .
{
  "acknowledged": true,
  "shards_acknowledged": true
}
$ curl -X POST -H 'Content-Type: application/json' http://localhost:9200/_bulk --data-binary @test.json 
$ curl -X GET http://localhost:9200/my_index | jq .
{
  "my_index": {
    "aliases": {},
    "mappings": {
      "my_type": {
        "dynamic_date_formats": [
          "yyyy年MM月dd日",
          "yyyy/MM/dd",
          "yyyy.MM.dd",
          "yyyy-MM-dd"
        ],
        "properties": {
          "DATE1": {
            "type": "date",
            "format": "yyyy年MM月dd日"
          },
          "DATE2": {
            "type": "date",
            "format": "yyyy/MM/dd"
          },
          "DATE3": {
            "type": "date",
            "format": "yyyy-MM-dd"
          },
          "DATE4": {
            "type": "date",
            "format": "yyyy.MM.dd"
          }
        }
      }
    },
  以下略
  }
}

意図通り、 date 型でのデータの保存ができるようになった。 ポイントは、事前に dynamic_date_formats を指定することで、予め想定される date を確定することだった。 これを template 化しておくことで、任意の index に対して、任意のカラムの日付を割り当てることができそう。

Dynamic field mapping | Elasticsearch Reference [master] | Elastic

日付が扱えるとどんな検索ができるか?

Range Query | Elasticsearch Reference [5.5] | Elastic

  • ○日から○日の範囲内
  • 今から何日以内
  • この日から何日以内

といった検索が行えそう

まとめ

文字列をトークン化するとか、予め決められたデータを処理することに対しては、 Elasticsearch は有効打にできそうだけど、日付周りは、フォーマットの関係に一工夫必要だった。

Elasticsearch 先輩との戯れ日記(登場人物の整理)

この記事何?

お仕事で Elasticsearch 先輩を使うことになったので、そのお戯れの記録

登場人物の整理

事例を中心に調査していたが、色々混乱してきたので、 Elasticsearch の世界の登場人物を整理することにした。

雑な説明

hoge

オブジェクト 概要
Cluster Node を束ねる存在
Node Elasticsearch のインスタンスのこと
Shard Index を分割したもの
Index インデックス
Type テーブル
Document レコード

雑すぎるので1個ずつ見ていく。

Cluster

Node を束ねるもの。 Cluster Health を使って、 Cluster の状態を確認することができる。

  • green: すべての Shard が Cluster 上に配置されてる
  • yellow: PrimaryShard は Cluster 上に配置されているが、配置されていない ReplicaShard が存在している
  • red: Cluster 上に配置されていない Shard が存在している

Shard については、後述。

Node

Node | Elasticsearch Reference [5.4] | Elastic

Node には Master (eligible) Node と Data Node と Ingest node が主要な感じ(Tribe Node はよくわからなかった)

Master Eligible Node

  • クラスタ内で行う処理の分配を行う
  • もし、 Master Node が死んだら、別の Master Eligible Node が Master になってくれる(デフォルトでは全 Node)
  • Master Node は、基本的に分配だけをメインにやらせると安定運用につながる

Data Node

  • document の管理を行う
  • 主に CRUD 操作を担当
  • Master Node とは役割分離させようね
  • I/O 系の処理はメモリいっぱい使うからちゃんと監視しようね

Data Node が死んじゃったらデータは欠損する? discovery.zen.minimum_master_nodes を設定すれば、設定した値の数で最小構成として、データの書き込みとかを行うみたい(おそらく) なので、一応データの欠損は防げるらしい。

参考:Avoiding split brain with minimum_master_nodes

Ingest Node

  • Elasticsearch 5.0 から登場
  • index を貼る前にドキュメントを変換するために使う Node
  • パイプラインを作って複数の処理を組み合わせて実行もできる

Shard

Shards & Replicas

10億個の document を複数に分割して読み込めるように、 index を分割する仕組み。 これによって、オペレーションの分散と並列化ができるため、パフォーマンスの向上につながる。

Primary shard を増やせば増やすほど並列数が上がって1ノードのパフォーマンスが上がるかも? →ただ、その分スペックは要求される デフォルトは5つの Primary Shard と 各 Primary Shard 毎に 1つの Replica Shard

Replica Shard は Primary Shard コピーを複製してあるもの。 このコピーは同一 Node には作成出来ない。

elas_0401.png

出典 : How Primary and Replica Shards Interact | Elasticsearch: The Definitive Guide [2.x] | Elastic

Index

Document を纏めるもの。 インデックス 付け、検索、更新、削除全てを行う。

Reindex | Curator Reference [5.1] | Elastic

インデックスを貼り直すとかって事もできる。 ここらへんが検索周りのチューニングポイントらしい。

Type

Document を種類別に分別することができる。 データベースで言うところの table

Document

1レコード分の情報が入ってるもの。 JSON で表現される。

Index Type Document の相関図はこんな感じ el-640x374.png

出典: データ構造について – AWSで始めるElasticSearch(4) | Developers.IO

まとめ

登場人物整理したら、なんとなく事例でチューニングした話とかも分かりそうな気がした。 あくまで、気がしているだけ。 図とかは出典元を見ていただけるとうれしいです。

番外編

こいつで、docker-compose up ってするだけで、 kibana と elasticsearch がどっちも立ち上がるんだって :sugoi:

version: '2'
services:
  kibana:
    image: kibana
    links:
      - elasticsearch:elasticsearch
    ports:
      - 5601:5601

  elasticsearch:
    image: elasticsearch
    ports:
      - 9200:9200
      - 9300:9300

公式もサポートしているっぽい。 library/elasticsearch - Docker Hub

Elasticsearch 先輩との戯れ日記(データの投入)

この記事何?

お仕事で Elasticsearch 先輩を使うことになったので、そのお戯れの記録

複数ドキュメントを扱う API って何がある?

Document APIs | Elasticsearch Reference [5.4] | Elastic

Multi-document API いっぺんにデータ突っ込んだり取ってきたりするやつっぽいな。

  • Multi Get API(データの取得)
  • Bulk API(データの挿入)
  • Delete By Query API(データの削除)
  • Update By Query API(データの更新)
  • Reindex API(インデックスの貼り直し)

データの投入について

Bulk API

Bulk API | Elasticsearch Reference [5.4] | Elastic

The bulk API makes it possible to perform many index/delete operations in a single API call. This can greatly increase the indexing speed.

endpoint は /_bulk, /{index}/_bulk, {index}/{type}/_bulk の3つ。 パラメーターに JSON 形式で挿入したいデータを指定する。 もし、データを送りたい場合は、

  • データの最終行に改行(\n)を入れる
  • もし改行したい場合は、 carriage return (\r) を使ってね
  • Content-Type: application/x-ndjson をヘッダーに含めて送ってね

を守ること。

$curl -X POST -H 'Content-Type: application/x-ndjson' 'localhost:9200/test/account/_bulk?pretty&refresh' --data-binary "@accounts.json"  
   ...
    {
      "index" : {
        "_index" : "test",
        "_type" : "account",
        "_id" : "990",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "created" : true,
        "status" : 201
      }
    },
    {
      "index" : {
        "_index" : "test",
        "_type" : "account",
        "_id" : "995",
        "_version" : 1,
        "result" : "created",
        "forced_refresh" : true,
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "created" : true,
        "status" : 201
      }
    }
  ]
}

CSV ファイルから直接いけるか?

:no_good: :no_good: デフォルトでは CSV ファイルから、直接ぶち込むことはできず、一旦 JSON 形式に変換してあげる必要がありそう。

$ curl -s -X POST -H 'Content-Type: text/csv' 'localhost:9200/test/feed/_bulk?pretty&refresh' --data-binary "@test_feed1000.csv" | jq
{
  "error": "Content-Type header [text/csv] is not supported",
  "status": 406
}

打開案

1手目はできそうだけど、そこまでして、 Elasticsearch の内部でデータを持つ必要があるのか説はあるな。。。

注意点

設計によっては、 OOM とかが頻発しちゃうのは結構怖さあるな。 OOM 発動しなくてもデータ破損とかはかなり痛いしな もうちょっとここは追加調査が必要。 場合によっては、検索/保存で仕組みを切り離しちゃうのも1つなんだろうなというお気持ちになりました。

余談

データの取得

ついでに確認のために、調べたので、データの取得も _search に query を投げてあげれば取れる。 from で指定する値が行数。 size が何行取り出すか。

以下の例は100行目から2行取り出すとなるので、100,101行目が取り出せている。

$ curl -s -X GET http://localhost:9200/test/account/_search -d '{"query":{"match_all": {}}, "from": "100", "size": "2"}' | jq .
{
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1000,
    "max_score": 1,
    "hits": [
      {
        "_index": "test",
        "_type": "account",
        "_id": "383",
        "_score": 1,
        "_source": {
          "account_number": 383,
          "balance": 48889,
          "firstname": "Knox",
          "lastname": "Larson",
          "age": 28,
          "gender": "F",
          "address": "962 Bartlett Place",
          "employer": "Bostonic",
          "email": "knoxlarson@bostonic.com",
          "city": "Smeltertown",
          "state": "TX"
        }
      },
      {
        "_index": "test",
        "_type": "account",
        "_id": "408",
        "_score": 1,
        "_source": {
          "account_number": 408,
          "balance": 34666,
          "firstname": "Lidia",
          "lastname": "Guerrero",
          "age": 30,
          "gender": "M",
          "address": "254 Stratford Road",
          "employer": "Snowpoke",
          "email": "lidiaguerrero@snowpoke.com",
          "city": "Fairlee",
          "state": "LA"
        }
      }
    ]
  }
}

というわけで、100行単位でページングみたいな使い方はできそうということが分かった。

Elasticsearch 先輩との戯れ日記(セットアップなどなど)

この記事何?

お仕事で Elasticsearch 先輩を使うことになったので、そのお戯れの記録

今回調べたものなど

まず Elasticsearch でどんなことができるかを調べるために、公式のチュートリアルとここ載ってる記事を読み漁った。

環境準備

公式さんから最新版(5.4)を Download してきた。 Kibana も合わせてローカルで準備しておく(戯れ用)

Download Elasticsearch Free • Get Started Now | Elastic Download Kibana Free • Get Started Now | Elastic

日本語全文検索用のプラグインである analysis-kuromoji を入れてみる。

# /Users/pokotyamu/Downloads/elasticsearch-5.4.2
$ bin/elasticsearch-plugin install analysis-kuromoji
-> Downloading analysis-kuromoji from elastic
[=================================================] 100%
-> Installed analysis-kuromoji

curl で叩いて、 plugin が入っていることを確認。

$ curl -X GET 'http://localhost:9200/_nodes/plugins?pretty'
{
  "_nodes" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "cluster_name" : "elasticsearch",
  "nodes" : {
    "tfQ1ZsOzQ9CYZtA0jXw2ww" : {
      "name" : "tfQ1ZsO",
      "transport_address" : "127.0.0.1:9300",
      "host" : "127.0.0.1",
      "ip" : "127.0.0.1",
      "version" : "5.4.2",
      "build_hash" : "929b078",
      "roles" : [
        "master",
        "data",
        "ingest"
      ],
      "plugins" : [
        {
          "name" : "analysis-kuromoji",
          "version" : "5.4.2",
          "description" : "The Japanese (kuromoji) Analysis plugin integrates Lucene kuromoji analysis module into elasticsearch.",
          "classname" : "org.elasticsearch.plugin.analysis.kuromoji.AnalysisKuromojiPlugin",
          "has_native_controller" : false
        }
      ],
     ...
}

基本コマンド

起動は以下で行う。終了は Ctrl-C-d オプションでバックグラウンド起動可能。

# /Users/pokotyamu/Downloads/elasticsearch-5.4.2
$ bin/elasticsearch

ポートはデフォルトで 9200番が使われるみたい。 crul で叩いてみて、次のように出たらOK

$ curl localhost:9200
{
  "name" : "tfQ1ZsO",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "Y2-U0XlwSxWX771QXbIN0w",
  "version" : {
    "number" : "5.4.2",
    "build_hash" : "929b078",
    "build_date" : "2017-06-15T02:29:28.122Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}

データ投入してみる

マッピング定義

MySQL Elasticsearch
database index
schema mapping
table type
record document

だいたいこんな感じの対応をしている。 マッピングの定義はこんな感じ↓

{
    "mappings": {
        "sample": {
            "properties": {
                "name": {
                    "type": "string",
                    "index": "not_analyzed"
                },
                "coord": {
                    "type": "geo_point"
                },
                "description": {
                    "type": "string",
                    "analyzer": "kuromoji"
                }
            }
        }
    }
}

sample の部分が type の名前になっている。 これを landmark という index に突っ込むためには PUT で書き換えれる。

# curl -X PUT http://localhost:9200/<Index Name>
$ curl -X PUT http://localhost:9200/landmark --data-binary @mapping.json
$ curl -s -X GET http://localhost:9200/landmark | jq .
{
  "landmark": {
    "aliases": {},
    "mappings": {
      "sample": {
        "properties": {
          "coord": {
            "type": "geo_point"
          },
          "description": {
            "type": "text",
            "analyzer": "kuromoji"
          },
          "name": {
            "type": "keyword"
          }
        }
      }
    },
    "settings": {
      "index": {
        "creation_date": "1498037230489",
        "number_of_shards": "5",
        "number_of_replicas": "1",
        "uuid": "9tFY4-F5S-KTyVEAa40DCg",
        "version": {
          "created": "5040299"
        },
        "provided_name": "landmark"
      }
    }
  }
}

ちなみに、このマッピング定義をしないでも、登録はできるらしい。 その時は Elasticsearch 側がよしなに値から型定義をしてくれるとか。凄い。

データ投入

今回は Bulk API でデータ投入してみる。 Bulk だと一回のリクエストで一気にデータを突っ込めるみたい。

{ "index" : {} }
{ "name": "スカイツリー", "coord": { "lat": "35.710063", "lon": "139.8107"}, "description": "東京スカイツリー(とうきょうスカイツリー、英: TOKYO SKYTREE)は東京都墨田区押上一丁目にある電波塔(送信所)である。"}
{ "index" : {} }
{ "name": "BLUE NOTE TOKYO", "coord": { "lat": "35.661198", "lon": "139.716207"}, "description": "N.Y.の「Blue Note」を本店に持つジャズクラブとして、南青山にオープンしたブルーノート東京。ジャズをはじめとする多様な音楽ジャンルのトップアーティストたちが、連夜渾身のプレイを繰り広げている。"}
{ "index" : {} }
{ "name": "東京タワー", "coord": { "lat": "35.65858", "lon": "139.745433"}, "description": "東京タワー(とうきょうタワー、英: Tokyo Tower)は、東京都港区芝公園にある総合電波塔とその愛称である。正式名称は日本電波塔(にっぽんでんぱとう)。"}
# curl -X POST http://localhost:9200/<Index Name>/<Type Name>/_bulk --data-binary @landmark.json
$ curl -X POST http://localhost:9200/landmark/sample/_bulk --data-binary @landmark.json
{
    "errors": false, 
    "items": [
        {
            "index": {
                "_id": "AVzJ-slvXdDNsyfvvuMV", 
                "_index": "landmark", 
                "_shards": {
                    "failed": 0, 
                    "successful": 1, 
                    "total": 2
                }, 
                "_type": "sample", 
                "_version": 1, 
                "created": true, 
                "result": "created", 
                "status": 201
            }
        }, 
        {
            "index": {
                "_id": "AVzJ-slwXdDNsyfvvuMW", 
                "_index": "landmark", 
                "_shards": {
                    "failed": 0, 
                    "successful": 1, 
                    "total": 2
                }, 
                "_type": "sample", 
                "_version": 1, 
                "created": true, 
                "result": "created", 
                "status": 201
            }
        }, 
        {
            "index": {
                "_id": "AVzJ-slwXdDNsyfvvuMX", 
                "_index": "landmark", 
                "_shards": {
                    "failed": 0, 
                    "successful": 1, 
                    "total": 2
                }, 
                "_type": "sample", 
                "_version": 1, 
                "created": true, 
                "result": "created", 
                "status": 201
            }
        }
    ], 
    "took": 286
}

検索

最後に検索。検索も json で query を作って実行する。 メソッドは GET を使用。 検索に使えるのは、調べ中。。。 AND 検索はもちろん、数値の比較とか範囲指定とかもできるみたい。

$ curl -s -X GET http://localhost:9200/landmark/sample/_search -d '{"query":{"match":{"description":"ジャズ"}}}' | jq .
{
  "took": 2,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.928525,
    "hits": [
      {
        "_index": "landmark",
        "_type": "sample",
        "_id": "AVzJ-slwXdDNsyfvvuMW",
        "_score": 0.928525,
        "_source": {
          "name": "BLUE NOTE TOKYO",
          "coord": {
            "lat": "35.661198",
            "lon": "139.716207"
          },
          "description": "N.Y.の「Blue Note」を本店に持つジャズクラブとして、南青山にオープンしたブルーノート東京。ジャズをはじめとする多様な音楽ジャンルのトップアーティストたちが、連夜渾身のプレイを繰り広げている。"
        }
      }
    ]
  }
}

まとめ

ひとまず、ローカルで立ち上げる→データを突っ込む→検索するの流れは試せてた。 次はどーやったら、がつっとデータ突っ込めるかを調べてみる。

社会人2年目もチェスト!!!

ついに社会人1年目終了。

東京の生活には全然慣れませんが、健康体でいれたので、 病欠による休みはゼロでした! (5分ぐらいの遅刻1回)

ただ、オリジン弁当とズブズブの関係なので、体重は7キロぐらい太りました。適正体重まであとちょっとですw


1年目は、技術力が高い先輩や同期に圧倒されつつも、ちょっとずつ自分の出来ることが増えていくのを感じれることができました。

自分の中で、学びたかったスクラムの知識を整理できたのも非常に身になりました!

pokotyamu.hatenablog.com

ただ、プログラミングスキルに関しては、まだまだ発展途上… 結局 Rails しか満足に勉強できてないし…(その Rails もまだまだ修行中)


今の会社では、自分のエンジニアとしてのキャリアパスに対して、色々挑戦させていただいたり、サポートしてくれる環境が整っているので、それに応えれるように準備してこうな!

この一年は自分のプログラミングスキルを一から見直す。

そのための勉強をするときのコストはなるべく惜しまないようにしていきたいと考えていて、2年目の初日、 Mac Book Pro の 2016年モデル買いました。

f:id:pokotyamu:20170403003051p:plain

2年目もチェスト!!!!!(宗りんおかえり!!!!!!)

新卒が真面目にアジャイル勉強してみた

社内勉強会で、アジャイルについて発表してみたので、こちらにも残しておきます!!!

当日資料はこちら↓↓

やってみた感想

きっかけは、なぜスクラムしているの?

資料の中にも書いていますが、自分が今回アジャイルについて勉強を始めたきっかけは、1番新しくチームメンバーとして入った方の「なぜスクラムしているの?その利点は何?」という発言でした。

実際に開発メンバーとして仕事をしている上で、何も知らない中スクラムを実施していることに違和感があったので、今回社内勉強会という機会を利用して、自分で勉強して、発表することにしました。

結果として、今のチームとしてここは良い点悪い点が言えて、そこから改善案も考えれるようになりました。

新卒が開発手法について口を出す

チームの中でも1番年下になりやすい新卒だからこそ見える世界もあると思います。 ただ、意見を1番いいにくいのも新卒な気がします(経験の問題や会社の風土だからという感じに流される)。

だからこそ、意見を言うための確かな知識を勉強して、経験じゃない根拠を持って話すことが出来れば、雰囲気に押し流されない意見を言えるようになるのではないか?とやってみて感じました。

アジャイル守破離の心構え

「これが正解」「こうやるもの」というわけではなく「何を目的にしていてこういうやり方が進められている」を知るのが「守」かな

これは、発表を終えたあと頂いた弊社 CTO からの感想です。

これも先程述べたように、原理原則を知った上で行動や意見をする必要があるという気づきにつながりました。

勉強会に対するモチベーション

準備期間が長く取れる場合ほど、途中で別のことしたくなったりなどあるかと。

今回はそれ対策で、途中途中で発表内容に使う本のまとめやセミナーのまとめを社内 Qiita やこのブログにまとめることで承認欲求を満たしつつ準備するというので乗り切りましたw

何かしらのリアクションやご意見を頂きつつ勉強することで、自分の見えてない見方や考えを途中の段階でも知れるきっかけになります! これは意外とよかったです。

pokotyamu.hatenablog.com

pokotyamu.hatenablog.com

※内容がネタバレ的になっちゃう系の場合はこそこそ頑張りましょう!

まとめ

スライドメインなので、さらっと終わりますが、今までチームとしてのやり方しかしらず、アジャイルスクラムを研修や独学で知識を付けてない方はぜひ一度勉強することをおすすめします!!!!