まず、dockerでElasticsearchとKibanaの環境を構築します。
docker-composeで書くと以下のような設定になります。
version: '3.1' services: elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:6.2.4 environment: - discovery.type=single-node ports: - 9200:9200 kibana: image: docker.elastic.co/kibana/kibana:6.2.4 links: - elasticsearch:elasticsearch ports: - 5601:5601 |
起動したらElasticsearchにアクセスできることを確認。
$ curl localhost:9200 { "name": "_5S39MV", "cluster_name": "docker-cluster", "cluster_uuid": "sqH8NB2KSrOOpkt2O9YTNg", "version": { "number": "6.2.4", "build_hash": "ccec39f", "build_date": "2018-04-12T20:37:28.497551Z", "build_snapshot": false, "lucene_version": "7.2.1", "minimum_wire_compatibility_version": "5.6.0", "minimum_index_compatibility_version": "5.0.0" }, "tagline": "You Know, for Search" } |
マッピングの作成
マッピングはRDBでいうスキーマのようなものです。
ElasticsearchはJSONを投入すると自動でマッピングを作成してくれますが、
実際本番で使う場合はちゃんとマッピングを定義した方が良いそうなのでマッピングの作成から始めます。
インデックスに以下のような形のデータを登録したいとします。
{ "id": "動画ID", "title": "動画タイトル", "release_date": "リリース日", "category": "カテゴリ", "group_name": "グループ名", "member_names": ["メンバー名", "メンバー名"], "setlist": ["楽曲名", "楽曲名"], "delivery": { "start": "YYYY-MM-DD HH:MM:SS", "end": "YYYY-MM-DD HH:MM:SS" }, "favorites": "お気に入り登録数" } |
その場合、マッピングは以下のように登録します。
用途に合わせて型やAnalyzerの設定を行う必要がありますがここでは省略します。
Elasticsearchではこちらに載っているデータ型が利用可能です。
PUT /videos/_mapping/hoge { "properties": { "id": { "type": "integer" }, "title": { "type": "text" }, "release_date": { "type": "date", "format": "yyyy-MM-dd" }, "category": { "type": "keyword" }, "group_name": { "type": "keyword" }, "member_names": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "setlist": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "delivery": { "properties": { "start": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" }, "end": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } } }, "favorites": { "type": "integer" } } } |
クエリ検索とフィルター
_search に対して条件を投げることで検索できます。
should が
OR 条件、
must が
AND 条件、
must_not が
NOT
filter が
WHERE みたいなものです。
GET /videos/hoge/_search { "query": { "bool": { "must": [ {"bool": {"should": [ {"match": {"setlist.keyword": "楽曲1"}}, {"match": {"group_name": "アーティスト"}} ]}}, {"bool": {"must_not": [ {"match": {"setlist.keyword": "楽曲2"}} ]}}, {"bool": {"must": [ {"match": {"category": "コンサート"}} ]}} ], "filter": {"range": { "release_date": { "gte": "2010-01-01", "lte": "2012-01-01" } }} } }, "_source": ["group_name", "category", "release_date"], "size": 3 } |
結果
{ "took": 9, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 30, "max_score": 6.005598, "hits": [ { "_index": "videos", "_type": "hoge", "_id": "894", "_score": 6.005598, "_source": { "release_date": "2011-03-19", "group_name": "アーティスト", "category": "コンサート" } }, { "_index": "videos", "_type": "hoge", "_id": "120", "_score": 5.314138, "_source": { "release_date": "2011-10-15", "group_name": "アーティスト", "category": "コンサート" } }, { "_index": "videos", "_type": "hoge", "_id": "906", "_score": 4.9989195, "_source": { "release_date": "2010-12-22", "group_name": "歌手", "category": "コンサート" } } ] } } |
SQLで書くとこのような感じでしょうか。
must と
filter はどちらもAND条件なのですが
filter は
score に影響を与えない点が違うそうです。
SELECT group_name, category, release_date FROM videos WHERE (「楽曲1」 in setlist OR group_name = "アーティスト") AND NOT (「楽曲2」 in setlist) AND category = "コンサート" AND release_date BETWEEN "2010-01-01" AND "2012-01-01" LIMIT 3; |
検索結果の重み付け
Elasticsearchはデフォルトで
score の高い順にソートされて返ってきます。
こちらをチューニングすることでよりニーズに合った検索結果を返すことができるようになります。
function_score と
field_value_factor を使用することでフィールドの値で重み付けを行うことができます。
functions 部分は
score = favorites x 0.1 x 5 と同じような感じです。
boost は条件にマッチした場合にscoreを加算する機能で
boost: 2 の場合はscoreが2倍になります。
GET /videos/hoge/_search { "query": { "function_score": { "query": { "bool": {"should": [ {"match": {"group_name": { "query": "アーティスト", "boost": 2 }}}, {"match": {"group_name": { "query": "歌手", "boost": 1.5 }}} ]} }, "functions": [ { "field_value_factor": { "field": "favorites", "factor": 0.2 }, "weight": 5 } ] } }, "_source": ["group_name", "favorites"], "size": 3 } |
結果
{ "took": 2, "timed_out": false, "_shards": { "total": 5, "successful": 5, "skipped": 0, "failed": 0 }, "hits": { "total": 1291, "max_score": 1331.5466, "hits": [ { "_index": "videos", "_type": "hoge", "_id": "223", "_score": 1331.5466, "_source": { "favorites": "642", "group_name": "アーティスト" } }, { "_index": "videos", "_type": "hoge", "_id": "216", "_score": 1123.427, "_source": { "favorites": "285", "group_name": "歌手" } }, { "_index": "videos", "_type": "hoge", "_id": "238", "_score": 1024.5858, "_source": { "favorites": "494", "group_name": "アーティスト" } } ] } } |
まとめ
簡単な検索と
score を調整する方法を少し理解できました。
今のところ元のscoreの計算方法が謎ですが、
元scoreの調整、マッピングの最適化、ユーザーによって重み付けを変えるクエリの設計などにより
複雑な条件でもニーズにマッチした結果が素早く返せるようになるのかなと思いました。
参考リンク
この記事を書いた人
最近書いた記事
- 2021.12.10React NativeでWallet風UIを実装する
- 2018.11.19Elasticsearchで簡単な検索とscoreを調整する方法
- 2018.10.05ECSをEC2からFargateに切り替える際の注意点
- 2018.09.12AKB48グループ映像倉庫のWeb版をリリースしました