Miller氏は、2.9で大幅な速度向上を発見。2.9は3分少々の処理時間のところ、2.4は20分以上もかかっている。2.4と2.9のautocommitのデフォルト設定値の違いであることが判明した。2.9はautocommit=falseがデフォルトであり、2.4はtrueである。2.9はソートがより速くなった。先日の記事で紹介したとおり、スコアが不要の時はScorerを呼ばないようにすることができるようになった。複数セグメントのインデックスにおいて、IndexReaderのreopen()時に変更のないセグメントは再読み込みしないようにしてwarm-up時間を短縮できるようになっている:https://issues.apache.org/jira/browse/LUCENE-1483つまり、ソート時に使用するFieldCacheの内容が引き継がれるのでIndexReaderのreopen()後のソートがこれまでより速くなることが期待できる。2.4から導入されたNIOFSDirectoryはぜひLinuxプラットフォームでは積極的に使うことを考慮すべきである。一般的にパフォーマンスが良くなる。Windowsでは逆に遅くなってしまい、現在のところWindowsではFSDirectoryを使った方が良い。1.3でもサポートされることとなった。検索エンジン・サーバ側に検索処理時間のタイムアウトを設けるのは、検索クライアント側でタイムアウトを設定するのと比較し、次のようなメリットがあると考えられる:クライアント側でタイムアウトを設定すると、検索がタイムアウトになったときは検索結果が一切得られないが、サーバ側でタイムアウトが設定できれば、タイムアウトに達するまでの時間に収集した一部の検索結果が得られる。これは特に分散インデックスを複数のshardを使って横断検索する場合に重要になる。クライアント側でタイムアウトを設定してしまうと、サーバ側の検索処理が無駄になってしまう。サーバ側では使用されることのない検索結果を収集するためにサーバリソースを消費してしまう。検索サーバ側にタイムアウトを設定できれば、このような無駄なリソース消費はなくなる。Fridayホワイトペーパー「LudiaとSolrにおけるファセットカウント取得と絞り込み検索」の公開ホワイトペーパー「LudiaとSolrにおけるファセットカウント取得と絞り込み検索」を公開した。2.3の新機能である。これまでインデックスをオープンするのにIndexReader.open()メソッドしかなかったが、reopen()が加わった。アプリケーションでreopen()を使うことで、open()メソッドよりも性能改善が見込める。使い方は、reopen()はインスタンスメソッドなので(open()はクラスメソッドである)、既存のIndexReaderオブジェクトに対してreopen()を呼び出し、新しいIndexReaderオブジェクトを得る。古いIndexReaderオブジェクトはその後close()をする。インデックスが更新されたら、検索結果に更新分を反映させるためにこれまではIndexReader.open()を呼んで新しいIndexReaderオブジェクトを取得する必要があった。2.3では新しいIndexReaderを取得するのにreopen()が使える、ということである。reopen()はインデックスの更新差分のみロードするので性能が改善するのである。どのくらい改善するのか測定してみたところ、次のようになった。プログラムはランダムな単語が100万単語ずつ増えていくインデックスから任意の1単語を100万単語増えるごとに1回、計5回検索したときの合計タイムを計測するものである(したがってプログラム終了時はインデックス内には500万単語が存在する)。1262回ずつ計測してみたが、24%から34%程度reopen()の方が速くなっていることがわかる。FridayLuceneインデックスのバージョンの活用LuceneのインデックスがWebアプリケーションサーバのプログラムから検索できるようになっている環境を考える。GETメソッドで投入されるとする。このとき、リクエストパラメータ"q"に検索式が設定されることとする。/search?q=luceneすると、Luceneの検索サービスを提供しているWebアプリ側はqパラメータから検索式を取り出して検索し、検索結果をHTMLなどに整形してクライアントに返す。/search?q=luceneこれは上とまったく同じリクエストなので、Luceneのインデックスが同じである限り(そして、ランダムに広告などを切り替え表示するのでもない限り)、先の結果と同じHTMLを出力して返すこととなる。これはまったく無駄な処理だ。このようなときに、クライアント側にキャッシュ機構があればそれを活用できるというHTTPの仕様がある。そこで、これをどのように実現するか考えてみよう。クライアントのキャッシュを活用するには、検索結果をサーバから返すときに、Last-Modifiedというレスポンスヘッダをつけてクライアントに渡すようにする。Last-Modifiedにはインデックスの最終更新日を設定すればいいだろう。すると、キャッシュ機構を持つクライアントは、同じ検索リクエストを発行するときにIf-Modified-Sinceというリクエストヘッダをつけて送ってくる。If-Modified-Sinceに設定されているのはLast-Modifiedで受け取ったインデックスの最終更新日である。Modified)を返せばよい。304を受け取ったクライアントは、キャッシュ内のコンテンツを再利用するので、応答速度があがる。具体的な実装方法はというと、サーブレットでgetLastModified()をインデックスの最終更新日を返すようにオーバライドするだけである。するとコンテナは、Last-Modifiedにこのコンテナの値を設定して返してくれる。Modified)を返すかを自動的に判断する。Luceneのインデックスの最終更新日の取得には、java.io.Fileクラスなどを使う必要はなく、インデックスに振られているバージョンを使うことができる。JavaサーブレットとHTTPの仕様、およびインデックスのバージョンをうまく組み合わせることで、検索応答性能を大幅に上げることができる。サーバのCPUの節約にもなるので、負荷がそれなりにある状況下ではスループット(=QPS)の向上にも役立つだろう。Monday現trunkでより速くなったLuceneのインデクシング処理Luceneのインデックス作成処理性能は進歩を続けている。現在のtrunkのパフォーマンスはかなりあがっている。グラフは10のユニークワードからなる1つのフィールドを持つドキュメントを1万件登録したときの処理時間をプロットしたものである。2.0、2.1、2.2およびtrunkで計測した。バージョンがあがるほどだいたい速くなっているが、特に現trunkでの処理時間の短縮が際立っている。trunkバージョンからFieldクラスにsetValue()というメソッドが追加された。このメソッドを使うと、Fieldインスタンスをnewせずに使いまわすことができる。そのため、GCの発生を抑制し、結果的にパフォーマンスの向上に貢献するようである。今回の簡単なテストではその効果があまり見られなかったのでグラフには掲載していないが、プログラムによっては効果があるかもしれない。比較のポイント商用検索エンジンを購入した企業担当者は読まないでください。McCandlessFastVectorHighlighterについて解説記事を寄稿しました。