お世話になっております、開発チームの池上です。
最近ちょっとした検索機能にSolrを導入しました。Solrは検索エンジンのミドルウェアでご存知の方も多いと思います。大規模な構成による導入実績が豊富でWeb上にもたくさんの事例がありますが、今回は慎ましい構成の事例を紹介させて頂こうと思います。
使用しているSolrのバージョンは2012年1月時点で最新の3.5.0です。
なお、検索エンジンやSolrに関する基礎的な情報につきましては、有用な解説がすでに多数存在していますので割愛させて頂きます。
今回はつぎの前提条件と要件を意識して構築しました。
前提条件
- サーバは極力少なめで
- ミッションクリティカルな機能ではない
- データ量はそれほど多くない
要件
- 更新はある程度頻繁
- 遅くとも数分以内にはインデックスに更新を反映させたい
- 一般的なWeb検索のように「いい感じに見つける」よりは「(入力されたキーワードを)正確に見つける」
設計のポイント
サーバ構成
最低限サーバ障害は考慮して2台。更新の反映は若干の遅延が許容されていましたので、検索性能を安定させやすいMaster/Slave構成としました。
インデックスの単位
インデックスを更新するとSolrのキャッシュはクリアされてしまうとのこと。ただしインデックスの量があまり多くなければ、ある程度頻繁に更新をかけても十分性能が出ました。インデックス分割はせずに1つに。
インデックスの更新
更新リクエストは一旦キューにつめ、数十秒程度の間隔でまとめながら非同期処理するようにしました。SlaveからMasterへのポーリング間隔は1分前後で調整しています。頻繁にcommitするためoptimize処理も大事になってきますが、重い処理になるので5分~10分程度の間隔をあけて実行しています。
インデックス全体の再構築
設計当初、定期的に元データ全体で更新される項目があったことからSolr側でもそのタイミングでインデックス全体の再構築を行うように検討しました。最終的にその要件は無くなったのですが、インデックスと元データの間の整合性を維持しやすくなるので、全体再構築の仕組みをそのまま含めています。
全体再構築では、構築途中の中途半端な状態でレプリケーションされないように気をつける必要がありますし、再構築中も一定のレスポンスを確保する必要があります。そこで、Solrのマルチコア機能(複数のインデックスのインスタンス(core)を動的に作成したり名前を入れ替えたりすることが可能)を利用して、再構築中のcoreを退避させています。
通常時

再構築中
Slaveサーバ上でcore Aのインデックスのファイルをコピーし、それを元にcore A′を作成。core Aとcore A′を入れ替えて(swap)、core A′がqueryを受け付ける状態にした上でインデックスの再構築を開始する。
※再構築中は通常の更新リクエストキューの消化は一時休止

再構築完了
再構築が完了したら、再度swapを行い元の状態に戻す。core A′は削除。

インデックス方式
今回は素直にマッチさせたかったのでbi-gram(2-gram)を採用しています。ちなみに使用するTokenizerはCJKTokenizer --- 英単語の部分一致もしたいのでNG ---> NGramTokenizer --- 最大1024文字までと聞いてNG ---> NGramFilter + WhitespaceTokenizer と移り変わりました。また Normalizeについては、今回適用はしていないのですが java.text.Normalizer を使ったフィルター自作も良さそうですね。
おまけ(負荷テスト)
SolrMeterというSolr専用の負荷テストツールがあります。Solr本体に比べてあまり更新されてはいないようなのですが、シンプルに負荷をかけて処理時間を測ることは十分にできました。実行中に負荷のレベルを変更しながら、リアルタイムで更新される処理時間のグラフをみることができたりします。テストデータもテキストファイルで簡単に作ることができますので、ちょっとした負荷テストに便利だと思います。

おわりに
今回のようにサーバの台数が少ない構成 慎ましい構成の場合などは、大きめの処理などをやりづらいケースも多いと思いますが、Solrのマルチコア機能などはなかなか便利な機能なので活用できるとおもしろいかもしれませんね。