RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

Ludia と Solr におけるファセットカウント取得と絞り込み検索2008 年 5 月 14 日
株式会社 ロンウイット

関口宏司

はじめにWeb アプリケーションにてユーザを目的のページ（コンテンツ）に効率よく誘導する方法として、最近は「フリーワード検索」と「ナビゲーションリンクの提示」を組み合わせる手法が注目されている。まず「フリーワード検索」では、ユーザは探したいコンテンツから連想する 1 つ以上の単語を「検索窓」に入力して[検索]ボタンをクリックすることで、候補となるコンテンツの一覧表示（検索結果一覧画面）のページを得る。同じページに図 1 のようなリンクが表示されることがあるが、このリンクが「ナビゲーションリンク」である。このリンクをクリックすることで＜図 2＞リンクに表示された条件で最初の検索結果を絞り込み検索することができる。

図 1 や図 2 にあるとおり、ナビゲーションリンクの隣には括弧でくくられた数値が表示されている。この数値は当該リンクをクリックすると、検索結果を何件まで絞り込めるかを示す。ユーザはリンクのラベル文字列とこの数値情報を参考にして、どのナビゲーションリンクをクリックするかを判断する。本書ではこの数値情報を「ファセットカウント」と呼ぶ。
本書は上記のようなナビゲーション導線を実装する Web アプリケーションを想定し、高負荷状態において検索エンジンがどの程度のパフォーマンスを発揮するかを計測し、その結果を記 し た も の で あ る 。 な お 、 検 索 エ ン ジ ン に は Ludia とApache Solr（以下 Solr）を取り上げることとする。

＜図 1＞
絞り込み検索の結果、新しい検索結果一覧と「ナビゲーションリンク」が表示される。このリンクをクリックすると、さらに絞り込み検索が行われる（図 2）。リンクをクリックするという簡単なオペレーションを繰り返すだけでユーザを目的のページに効率よく誘導できるのが特徴である。
Copyright © RONDHUIT Co.,Ltd.
1

RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

demo=# select * from pgs2getoption();

検証環境-[ RECORD 1 ]------+--------

検証環境を下表に示す：
項目

説明

max_n_sort_result| 10

enable_seqscan| off

seqscan_flags|1| 31

ハードウ

機種名

DELL Inspiron 1501

sen_index_flags

ェア

CPUAMD Athlon(TM) 64 X2 デュ

max_n_index_cache

アルコア・プロセッサ TK-53

initial_n_segments | 512

メモリ

| 16

1GB(512MBx2) デ ュ ア ル チ ャ
ネル DDR2-SDRAM メモリ

demo=# show work_mem;

ハードディ

120GB SATA HDD（5400 回-[ RECORD 1 ]--スク転）

work_mem | 10MB

ネットワー

100MB Ethernet
demo=# show shared_buffers;

クOS

Windows XP Professional-[ RECORD 1 ]--+-----

SP2

shared_buffers | 32MB

Web サーバ

Apache2.2 + FastCGI

Ludia

Ludia

1.4.0

また Ludia で全文検索の対象となる PostgreSQL のテー(*1)

MeCab0.96

ブル定義は次のとおりである（紙幅の関係上、一部略称をSenna1.0.9用いた）：DB

PostgreSQL 8.2.6

Solr

Java

build 1.6.0_05-b13(*1)

Tomcat5.5.26

Solr

r635924

demo=# ¥d auction_item
Table "public.auction_item"
Column|Type| Mod

負荷試験ツール

Apache JMeter 2.2---------------+------------------+-----------

検索対象データ

Yahoo!オークション商品データ

auction_id| charvar(16)| not null

約 200 万件

category_id| charvar(16)|(*1) Ludia と Solr は、本検証を開始した当時の最新バ

category_path | charvar(256)ージョンを利用。

title| charvar(128)| not nullurl

| charvar(128)|

検索エンジンを実行するサーバマシンには DELL 製のノート

seller_id| charvar(24)|PC を使用した（本体価格：57,124 円）。ハードディスクはimage1_url| charvar(256)|低速なため今回の検証では、Ludia、Solr ともに OS のペ

image2_url| charvar(256)|ージキャッシュが十分に効いた状態で性能を測定することとし

image3_url| charvar(256)|た。

init_price| integer| not nullprice| integer|

全検証を通じて Ludia（Senna および PostgreSQL）のbids| integer|

主なパラメータは次のとおり設定した：

start_time| timestamp w/o TZ |end_time| timestamp w/o TZ |Copyright © RONDHUIT Co.,Ltd.
2|

RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

description| charvar(8192)|

で検索し、XML 形式で検索結果を得る。アーキテクチャのprice_group| integer|

異なる両者をなるべく公平に比較するため、検証用の簡易

date_group| integer|

フロント CGI プログラムを Ruby on Rails でそれぞれの検索エンジン用に構築し、最終的な検索結果画面を HTML

Indexes:"auction_item_pkey" PRIMARY KEY, btreeで得られるようにした。検索エンジンへの負荷がけはこの
Rails の CGI プログラムを通じて行った（図 3）。

(auction_id)"fidx" fulltext ((description::text))"idx_date" btree (end_time)"idx_date_group" btree (date_group)"idx_price" btree (price)"idx_price_group" btree (price_group)
Solr のスキーマ設定は次のとおりである：
フィールド名型

概要

auction_id

string

オークション ID

category_id

stringカテゴリ ID

category_path

text_ja

カテゴリパス

title

text_ja

商品タイトルurlstring

商品 URLseller_idstring

出品者 ID

image1_url

string

画像 1 の URL

image2_url

string

画像 2 の URL

image3_url

string

画像 3 の URL

init_price

sint

開始価格

price

sint

現在価格

start_time

date

オークション開始日時

end_timedate

オークション終了日時

description

text_ja

商品説明timestampdate

インデックス登録日時＜図 3＞
サーバに負荷をかけるクライアントマシンであるが、ノート PC1 台で行った。これは事前調査の段階でほとんどの場合においてサーバの CPU がボトルネックとなることが判明したため、ノート PC 1 台の負荷で十分であったためである。クライアントからは JMeter で 100 または 30 同時ユーザ（スレッド）を起動して負荷をかけた（試験項目によりユーザ数を変更）。

フロントアプリケーションについて
図 3 のフロントアプリケーションでは次のような HTTP リクエス
トを受け取り、Rails のコントローラのメソッドにて検索エンジンを呼び出し、得られた検索結果をそれぞれのビュー

上記フィールドのうち auction_id をユニークキーとし、description フィールドをデフォルトの検索フィールドに指定した。なお、text_ja はインデックスに索引付けする日本語のテキストを示し、形態素解析器 Sen により単語分割をしている。

（.rhtml）にて HTML に変換している：
http://localhost/contoller/method

ここで"controller"の部分に"ludia"または"solr"を指定することでテスト対象を Ludia または Solr に切り替えられるようになっている。また"method"には次の指定ができるよう

Ludia は SQL の SELECT 文で検索し、レコードの結果セになっており、これにより実行する検索方法を切り替える：ットとして検索結果を得る。一方で Solr は HTTP リクエスト
Copyright © RONDHUIT Co.,Ltd.
3

RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

methodす機能を指す。Senna では snippet と呼ばれる。

意味query

通常の検索のみを行う

facet

通常の検索に加え、ファセットカウントの取得をそれぞれのメソッドの実装方法であるが、Solr に関しては標

gfacet行う。gfacet は Ludia のみ有効（後述）

準的な機能であるため、ここでは説明を省略する。Ludia に

filter

通常の検索に加え、絞り込み検索を行う。

ついては各メソッドを次のように実装している：

gfilter

gfilter は Ludia のみ有効（後述）■/ludia/query

HTTP リクエストにはさらに下記のパラメータを指定できるよう"select 必 要 な カ ラ ム リ ス ト from auction_itemになっている：

where 全 文 検 索 条 件 " で 検 索 し た 後 、 "selectpgs2getnhits()"でヒット件数を取得する。
全 method 共通パラメータq

検索式を指定（必須）■/ludia/facetstart

結果表示オフセット（デフォルト=0）

通常の検索とヒット件数の取得は/ludia/query と同じでrows

結果表示数（デフォルト=10）ある。これに加えて、ファセットカウントの取得は selecthl(*2)

ハイライト on または off（デフォルト）。

count(*)の where 句の全文検索条件の後に AND で価格帯や日付の範囲を指定する（この場合、Ludia の制限に

facet メソッドに有効なパラメータ
price.N.from

N=0,1,2,…でファセットにする価格帯

price.N.toを指定

date.N.from

N=0,1,2,…でファセットにするオークシ

date.N.toョン終了日の範囲を指定■/ludia/gfacet
通常の検索とヒット件数の取得は/ludia/query と同じでgfacet メソッドに有効なパラメータ
facet.priceより pgs2getnhits()は利用できない）。

on または off（デフォルト）。on のときはprice_group カラムで group by をある。これに加えて、ファセットカウントの取得は"group by
price_group"または"group by date_group"で一度で取得する。

実行しファセットカウントを一度で取得
facet.date

on または off（デフォルト）。on のときはdate_group カラムで group by を実行しファセットカウントを一度で取得
filter メソッドに有効なパラメータ

price.fromwhere 全文検索条件 AND フィルタ条件"で検索し、"select count(*) from auction_item where 全文の場合、Ludia の制限により pgs2getnhits()は利用でき

date.from

絞り込むオークション終了日の範囲をdate.to指定ない）。
■/ludia/gfilter

gfilter に有効なパラメータ
price_group カラムの integer 値を指定
fq.date"select 必 要 な カ ラ ム リ ス ト from auction_item

検索条件 AND フィルタ条件"でヒット件数を取得する（こ絞り込む価格帯を指定

price.tofq.price■/ludia/filter

date_group カラムの integer 値を指定(*2)hl パラメータのハイライトとは、検索結果表示画面で/ludia/filter と 同 じ で あ る が 、 フ ィ ル タ 条 件 をprice_group または date_group カラムを用いて行う。
なお、1 つの検索リクエストを処理するために複数のSELECT 文を発行するときは、1 トランザクションで処理するようにし、ISOLATION LEVEL は SERIALIZABLE を指検索語の周辺文字列を検索語の強調表示付きで切り出
Copyright © RONDHUIT Co.,Ltd.
4

RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

シナリオである。このとき Ludia では 1 ユーザでもレスポンスが定した。

悪く、負荷試験が実施できなかった。よって/solr/facet のみ 100 同時ユーザで実行した。

検索性能サマリー(g)filter テストは任意に選択した価格帯とオークション終負荷試験の結果を以下の表に示す：

了日の同時指定による絞り込み検索のシナリオである。
試験種別
queryhl
facet

検索

平均応答エンジン

時間（ms）(g)filter テストは/xxx/filter と/ludia/gfilter を 100
QPS

Ludia

1,10045.3

Solr920

52.0

Ludia

40040.0

Solr

900

17.5

Ludia

計測せず

Solr

計測せず

1,050

44.4

gfacetLudia

filterLudia

1,20838.2

Solr

420

113.8

Ludia

1,17738.8

gfilter

計測せず

計測せず同時ユーザで実行した。

考察
query テストでは Ludia と Solr で検索性能（平均応答時間と QPS）の大きな差は見られなかった。Solr は前々回のレポート（RONDHUIT REPORT Vol.1）「Solr の検索性能」で示した性能（200 同時ユーザ、200QPS）に及ばないが、これは今回のテストでは Rails のフロント Web アプリケーションが稼動しているためである。実際にフロントの Web アプリケーションが Solr のレスポンスをパースするのに忙しくなってしまい、Solr より先にフロントの Web アプリケーションがボト

すべてのテストで検索語は Solr のインデックスから任意にルネックになるのは本番システムなどでもよく見られる現象で100 個選択した単語を JMeter でループさせて使用した。

ある。

Solr のインデックスから選択した理由は、インデックスから単語を抽出するプログラムが Lucene で簡単に書けるためである。なお、選択した 100 個の単語で Ludia の検索が問題なくできることは確認してある（しかしながら当然検索結果件数などは両者で異なる）。

hl テストでは Ludia は平均応答時間と QPS の両方でSolr のほぼ 2 倍の性能を達成した。また前述のとおり、この結果は Solr がピークを示した QPS での同時ユーザ数（30同時ユーザ）である。Ludia は 40 同時ユーザまで QPS が伸びるので、Ludia のピーク性能はこの表の数値よりもさらにquery テストでは/xxx/query（xxx は ludia または solrを表す。以下同じ）を 100 同時ユーザで実行した。

よい結果となる。
hl テストにおける Ludia と Solr の性能の違いは、それぞれhl テストでは/xxx/query に hl=on を指定して 30 同時の検索エンジンライブラリのスニペット抽出アルゴリズムの違いユーザで実行した。同時 30 ユーザは Solr がピークのスルーからくるものである。Senna（Ludia の検索エンジンライブラプットを示したユーザ数である（Ludia はもう少し性能がよく、同時 40 ユーザでピークのスループットを示した）。なお、
Ludia と Solr でほぼ同程度の長さの断片（ハイライトスニペ

リ）ではスニペット取得対象文字列の先頭から検索語文字列との単純一致を見ている。これに対し Lucene（Solr の検索エンジンライブラリ）ではハイライト取得対象文字列は再度トークナイズ処理を経て検索語トークンと比較され、スコアット）が返るように調整してある。

計算がなされてもっとも高いスコアを獲得した断片を返すといfacet および gfacet テストは 5 個の価格帯と 7 個のオークション終了日範囲の計 12 個のファセットカウントを取得するう凝った処理を行っている。hl テストの Ludia と Solr の性能の差は、この両者のロジックの差が顕著に現れたものである。

Copyright © RONDHUIT Co.,Ltd.
5

RONDHUIT REPORT Vol.3 - Ludia & Solr - faceted/filtered search comparison

理テーブルが必要になる、(b)ファセットカウント集計用の大facet テストでは Ludia が相当重くなってしまい、負荷試験量のバッチ実行が必要になる、という点で管理・運用コストのを実施することができなかった。Solr は負荷試験を実施する増大をまねくこととなる。さらに、(c)バッチ実行時にファセットことができ、query と比較するとやや重くなるにとどまった。の条件が静的に決定してしまう、(d)フリーワード検索の結果と連動させるのが困難、という機動性・柔軟性に欠けるシ

filter テストでは Solr は平均応答時間と QPS の両方でステムを生み出す原因となる。

Ludia のほぼ 3 倍の性能を達成した。Ludia の filter テス
トの結果は Ludia の query テストと比べるとやや重くなってSolr の場合はファセットカウントを動的に集計しても十分にいる。このことから全文検索条件に RDB クエリ条件を付加し速く処理することができる。Solr を使用すれば RDB で必要て絞り込み検索を実施すると、全文検索条件単独のクエリなバッチ実行は一切不要となるため、管理・運用コストを抑よりも遅くなる、ということがわかる。逆に Solr の filter テストえることが可能である。また動的に実行できることから、適当は Solr の query テストと比べると 2 倍の性能向上となってな絞り込み条件をセットにした「キャンペーン」を定義して新しおり、全文検索条件にフィルタリング条件を加えて絞り込みいリンクをトップページに設置することも比較的簡単に行える。

検索を実施すると、全文検索条件単独のクエリよりも速くなナビゲーションの導線を増設するのに、フロントの Web アプリることが確認できた。Solr は過去に使用したフィルタリング条ケーションへの少しの変更で対応できるようになる。

件と結果をキャッシュすることができ、また、絞り込み検索ではそうでない検索よりも検索結果一覧が小さくなるために通ファセットカウントはユーザに対し、「このリンクをクリックすると常の検索よりも速くなると考えられる。今回の検証により、検索結果を xx 件に絞れる」ということを伝える重要な情報とSolr の絞り込み検索が実際に高速に処理されていることがなっており、ファセットカウントを表示する Web サイトは今後裏付けられた。ナビゲーションリンクをクリックして検索結果をも増えていくと考えられる。このとき、その実装方法として次々に絞り込んでいく場合、ユーザは絞り込むたびに速いレRDB による静的事前バッチ集計方式で構築するのか、スポンスを期待するが、Solr のレスポンスはユーザの期待感Solr などの検索エンジンを導入して動的リアルタイム集計方と合致しているといえるだろう。

式で構築するのかでその後のシステムの TCO や機動性にも大きく違いが出てくるだろう。現在は RDB による静的事前バ

Ludia の gfilter は filter とあまり変わらなかった。

ッチ集計方式が一般に広く採用されているが、今後は検索エンジンによる動的リアルタイム集計方式が認知され、適用するプロジェクトが徐々に広まっていくと思われる。

まとめfacet テストが Ludia では実施できなかった。チューニングに（株）ロンウイットについてより改善できる可能性はあるが、今回のテスト実施期間内ロンウイットはオープンソースの全文検索エンジン Luceneでその方法を見つけることはできなかった。しかし、どうやら/Solr を企業システムに導入する支援サービス事業を展開

RDB データソースからの動的ファセットカウント取得のコストしている。

が高いことは一般に知られている事実のようである。このため、お問い合わせ先

ファセットカウント集計のバッチを実行して、別に作成したファ〒100-0005

セットカウント保存テーブルにあらかじめ記録しておき、HTTP

東京都千代田区丸の内 1-1-3 AIG ビル B1F

リクエストが来たときにそのテーブルからカウントを取得して表

電話：03-5288-5927

示する、という実装が多くのプロジェクトで採用されている。

メール：sales@rondhuit.com

FAX：03-5288-5353

ホームページ：http://www.rondhuit.com/
しかしこの方法は、(a)ファセットカウント保存用の大量の管
Copyright © RONDHUIT Co.,Ltd.
6