目次

目次

Elasticsearchで簡単な検索とscoreを調整する方法

松木佑徒
松木佑徒
最終更新日2018/11/19 投稿日2018/11/19

まず、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 に対して条件を投げることで検索できます。 shouldOR 条件、mustAND 条件、 must_notNOT filterWHERE みたいなものです。

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で書くとこのような感じでしょうか。

mustfilter はどちらもAND条件なのですが filterscore に影響を与えない点が違うそうです。

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_scorefield_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の調整、マッピングの最適化、ユーザーによって重み付けを変えるクエリの設計などにより 複雑な条件でもニーズにマッチした結果が素早く返せるようになるのかなと思いました。

参考リンク

松木佑徒

目次