概要
elasticsearchのterms filterとfunction_scoreを使って、パーソナライズのようなことをしてみます。
blogを検索をすることを考えます。
blogには下記のような属性があるとします。
属性 | 説明 |
---|---|
source | 作者 |
date | 投稿日付 |
text | 本文 |
tag | ブログのカテゴリのタグ |
blogを検索するユーザには下記のような属性があるとします。
属性 | 説明 |
---|---|
follows | お気に入りユーザ的なやつ |
tags | お気に入りのカテゴリとか |
たとえば、blogを全文検索したときのソート結果について、フォローしているユーザの日記や、
好きなカテゴリの日記を上位に表示したりということをやります。
準備
blogのインデックスを作ります。
curl -XPUT 'http://localhost:9200/blog' -d '
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"item": {
"_all": {
"enabled": false
},
"_source": {
"enabled": true
},
"_type": {
"index": "no"
},
"_id": {
"index": "not_analyzed",
"store": "yes"
},
"_routing": {},
"properties": {
"text": {
"type": "string",
"store": "yes"
},
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"tag": {
"type": "string"
},
"source": {
"type": "integer"
}
}
}
}
}'
userのインデックスを作ります。
curl -XPUT localhost:9200/users -d '
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"source": {
"_all": {
"enabled": false
},
"_source": {
"enabled": true
},
"_type": {
"index": "no"
},
"_id": {
"index": "not_analyzed",
"store": "yes"
},
"_routing": {},
"properties": {
"follows": {
"type": "integer"
},
"tags": {
"type": "string"
}
}
}
}
}'
blogのドキュメントを作ります。
curl -XPUT "http://localhost:9200/blog/item/1" -d '{
"text": "aaa bbb ccc",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "111"
}'
curl -XPUT "http://localhost:9200/blog/item/2" -d '{
"text": "bbb ccc ddd eee",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "222"
}'
curl -XPUT "http://localhost:9200/blog/item/3" -d '{
"text": "ccc ddd eee",
"date": "2015-06-22 15:00:00",
"tag": "music",
"source": "333"
}'
curl -XPUT "http://localhost:9200/blog/item/4" -d '{
"text": "aaa bbb ccc",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "444"
}'
userを作ります。
ユーザ333と444を気に入っており、音楽カテゴリのブログを好むユーザです。
curl -XPUT "http://localhost:9200/users/user/1" -d '{
"follows": [333,444],
"tags": "music"
}'
検索
普通の検索
curl -XGET "http://localhost:9200/blog/_search" -d '{
"query" : {
"simple_query_string" : {
"query": "ccc",
"fields": ["text"]
}
}
}'
結果
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 0.3884282,
"hits": [
{
"_index": "blog",
"_type": "item",
"_id": "1",
"_score": 0.3884282,
"_source": {
"text": "aaa bbb ccc",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "111"
}
},
{
"_index": "blog",
"_type": "item",
"_id": "2",
"_score": 0.3884282,
"_source": {
"text": "bbb ccc ddd eee",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "222"
}
},
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ユーザ1がフォローしているユーザと気に入っているタグを重視してソート
ユーザ1がフォローしているユーザ(333と444)と、
気に入っているタグ(music)を指定して検索します。
function_scoreで、filterがマッチしたドキュメントのスコアを2倍しています。
curl -XGET "http://localhost:9200/blog/_search" -d '{
"query": {
"function_score": {
"query": {
"simple_query_string": {
"query": "ccc",
"fields": [
"text"
]
}
},
"functions": [
{
"filter": {
"terms": {
"source": [
333,
444
]
}
},
"weight": 2
},
{
"filter": {
"terms": {
"tag": [
"music"
]
}
},
"weight": 2
}
],
"score_mode": "multiply"
}
}
}'
結果
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 4,
"max_score": 1.5537128,
"hits": [
{
"_index": "blog",
"_type": "item",
"_id": "3",
"_score": 1.5537128,
"_source": {
"text": "ccc ddd eee",
"date": "2015-06-22 15:00:00",
"tag": "music",
"source": "333"
}
},
{
"_index": "blog",
"_type": "item",
"_id": "4",
"_score": 0.7768564,
"_source": {
"text": "aaa bbb ccc",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "444"
}
},
{
"_index": "blog",
"_type": "item",
"_id": "1",
"_score": 0.3884282,
"_source": {
"text": "aaa bbb ccc",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "111"
}
},
{
"_index": "blog",
"_type": "item",
"_id": "2",
"_score": 0.3884282,
"_source": {
"text": "bbb ccc ddd eee",
"date": "2015-06-22 15:00:00",
"tag": "game",
"source": "222"
}
}
]
}
}
lookup
なお、上記のクエリは、terms filterのlookupという機能を使用して下記のように書くことが可能です。
usersインデックスに作ったユーザ1のfollows,tags属性をフィルタにセットして検索しています。
https://www.elastic.co/guide/en/elasticsearch/reference/1.4/query-dsl-terms-filter.html#_terms_lookup_twitter_example
これを使ったほうが効率が良さそうです。
(たくさんフォローしてたりしてもクエリが長くならないし、lookupキャッシュとかも作ってくれるらしい。)
curl -XGET "http://localhost:9200/blog/_search" -d '{
"query": {
"function_score": {
"query": {
"simple_query_string" : {
"query": "ccc",
"fields": ["text"]
}
},
"functions": [
{
"filter": {
"terms": {
"source" : {
"index" : "users",
"type" : "user",
"id" : "1",
"path" : "follows"
}
}
},
"weight": 2
},
{
"filter": {
"terms": {
"tag" : {
"index" : "users",
"type" : "user",
"id" : "1",
"path" : "tags"
}
}
},
"weight": 2
}
],
"score_mode": "multiply"
}
}
}'