Elasticsearch
Aggregation, Parents & Children を活用してつくるツアー検索

Miyuki Endo
Hello! Elasticsearch.
6 min readSep 30, 2014

--

国内旅行者の25%がネット予約の時代
利用者に易しい検索をElasticsearchで実現しよう。

突然ですが、ウェブサイトから旅行の申し込みをする人がどのくらいいるか知っていますか?

国内旅行では2012年、約25%の旅行者が旅行予約サイトを利用して申し込んでいるとのことです。(日本旅行業協会サイトより: http://www.jata-net.or.jp/data/stats/2014/03.html)

年間国内宿泊旅行者数が、約4億5000万人泊 (日本旅行業協会サイトより : http://www.jata-net.or.jp/data/stats/2014/01.html) ということなので、仮に1回の旅行で5人グループが2泊するとした場合、4,500万回の国内旅行が催行され、その内の25%、1,125万回がウェブサイトから申し込みされていることになります。情報収集でサイトに訪れる利用者を含めるとさらに大変な来訪者になります。今後は25%という割合も増加するだろうし、ツアーサイトの利便性強化はやればやる程効果としてあらわれそうです。

利便性の強化といってもいろいろありますが、今回はそのなかでも重要な「ツアー検索」をElasticsearchを使って実現する方法について記述します。

使用する主なElasticsearchの機能

Aggregations : 次世代Facetと言われ、階層を意識したドキュメントの集計が行えるため、分析にも応用できる
Parents & Children : 親子関係を持ったドキュメント管理
Highlight : 検索キーワードをハイライトする

Index & Mapping

オプショナルツアーの検索をイメージし、以下のように定義します。

Tours Index

[ Index ]
・tours (ツアー)
[ Type ]
・op (オプショナルツアー : 親)
エリア、国、市、カテゴリー、名前、概要、会社、タグ、標準価格、通貨、催行日、所要時間(最小、最大)
・plan (各オプショナルツアーのプラン : 子)
親ID、名前、日付、価格、通貨

https://gist.github.com/miyuki25/91e9307a5def8baac70f.js

Memo
・analyzerは日本語向け(kuromoji_tokenizer + filter)と単語用(keyword tokenizer + filter) を事前に設定しています。

・Aggregationsを使った検索での使用が考えられるフィールドは、日本語解析だけでなく、”keyword”フィールドも設定しています。

・子のドキュメントには、自動で”_parent”フィールドに親のIDが入り、検索条件に指定したり、filedsの指定により中身を参照することができますが、ここではあえて”parent”フィールドを定義しています。

データ登録

サンプルデータを登録します。

https://gist.github.com/miyuki25/f0314f9284fb404d4146.js

Memo
・親の”tags”、子の”date”は配列で複数の値を入れています。

検索

キーワードによる検索を基本に、絞り込みが行えるようなUIを想定して次のクエリーを実行します。

  1. 利用者が、キーワード「ツアー」と入力し検索を開始

    キーワード「ツアー」を条件にクエリ実行、絞り込み項目 (tags、area -> country -> city を階層化)の表示
  2. 利用者が、city「パリ」でフィルタリングを行う

    キーワード「ツアー」& city「パリ」のクエリおよびフィルタリングを実行、絞り込み表示
  3. 利用者が、date「12/25」を指定

    キーワード「ツアー」& city「パリ」& 催行日「12/25」のクエリおよびフィルタリングを実行、絞り込み表示

これをクエリーにすると ↓

https://gist.github.com/miyuki25/f5c0768f6f4b7e833c3e.js

結果はこんな感じです。

https://gist.github.com/miyuki25/16224b27280c3a757524.js

結果はこちらでも参照可能です。

3つの検索は、親の”op”に対して行っていますので、すべて親のドキュメントが返ってきます。

Memo
・(#1,#2,#3) キーワードによる全文検索を行っていますが、”fields”の指定で”name”(ツアー名)の重要度を3倍に設定して検索しています。

・(#1,#2,#3) Aggregations (上記コードの”aggs” 部)ですが、areafilter, countryfilter, cityfilter を構造化させるために、”aggs”の中に次階層の”aggs”を記述しています。
また、すべての単語を返しているため、”doc_count”が検索結果件数と同一でこれ以上絞り込むことができない場合は表示しないなど、アプリケーション側の処理を入れると良いと思います。

・(#3) Parents & Children の構造の場合、親子両方のドキュメントを同時に返すことができません。クエリーによって親のみ、子のみのドキュメントが返ってくるので、もし検索結果一覧に親子どちらのデータも掲載する必要があるという場合は、別の方法(nested typeを使うとか、配列で管理するとか)を検討した方が良いかもしれません。

・(#1,#2,#3) “highlight” では、キーワードにマッチした ”name“と”description” 部分を強調タグで返してくれます。

このサンプルでは、親のドキュメント(オプショナルツアー)をベースに検索していますが、検索結果一覧上ではクリック、マウスオーバーなどのアクションにより、子のドキュメント(プラン、スケジュール、価格)を表示する等、柔軟に対応できそうです。
データ数が少ないためAggregationsの効果がわかりにくいかもしれませんが、データ数を増やして、価格帯などもAggregationにしてみると面白いと思います。

最後に

今回は、ツアーの中のプランごとに日程と価格が決められていることを想定して、データ登録や更新が柔軟に行えるよう親子関係の構造にしました。ツアー検索でもデータの構造や用途によって構築方法は変わってくると思いますが、いずれにしてもElasticsearchであれば充分に活用できそうです。

--

--