本記事は、Mark Miller 著「Scaling Lucene and Solr」の日本語訳である。
日本語訳 © 2011 Basis Technology
訳注:原典が書かれた2009年前半の時点では、Solr 1.4 は開発中であったため、単に Solr と書かれているものは、Solr 1.3 を指します。本記事の内容の一部は、Solr 1.4 またはそれ以降の版には適用できない可能性があることをご承知置き下さい。
目次
はじめに
Lucene は非常に複雑で強力な検索ライブラリーで、Solr は Lucene ライブラリーを基にして構築されたエンタープライズサーチエンジンです。Lucene は、すばらしい情報検索中核技術を提供する小さくまとまったパッケージです。Solr は、Lucene の基本機能に加えて、どのプラットフォームでも使えるインターフェース、ファセット処理、レプリケーション機能、キャッシング、大規模な分散検索などを提供します。この記事では、読者が Lucene/Solr の基礎 に精通していること、また、Lucene スタックのスケーラビリィティー(規模拡張性)の調査担当者と協力して仕事をしていることを前提としています。
LuceneとSolr は、どちらも規模の変更に非常に柔軟に対応できる検索ソリューションです。多数の要因にもよりますが、5百万〜8千万の文書に関するLucene/Solr の索引なら、単一のコンピューターで容易にホスティングできます。分散ソリューションでは、何十億もの文書を1秒以内で検索できます。それ以上の文書量の場合は、個々のサーバーの索引レプリケーション機能により、クエリーのスループットを調整できます。
Lucene/Solr の規模の変化に対応させる標準手順を説明します。まず、1台のコンピューターのパフォーマンスを最適化します。次に、レプリケーション機能により、大容量クエリーを複数のコンピューターに分散させます。索引の規模が大きすぎて1台のコンピューターに収まらなくなったら、索引を複数のコンピューターに分散させます(索引を「シャード化」する)。最後に、クエリーの容量や索引の規模が大きい場合は、各シャードをレプリケートします(シャードは、分散構成における1 台のサーバーを指します)。
拡張の進行過程の図を見ると、この進行過程を図解で把握することができます。最初の段階では1 台のコンピューターがすべてのクエリーを処理して、すべての索引を更新します。次の段階はマスター/スレーブ構成で、マスターがすべての更新を処理し、索引のすべての変更をスレーブにレプリケート(複製)します。各マスターに対応するスレーブは、すべてのクエリーを処理します。また、索引を分割して複数のコンピューター(分散 Solr を使用している環境ではシャードと呼びます)に分散させることもできます。各シャードにおいて、索引が更新され、クエリーが処理されます。最後の段階では、各シャードをレプリケート用に設定することができます。この場合も、更新は各シャードのマスターが処理し、各シャードのすべてのスレーブがクエリーを処理します。
以上が、Lucene/Solr アプリケーションをスケールさせる際のベストプラクティスの4段階です。
単一サーバーの最適化
Lucene/Solr のパフォーマンスを最適化する際の要点は構成(訳注:configuration の訳。設定ファイルの内容のこと。)にあります。Lucene/Solr の開発者は、通常の場合、工場出荷状態(訳注:Solr 解凍直後の構成)でパフォーマンスを最適化できるよう努力しますが、開発者自身の環境を正しく調整することによって、パフォーマンスをかなり改善することができます。どのLucene/Solr のインストールにも多くの要因があり、構成やアーキテクチャーに関する多くの検討事項が、こういった要因に依存しています。これは、取りも直さず、基本的にあらゆることを読者独自の環境と突き合わせてテストし、どれが読者の環境に最適であるかを見極めなくてはならないことを意味します。しかし、このテスト作業にも利点はあります。パフォーマンスを最適化する方法を絞り出す過程において、他の事項にも適用できる一般化規則が多く見つかり、ヒントも多く見つかります。検索のパフォーマンスに影響するアイデアの一部をこれから説明します。読者は、こういったアイデアの中から自分の状況に該当するものを選び、それを綿密に研究した後で(この記事の最後にあるリンクから始めてください)微調整し、読者の要件を満たすかどうかをテストしてください。Lucene の contrib (寄贈)部分には、非常に強力なベンチマークモジュールがあります。これを使用することを考慮してください。パフォーマンスには、検索側、索引側の2つの領域があります。この記事では検索側のパフォーマンスに影響する事柄を中心に説明しますが、検索パフォーマンスに影響する索引関連の意思決定事項にも触れます。
索引の管理
索引は、初めからパフォーマンスを考慮して設定することが重要です。必ず最初に正しい設定を選択することにより、コンテンツの索引を後で一から再度生成するはめにならないようにしてください。大規模の索引を扱っている場合、索引の生成はかなりの時間を要します。また、生成した索引をどのように維持していくかを考慮することも重要です。Lucene/Solr では、索引の構造に関して読者が広範な決定をできるため、どのような索引構造にしてパフォーマンスを最適化するかは、読者次第です。
項頻度
データによっては、フィールドに対して Fieldable.omitTf(Boolean) を使うと便利な場合がよくあります。これにより、フィールドは、項頻度(term frequency;ターム頻度とも)、位置、ペイロードなしで索引付けされます。これは、フィールドが非常に短い場合、または全文でない場合に便利です。役に立たないデータ構造を除外することにより、パフォーマンスが向上します。この最適化機能が Luceneに搭載されたのがつい最近であるため、Solr ではまだ実現されていません。しかし、SOLR-739 には現在実装作業中で、まもなくリリースの予定です。
ノーム
場合に応じて omitNorms を使ってください。ノーム(norm)は、索引時のブースト、およびフィールド長の正規化に使います。これにより、索引時にブースト値をフィールドに追加し、比較的短い文書のスコアを高めることができます。omitTf の場合と同様に、フィールドが短い場合、または全文ではない場合は役に立たないかもしれません。ノームは、索引の中で文書別、フィールド別にバイト値として保存されています。ノームが IndexReader にアップロードされる際は、各フィールドのbyte[maxdoc] 配列にロードされます。このため、たとえば4億ある文書のうち1つにだけフィールドがある場合でも、そのフィールドに byte[maxdoc] がロードされるので、多量の RAM を費やしてしまう可能性があります。特に索引内のフィールド数が多いときは、一定のフィールドに関してノームをオフにすることを考慮してください。ノームをオフにする対象となるフィールドとしては、非常に短いフィールド(全文とは言えないフィールド、ID、名前、キーワードなど)が考えられます。規模の大きい索引に関しては、判断が難しい場合がありますが、重要な全文フィールドのノームをオフにしなければならない場合もあります。どのくらいの RAM が関わっているかの一例を示すと、1千万の文書に対する索引内にある1個のフィールドは、10 MB 弱の RAM を必要とします。このようなフィールドが100個あった場合は、1 GB に近い RAM が必要となります。Lucene では、文書にフィールドを追加する際にノームを除外できます。Solrでは、schema.xml ファイルで正しいフィールド定義を使うことによってノームを除外できます。
フィールドのレイジー・ロード
Lucene と Solr 索引から文書をロードする際(たとえばヒットしたものを強調表示する場合)、ロードされる文書に保存されているフィールドはすべて一括してロードされます。文書に大規模のフィールドを含めて多数のフィールドがあり、そのフィールドの全部を常に使っているわけではない場合は、LuceneではFieldSelector を使って、または Solr ではフィールドのレイジー(lazy)・ロードを使用して(背後で FieldSelector を使っています)、速度を上げることができます。これにより、Lucene が文書をロードする際に不要なフィールドを避けることができます。避けるフィールドが少ない場合はあまり節約になりませんが、条件さえそろえば、保存フィールドのロードのパフォーマンスが飛躍的に向上します。たとえば、さまざまな小規模フィールドを保存してヒットリストに表示させようとしているときに、少数の大規模フィールドのために元々のコンテンツの表示が滞ってしまっている場合を考えてください。ヒットリストのフィールドをロードするときは、大規模なコンテンツ・フィールドを避けることにより、かなりの時間を節約できます。
停止語
1台のコンピューターの処理能力の上限が近づいてくると、クエリーによっては異常に頻出する項(stop word=停止語と言います)が大きな問題になります。トップレベルの BooleanQuery の一部で、全文書に存在する語に対する SHOULD 節は、索引内の全文書に一致し、スコアを付与します。1 台のコンピューターの処理能力の上限に近い索引においてでも、標準クエリーは1秒以下ですむかもしれませんが、索引にある文書のほとんどに一致するクエリーは、パフォーマンスに大きな悪影響を与えると言えます。 私の経験から言うと、非常に大規模の索引(1000万文書以上の)の場合、反応時間が1秒以下から10秒以上になるという劇的な変化もあり得ます。あえて索引作成時の停止語の削除を行なうことにした場合(通常は推奨しません)1 台のコンピューターの処理能力の上限ぎりぎりで使う場合、停止語リストに含める語を慎重に考慮してください。停止語を削除しない場合は(停止語は少なくとも句検索に役立つと思う読者が大半でしょう)、クエリー時に停止語を除外するオプションを設定することを考慮してください。停止語がトップレベルの BooleanQuery 内のトップレベルの OR 節である場合だけ除外するのが最善策とも考えられます。Lucene の contrib ディレクトリーに、 クエリー時に停止語を自動的に除外するアナライザー(org.apache.lucene.analysis.query.QueryAutoStopWordAnalyzer)があります。読者の状況に合うかどうかをテストしてみてください。Lucene は、パフォーマンスに関して思わぬ動作を起こすことがあります。停止語を使用して非常に大規模なシステムを構築する場合は、このアナライザーより効率的な索引付けスキーム(例えば、停止語をバイグラム、バイワード(2語組)として索引付けし、希少度が高い項を作成するなど)を使うと、句検索のパフォーマンスを向上させることができます。その他の選択肢としては、それぞれのコンピューターが規模の小さい索引を収納する分散検索、および大容量を処理できるサーバーを新規購入する方法の2つがあります。
索引の最適化
Luceneの索引は1〜n個のセグメントで構成されています。1つのセグメントがLuceneの1つの索引自体に対応するとも言えますが、両者には多少の違いがあります。1つのセグメントは、自己完結型の転置索引と言えます。Lucene/Solr の索引はセグメントに別れています。このため、更新の際は既存セグメントを変更する代わりに新規セグメントを追加するだけなので、更新が効率的です。その後、既存と新規のセグメントを結合してアクセスをさらに効率的にすることができます。検索対象範囲にあるセグメント数が増えれば増えるほど、必然的に検索にかかる時間が増えます。複合ファイル形式を使っていない場合は、セグメント数を少なくすると、開いているファイルの数も少なくすることができます。こうすることにより、索引の規模が大きくなっても「開いているファイルの数が多すぎます」 という例外メッセージが表示されなくなります。この例外メッセージが表示された場合は、使用しているオペレーティングシステムの開いているファイル数の上限を引き上げる必要がでてきます。または、後述のテクニックによってセグメント数を制限することもできます。もちろん、複合ファイル形式を使ってこれが Lucene と Solr のデフォルトです)セグメントを1つのファイルに書き出し、索引内のファイル数を大幅に減らすこともできます。
Lucene の索引を最適化(optimize)すると、索引のそれぞれのセグメントが1つの大きなセグメントとして結合されます。こうすることにより、検索が効率化されます。検索対象となるファイル数が減るだけでなく、セグメントが1つであれば、複数のセグメントを1つの索引として処理するために必要なLuceneの多くの細かな処理を省くことができます。FieldCache を使っている場合(たとえばソートに使う場合)は、この細かな処理はIndexReader の FieldCache のロードに極端な負担をかけます。この問題は近々のリリースによって解決されると思われますが、それまでは、FieldCache のロードの際、索引を最適化することが 今のところは非常に有益です。LUCENE-1483 の説明を参照してください。
ucene と Solr の両方の最適化は、入出力の頻繁な作業です。索引が大きい場合は、最適化を完了するのに時間がかかることがあります。時間問題の対処法として、部分的に最適化することが考えられます。最適化を部分的に実行すると、検出するセグメントの数を Lucene/Solr で指定することができます。これにより、段階的に検索速度を改善でき、セグメント1つ1つを完全に最適化しなくても済みます。
索引にセグメントを追加する際、IndexWriter のマージ・ファクターを小さい数値にしておくことも、 セグメントの数を増やさないためのもう1つの対策です。マージ・ファクターは、索引に含める必要のあるセグメントの数をコントロールします。この数値を10未満にすれば、検索がスムーズになり、速度が上がります。その代わり、索引にセグメントを追加する作業に少々時間がかかるようになります。これは、セグメントの数を小さく維持するために、マージの回数が多くなるからです。たとえば、マージ・ファクターが2(最小許可値)の場合、追加するセグメントは2個が最多です。
メモリーの管理
規模の大きい索引は、大量の RAM を必要とします。Lucene/Solr には、かなりの資源を必要とするような仕組みがあります。読者は、このような仕組みについて精通しておくことをお勧めします。どれだけの量の RAM が必要か、またその RAM をどのように割り当てたらよいのかを正しく把握しておかないと、パフォーマンスに関する問題にいろいろ悩まされることになります。
Lucene のキャッシュ
規模の検索アプリケーションでは、キャッシュが非常に重要になります。ディスクによる入出力をできるだけ避けるとパフォーマンスを改善できるというのが、一般的な見解です。工場出荷時のLuceneはほとんどキャッシングに対応していないため、キャッシング層を読者自身で構築することをお勧めします。それがまさに Solr が開発された理由です。
フィールド・キャッシュ
Lucene では、ディスクを使う代わりに FieldCache を使って、メモリーにあるフィールドの全部の値に効率的にアクセスできます。この作業はソートのために必要であるほか、Solr のファセット処理など様々な用途に使うことができます。索引が大規模の場合は、特に多くのフィールドに対してRAMをロードした場合(多くのフィールドを基準にソートした場合など)、FieldCache はかなりの量の RAM を必要とすることがあります。FieldCache に関してどうしてメモリー不足のエラーが発生するのかを理解することが、 Lucene/Solr の多くのユーザーにとって、今まで共通の問題でした。
FieldCache は、メモリー内の索引にある各文書の値(恐らく序数)をキャッシュします。これにより、文書内のフィールドの値を迅速に比較することができます。序数は単に順序を示すもので、文字列などの代わりとして使うことができます。たとえば、「山田、鈴木 、佐藤」とする代わりに「3、2、1」とすれば、ソートが速くなる上、正しい順序を維持することもできます。int、longなどの値は、その値自体が序数として機能できます。
FieldCache の中身のほとんどは配列です。そのサイズは、索引にある文書(削除されたがマージされていない文書も含めて)の数によって決まります。たとえば、1千万の文書を含む索引のlongフィールドをソートする場合は、1千万の long が long[] 配列にロードされます。これに必要なRAMは約76.29 MB になります。この値に FieldCache を使った long フィールドの数をかけると、long の FiledCache メモリーの合計使用値が得られます。この方法を他のフィールド型にも適用して計算すると、総合計の概算ができます。もう1つの例を挙げると、1億個の文書の索引にある int[] 配列は、380 MB 以上を必要とします。
文字列型は、ほかの型より少々複雑です。ローケールを使用しない文字列(String フィールドに対するソートは行なっているが、ソート用のLocale を与えてない場合)に対する FieldCache がある場合は、索引にあるすべての非重複項(String[])がロードされ、その後、整数を含んだもう1つの配列が、索引内の各文書ごとにロードされます。この2つ目の配列には序数が含まれており、非重複項の配列への索引となります。この方法によって値にアクセスすることは効率的とは言えません(2つの配列が逆参照するため)が、単一のIndexReaderの場合は、整数の序数配列を使って比較できるため、文字列の代わりに整数を使ってソートできます。文字列の FieldCache にロケールを使った場合、String[] 配列は、ほかの基本型と同様、索引内のそのフィールドの各文書にある項で満たされます。ローケールを使った場合、序数による比較はできません。String[] を使うと、索引が検索時に配列に保存されますが、ソート時に整数の序数ではなく文字列の比較を行なう必要があるため、依然として速度は向上しません。
上図は、比較にロケールを使用しない文字列の FieldCache が、多量のメイン RAM が消費されている状態を示しています。最初の配列は、FieldCache フィールドの索引に含まれているすべての非重複項から構成されています。2 番目の配列は、索引内の各文書に対する、1番目の配列の索引です。この図からわかるように、2番目の配列にある整数を比較するだけで文書をソートすることができます。しかし、MultiSearcher を使った場合、別々の索引に属する序数は有意に比較できないため、このようなソートができません。
フィルター
Lucene には CashingWrapperFilter が搭載されています。IndexReader の寿命と連携しているこのフィルターにより、Lucene のフィールターがキャッシュされます。このフィルターの初回使用時は、どの文書がフィルターと一致するかが計算されるため、少々速度が下がります。計算結果は WeakHashMap にキャッシュされます。しかし、その後のフィルター処理は、この計算作業を必要としないため、キャッシュされたフィルターと直接連動して、かなり速度が向上します。CashingWrapperFilter と QueryWrapperFilter を一緒に使うと、対象範囲の文書をかなり効率的かつ容易に摘出することができます。
文書
また、Lucene の文書をキャッシュすることもお勧めします。Lucene の文書クラスの機能により、保存されたフィールドにアクセスすることができます。ディスクからフィールドを読み出すと、効率がかなり低下することがあります。トラフィックが多いサイトでヒットリストの表示のための保存フィールドを設定すると、すぐディスクへのトラフィックに渋滞が起こりかねません。従来は、Lucene におけるヒットのクラスにより、文書を部分的にキャッシュできていましたが、TopDoc API への移行により、このクラスは廃止予定になっています。このため、開発者自身が文書のキャッシュ・システムを作成するのが一般的になっています。
Lucene アプリケーションに関しては、いつものように Solr をベストプラクティスとして参照すると便利です。
Solr のキャッシュ
Solr には、(開発者の)独自に作成したキャッシュのほか、3つの型のキャッシュが搭載されています。キャッシングが必要な場合(通常は必要です)は、キャッシュを正しく設定することがパフォーマンスにとって非常に重要です。キャッシュすることにあまり利点がない場合(同じクエリーを2回発行することはめったにないという場合など)は、キャッシング機能をオフにするとパフォーマンスを改善できます。
次の各キャッシュを慎重に考慮してください。
- FilterCache:順序付けのない文書 ID。フィルタークエリーのキャッシング用。このキャッシュは、あるクエリーに対して、条件に一致する文書を索引全体からフィルター機能により抽出するために十分な情報を保存します。フィルタ処理されたこの ID の集合に対する共通集合を取ることにより、フィルタークエリーを効率的に組み合わせることができます。ただし、返された文書の順序はキャッシュされないため、関連性またはソートフィールドに依存するクエリーのキャッシングには適していません。FieldCache 法によってファセット処理を実施している場合(非重複フィールドの数が多い場合はこれを実施すべきです)は、ファセット処理(FieldCache 法を使った場合)に使っているすべてのフィールドにおいて、少なくとも非重複値の個数に設定してください。
- QueryCache:順序の付いた文書ID。通常クエリーの結果のキャッシング用。QueryCache がキャッシュするのは返された文書だけであるのに対し、FilterCache は索引全体の結果をキャッシュする必要があるため、QueryCache が必要とする RAM の量は FilterCache よりはるかに少ないと言えます。このキャッシュの最適サイズは様々な要因によって異なりますが、基本的には、非常に一般的なクエリーの大部分の結果をキャッシュするのに十分なサイズが最適と言えます。
- DocumentCache:蓄積(stored)フィールドを保存します。Solr では、文書をメモリーに保存することにより、リクエストがディスク上の保存フィールドにアクセスしなくても済むようになっています。保存フィールドがもっとも頻繁に使われるのはヒットリストの表示であるため、文書がメモリーに蓄積されていると非常に貴重になることがあります。Solr Wiki では、リクエストの処理中に文書を再度フェッチする無駄を省くため、DocumentCache のサイズを少なくとも <結果の最大数> × <最大並行クエリー数> に設定することを推奨しています。
キャッシュの設定について注意するべきことに、自動ウォーム(auto wam)の値があります。自動ウォームの値は、(索引の変更のため)索引の新規ビューが開いたときに、古いキャッシュから新しいキャッシュにいくつのエントリーを移動させるかを決めます。文書のキャッシュは自動ウォームすることができませんが、その他のキャッシュの自動ウォームの値は、キャッシュをウォームするのに時間がかかりすぎない範囲内で、書き込みの速度を上げるのに十分な値を設定してください。新しいビューはウォーム処理が完了するまで表示されないため、ウォームが許容範囲の時間内で処理されていることを確認するテストを実行してください。自動ウォームの値は、高すぎると、新しいサーチャーを使うためにウォームする時間がかかりすぎます。キャッシュのかなりの部分が新しいサーチャーに移行できるが新しいサーチャーを使うのに時間がかかりすぎないようなバランスの取れた値を設定することをお勧めします。
また、Solr の管理ウェブページ上で、キャッシュの統計を調べることもお勧めします。ヒット率が非常に低い場合は、キャッシングをしない方がいいと言えます。また排除率(eviction rate)が非常に高い場合は、キャッシュが小さすぎることが考えられます。この場合も、キャッシングはしない方がいいと言えます。排除がかなりの頻度で発生している場合は、キャッシングの結果が使用されずに廃棄されているか、または数回使用されただけで廃棄されている可能性が十分あります。SolrWiki の SolrCaching の記事 を参照してキャッシュを適切に設定し、最高のパフォーマンスを実現してください。
1つのフィールド内にいくつの非重複値があるかを知る必要性は、以前からありました。Solr で提供されている LukeRequestHandler は、こういった情報の取得に非常に役に立つツールです。solr/admin/luke、つまり solr/admin/luke?wt=xslt&tr=luke.xsl にアクセスするだけで、データに関するあらゆる有益な統計値が表示されます。内容を吟味し、LukeRequestHandler を使って検討して、実行した内容に変化を加えた後で、また最初からやり直すなど、自由に遊んでみてください。索引の規模が大きい場合は、numTerms=0 を追加して、solr/admin/luke?numTerms=0 とすれば、一部の情報は犠牲になりますが、何十分もかかる呼び出しを何秒という時間に短縮できます。この場合、項のデータの詳細が失われるだけです。
Solr のファセット処理
ファセット処理に関する Solr の実装能力は非常に優れており、効率的ですが、ファセット処理のメモリーへの影響を考慮することが非常に重要です。Solrの ファセット処理には FacetQueries、および FacetFields という2つの主要モードがあります。
- FacetQueries:クエリーの結果をフィルターとしてキャッシュします。この FacetQery の文書集合は、結果集合との共通集合を取られ、クエリーの条件を満たす文書の数(ファセットカウント)が計算されます。フィルター内の結果の数が少数であれば、そのフィルターは文書 ID のハッシュセットとして維持されます。「hashDocSet」の設定値より多くの結果がある場合は、代わりにビットセットが使用されます。
- FacetFields:フィールドに含まれる固有値(訳注:非重複値と同意。異なり値。)の個数に基づいて、ファセットカウントを取得します。FacetFields には2 種類の方法があります。1つは固有値数が少ない場合にパフォーマンスが向上する方法、もう1つはフィールドに含まれる固有値が多い(一般的には数千以上の場合。ご使用の使用環境で最適状況をテストしてください)場合にうまく行く方法です。
最初の方法は facet.method=enum です。フィールド内の非重複値全部に FaceQuery を発行します。前述のとおり、フィールド内の固有値の数が少ない場合に非常に有効な方法です。ただし、この方法は膨大な量のメモリーを必要とするため、固有値の個数が増えると破綻します。この方法を使う場合は、FilterCache が、少なくともファセット処理の対象となる固有値それぞれに対して1つのフィルターを含むのに十分な大きさであることを確認してください。
2つ目の方法では、LuceneのFieldCache を使います(Solrの将来のバージョンでは、別の非転置構造である UnInvertedField を使います)。この方法は、非重複値の個数が少ないフィールドの場合、実際には処理速度が遅く、大量なメモリーを必要としますが、非重複値が多いフィールドの場合は、この方法が便利です。この方法では FieldCache を使って各文書の所定フィールドの値を検索します。所定値を持つ文書が1つ見つかるごとに、その値のファセット数を1つずつ増やします。
クエリー
どの種類のクエリーが一般的に速度が遅いかに注意し、その使い方を慎重に考慮することをお勧めします。これから説明することはあくまでも一般論ですが、設定を考慮する際は重要であることを覚えておいてください。基本的には、多項クエリーの類は、言うまでもなく、通常の項クエリーより速度が落ちます。特に FuzzyQuery は、スコア付けと一致の処理のために編集距離(edit distance)の計算を行なうため、非常に遅くなることがあります。当然ながら、一致する文書の数が少ないクエリーは比較的高速であること、および単項クエリーに近ければ近いほど高速になることを考慮すると便利です。また、BooleanQuery にはそれ独自のオーバーヘッドが少々あり、クエリーの節のコストも加わります。SpanQuery は、一致、スコア付けの両方で位置を考慮に入れるため、標準クエリーよりコストがかかります。句クエリーにも 同じことが言えますが、句クエリーは、通常、SpanQuery より高速です。最後に、AND クエリーはスキップリストを使えるため、OR クエリーよりかなり高速になるのが普通です。ユーザーにどの種類のクエリーー使用を許可するか、およびどの種類を扱うことになる可能性が大きいかを考慮してください。Lucene/Solr のデフォルトの実装では読者のニーズを十分満たせない場合(複雑なワイルドカードクエリーを主に処理しなくてはならない場合など)は、ほかのやり方があります。たとえば、別の permuterm 索引を作成すると、ワイルドカードの対応を効率化することができます。
ConstantScore クエリーは、関連性の計算を実際にしないで、各文書に対して予め決めた一定値をスコアとしてを返すクエリーです。索引の規模が大きい場合は、可変スコアを返すクエリーより大幅に速くなることがありますが、関連性を考慮しないという取引をしています。(フィルターと似ているように思われますが、その通りです。このクエリーは裏でフィルターが使われています)。Lucene 2.4 には ConstantScoreRangeQuery 、およびフィルターを引数として取るConstantScoreQueryが搭載されています。QueryFilter を使っている場合は、どのようなクエリーでもConstantScoreQuery にすることができます。実際、SolrにはConstantScorePrefixQuery、ConstantScoreWildcardQueryなどのConstantScoreクエリーも搭載されています。Solr 1.3以降では、クエリーパーサーの構築にConstantScoreファミリー全体がデフォルトとして使われています。
索引の規模が大きい場合、ConstantScoreクエリーは、WildcardQuery、FuzzyQuery、 RangeQuery などの多項クエリーの代用として有効です。標準多項クエリーは、索引内の一致した項をすべて列挙した後、その各項を節としてBooleanQueryを作成します。列挙された非重複項の一致数が多いと、このクエリーの速度がかなり落ちることがあります。ConstantScoreQueryの場合は、多項クエリー内の各項のスコアを計算する代わりに(計算してもあまり役に立たない可能性があります)、すべての一致項に一定のスコアが与えられ、BooleanQuery は作成されません。これにより、BooleanQuery に拡大するクエリーによく発生する maxclause(節数過多)の例外エラーを避けることができ、また規模の大きい索引においてかなり高速になります。Luceneの次のバージョンでは、すべての多項クエリー(ワイルドカード、ファジー、範囲、および前値)において、BooleanQuery に拡張する代わりに定数スコアを使うオプションが提供されます。
Lucene 2.4 では、TrieRangeQuery という新しい範囲クエリーが登場します TrieRangeQuery は、大規模の数値範囲の非常に効率的なクエリーを可能にします。次のバージョンにご期待ください。これはLuceneの数値範囲クエリーの対応機能として、大きな進歩です。また、Solr 1.4 が TrieRangeQuery に対応する予定もあります。
スループットの最大化
LuceneとSolr をマルチコアまたはマルチプロセッサのサーバーで使い始めると、ある種の問題が発生することがあります。このセクションでは、ハイエンドのハードウェアで Lucene/Solr を最大限に活用する場合によくある問題をいくつか説明します。
Lucene を使ったシステムを設計する場合は、1つの IndexSearcherとIndexReader を複数のスレッドで共有するのが一般的です。IndexSearcher は、基本的には IndexReader を薄く包んだものであるため、IndexReader と IndexSearcher のどちらを使っても原則的に同じです。Sun のJRE のバグ により、Windows の場合は問題が複雑化していますが、マルチコアまたはマルチプロセッサのシステムでは、複数の IndexSearcher/IndexReader のインスタンスを使ってパフォーマンスを向上させることができます。ただし、これを実行すると莫大なリソースが必要になります。特にフィールドでソートする場合、または IndexReader をキーとした FieldCache などのキャッシュを使っている場合は、リソースの消費が激しくなります。索引が大きい場合は、同時に使われている IndexReader の数をできるだけ少なくして、使用可能なリソースを増やすことをお勧めします。これをお勧めしないケースとしては、新しい IndexSearcher を使用前にウォームアップすることにより、新しいサーチャーに表示される最初の検索結果を他のどの検索結果にも劣らない速度にする場合です。この場合は、新しいサーチャーが使用可能になるまで古いサーチャーがリクエストを処理し続けるようにしてください。Solr の場合、この種の管理は何もしなくても裏で効率的に処理されます。
マルチコアまたはマルチプロセッサのコンピューターの場合、IndexReader の数をできるだけ少なくしようとすると、恐らく既知のボトルネックにぶつかります。こういったボトルネックを注意して回避できれば、サーバーのスループットが大幅に増えます。
ボトルネックを回避する1つの方法は、IndexReader を読み取り専用モードで開くことです。これにより、Lucene のマルチコアのパフォーマンスを最大化することができます。読み取り専用モードでは、主に削除(された文書)の検出のための同期をするためのボトルネックを排除でき、マルチコアおよびマルチスレッドの同時スループットが向上します。IndexSearcher を使うことに慣れている読者の場合は、まず IndexReader を作成し、それを使って IndexSearcher を作成することになります。Solr 1.3 以降のバージョンは、内部で読み取り専用の IndexReaderx を使用しており、何も特別なことをする必要なく、パフォーマンスが最大限になるように設計されています。
Lucene/Solr の同期関連の問題を解決するもう1 つの方法は、Windows 以外のオペレーティングシステムを使うことです。Lucene の場合、Windows 以外のオペレーティングシステム上ではFSDirectory の代わりに NIOFSDirectory を使ってマルチスレッドのパフォーマンスを向上させることができます。前述のとおり、Sun の Windows JVM のバグのあるため、Windows のユーザーはこの最適化の手段を使うことができませんが、JRE の将来の更新によって解決される可能性があります。Solr 1.3 ではこの機能をまだ活用していませんが、Solr 1.4 以降のバージョンでは、使用中のオペレーティングシステムが自動検出され、正しい実装を使ってパフォーマンスを最大限にできるようになります。
最後に、Solr 1.4を入手したら、solr.LRUCache 実装の代わりに FastLRUCache によるキャッシュの実装を使用することをお勧めします。標準の LRUCache では、内部で使用する Map で、同期型の get メソッドが使われています。このため、コア、プロセッサ、またはスレッドの数が多くなると、同期によるボトルネックが発生する場合があります。FastLRUCache では、時々クリーンアップを行なうコストが発生しますが、内部で使用する Map で非同期の get メソッドが提供されます。FastLRUCaheは、ヒット率が高いキャッシュに向いている(put メソッドは高コスト、get は put より低コスト)とされているため、ヒット率が低いキャッシュには solr.LRUCache の使用を考慮することをお勧めします。
JVM の設定
JVM の正しい設定に関する説明は複雑になりかねないので、JVM の設定を主眼とした記事に譲ります。また、最近のJVMは、ハードウェアの検出に基づいてデフォルト設定を選択する能力が非常に優れています。以下の節にヒントをいくつかご紹介します。
Xms Xmxの選択
最小メモリーを非常に低い値に設定し、最大メモリーを高い値に設定するのが1 つの対策です。まず、Lucene/Solr アプリケーションを実行して、JVM のメモリーの使用状況を監視してください。次に、一般的な使用量と思う値を最低値として設定します。最高値としては、オペレーションシステム、他のアプリケーション、およびファイルシステムのキャッシュ(これがもっとも重要)に十分なRAMを残した上で、できるだけ高い値を最高値として設定します。残しておくRAMの量は、使用中のオペレーティングシステム、実行中のほかのプログラム、索引の規模など、種々の要因によって異なります。オペレーティングシステムは、余ったRAMを使ってファイルシステムにアクセスし、キャッシュします。規模の大きい索引は、そのキャッシュに十分な RAM を確保してパフォーマンスを最適化する必要があります。概して、索引の規模が大きい場合は、JVM に与えるRAMに加えて、少なくとも数ギガバイトのRAMを確保するのが得策です。
サーバーの HotSpot VM の使用
必ず -server で起動する HotSpot VM を使用してください。長期間稼働しているサーバーアプリケーションのスループットを最大化するには、この方法がベストです。使用する HotSpot VM がクライアントであるかサーバーであるかを調べるには、コマンドラインに「java -version」と入力し、client または server という表現を探してください。システム上に Java JRE が多数ある場合は、正しいものをチェックするよう注意してください。JRE の場合は、HotSpot VM のサーバーは配布されていないかもしれませんが、JDK は普通、サーバー版も配布されます。
設定の確認
使用中のサーバーがどのような JVM の設定を使っているかを確認するには、管理画面のリクエストハンドラー(solr/admin/system)を使うと非常に便利です。このリクエストハンドラーを使うと、サーバーの様々な統計情報や設定情報を取得できます。
サーバーの他の機能
Solr と Lucene の索引が索引作成アプリケーション(Windows の索引作成サービス、デスクトップ検索アプリケーションなど)から除外されていることを確認してください。索引作成アプリケーションが Solr/Lucene の索引ファイルを適用ファイルとして取得し、解析することはまずないと思えますが、除外しておいた方が安全です。また、使用中コンピューター内の索引ファイルの変更中に外部アプリケーションが索引ファイルを検査しないように気をつけてください。特に大きな索引を作成しているときは気をつけてください。さらに、索引はバックアップ用のアプリケーションからも除外してください。バックアップ・ファイルは、Solr/Lucene と連携してバックアップしない限り、一貫性に欠けて、パフォーマンスに悪影響が出ます。書籍 Lucene In Action 2 中の Lucene を使ったホット・バックアップに関する章が、 無料で公開されています。Solrのレプリケーション機能 を設定するだけで、Solr の索引のバックアップを簡単に作成できます。
サーバー上で稼働する必要のあるほかのプログラムも考慮し、Java JVM に割り当てた RAM のほかに、ほかのプログラム用の RAM が十分あることを確認してください。また、オペレーティングシステムが機能するための RAM、およびオペレーティングシステムのファイルシステムのキャッシュに使う RAM が十分あることも確認してください。さらに、メモリーにある Lucene の主要索引ファイルをキャッシュするために十分な RAM も必要です。非常に大きな索引の場合は、少なくとも数ギガバイトのRAMがあることが理想的です。RAM の正確な必要量は、多くの要因によって異なります。索引の物理的サイズを確認し、そのうち RAM でキャッシュするためのできるだけ大量で無理なく入手できる量が必要であると見なしてください。
多量のクエリー
多くのアプリケーションは、ある規模の索引を1台のコンピューターで容易に処理できても、クエリーの量が多すぎて処理しきれないという点に達します。このようなケースを正しく解決するには、レプリケーション機能によって索引をほかのサーバーに複製した後、その索引の複製を持っているすべてのサーバーに対してリクエストを分散します。この複製はその後、索引の「マスター」版が変更になるたびに更新されます。
Lucene のレプリケーション機能

rsync によりセグメントを複製
新しい文書のかたまりを Lucene/Solr にロードすると、必ず新しい索引のセグメントが作成されます。(マージのことは、ここではしばらく忘れることにします。)索引を別のサーバーにレプリケートする際は、通常、この新しい小さなセグメントをコピーするだけで済みます。
Lucene の レプリケーションは、主に読者自身の手にゆだねられます。「ベストプラクティス」の技法は、Lucene の索引ファイルの性質を利用することです。Lucene の索引は1〜n個の個別のセグメントから成り立っています。ライトワンス(write once=一度書き込まれたら変更されない)という原理が使われているため、索引の更新の際、各セグメントのファイルは変更されません。その代わりに新しいファイルが作成され、索引が変更されていない古いファイルと作成された新しいファイルを自動的に指すようになります。rsync などを使って索引の変更のレプリケーションを効率化するには、新しいファイルをコピーするだけでよく、非常に簡単であるため、この設定は索引のレプリケーションに適しています。たとえば、すでに何百万という数の文書を含むいくつかの文書を索引に追加すると、その追加された文書を含む新しいセグメントが書き込まれます。通常、ほかのコンピューターにレプリケートする必要のあるのはこのセグメントだけです。セグメントのマージの際は、コピーする必要のあるセグメントが影響を受けますが、通常は変更のない大規模の文書があるため、小規模な変更部分だけのコピー作業だけですみ、効率的になります。
典型的な構成では、文書を追加して更新するためのマスターを設定した後で、マスター索引(実際は索引の中で変更のあったファイルのみ)をレプリケートするスレーブサーバーをn台設定します。
レプリケーションに必要な時間と帯域幅がそれほど心配ではなく、それよりクエリーのスループット量を多くすることが重要である場合は、変更のあったセグメントを移すという利点は使わず、完全に最適化(optimize)された索引だけをレプリケートすることをお勧めします。この方法ではリソースの消費が少々増えますが、マスターが最適化のコストを負担し(最適化を実行したときに起こるコンピューターが低速化しても、読者は気づきません)、スレーブは完全に最適化された索引を常に取得し、それに対してクエリーを発行するため、クエリーのパフォーマンスを最大化できます。一般的には、現在、レプリケーションのための帯域幅は、それほど懸念することではありませんが、大規模の索引を最適化するには相当量の時間がかかるため、この方法をすべての状況に適用できるわけではありません。

3台のスレーブへのレプリケーション
上図は、1台のマスターと3台のスレーブを示しています。マスターは索引のすべての更新内容を受け取り、この更新内容を主要ポイントで各スレーブにレプリケートします。レプリケートのタイミングは、通常、最適化、または索引更新のバッチの後が適切です。
Solr のレプリケーション機能
Lucene のレプリケーション機能の例として一番いいのは、実は、Solr です。Solr はハードリンクを利用した unix/rsync/script ソリューションによって索引のスナップショットを効率的に取得する上、Java だけによる新しいソリューションによって Lucene のプラグイン IndexDeletionPolicy を利用して索引のスナップショットを維持します。スクリプトによるレプリケーションはかなり剛健で、長年安定しています。Java だけの新しいレプリケーション機能は、Solr 1.4 で登場します。この機能はまだ剛健化フェーズの早期にありますが(なにせまだ新機能なので)、多くの読者が期待している機能であることは確かです。
Solr のモデルには、すべての更新を処理する1台のマスターサーバーと、すべてのクエリーを処理する1〜n台のスレーブサーバーがあります。マスターは時々、索引の「スナップショット」を取得して、ある時点での索引のビューを、文字どおり凍結します。次にスレーブがマスターをポーリングし、ダウンロードすべき新しいスナップショットがあるかどうかを調べます。ダウンロードの必要がある場合は、変更のあったファイルはマスターからスレーブに複製され、更新された索引の新しいビューが開きます。(この時、キャッシュの自動ウォームアップなど、1台のコンピュータによる索引ビューの更新の際に通常実行されることが実行されます。)設定の際は、1回のレプリケーションが完了するまでの時間を十分取り、次のレプリケーションの開始と重ならないよう、気をつけてください。ハードウェアと索引によりますが、通常は設定時間は1分以上にする必要があります。索引の規模が非常に大きい場合、または帯域幅が小さい場合は、レプリケーションの所要時間がそれ以上になることがあります。
このモデルを使うと、Solr は容易に水平拡張することができます。所定量の負荷を処理するのに必要な数のスレーブを追加した後、ロードバランサー(負荷分散装置)を設定して、単一の仮想 IP アドレスが、リクエストごとに、各スレーブの IP アドレスに変換されるようにします。
Solr のレプリケーション機能に関する詳細説明は Solr Wiki の Unix スクリプトによるレプリケーションの記事、およびピュア Java によるレプリケーションの記事に記載されています。
スクリプトによるレプリケーションを実行する場合は、Java JVM からスクリプトがいくつか起動されることに注意してください。問題がなければ懸念する必要はありませんが、JVM によって新しいプロセスが起動される際は、オペレーティングシステムによって異なる方法が使われます。Unix システムでは、この方法は、通常、fork 呼び出しです。fork 呼び出しでは、通常、そのときのプロセスが使用しているメモリーと同じ量のメモリーが割り当てられます。ただ、次に exec によってスクリプトが実行されるため、このメモリーは使用されません。しかし、オペレーティングシステムは要求された RAM が使用されるものとして用意されます。たとえば、JVM で 5 GB のRAM が使われているとすると、簡単で小さいスクリプトを実行する場合でも、オペレーティングシステムにより、5 GB が追加要求されます。前述のとおり、このメモリーは必要でなく、使われません。しかし、所定量の RAM がなく、オペレーティングシステムによってデフォルトとしてこの問題が解決されないと、メモリー不足の例外エラーが発生することがあります。この問題は、以前からあった問題です。回避策としてメモリーのオーバーコミット(overcommit)とコピーオンライト(copy-on-write)の組み合わせ、と呼ばれるものがあります。このモードでは、RAM の割り当て要求は、無理であっても許可されます。メモリー不足の問題は、後で RAM を実際に使い過ぎる時に発生します。これがコピーオンライトの部分です。fork されたプロセスのメモリーは、それがコピーされる際に親プロセスが修正しようとするときまで、親プロセスと共有されます。この問題が発生した場合は、使用中のオペレーティングシステムが overcommit memory(メモリーのオーバーコミット)に設定されているかどうかを確認してください。例として、Linux では、ある条件下ではメモリーのオーバーコミットを許可するが、非常に大きな要求の場合は許可しない設定になっています(5 GB のオーバーコミットは多分しないと思われます)。ある発見的方法によって、オーバーコミットを許可するべきかどうかをを判断します。 Solr でスクリプトが起動するときにメモリー不足の問題が発生する場合は、オペレーティングシステムを常にオーバーコミットするように設定を変更することを考慮してください。
大規模な索引
索引があまりに大きくなったため、1台のコンピューターではさばききれなくなる場合があります。文書が何千万という数にのぼると、 この問題に直面しかねません。一般的な解決方法としては、索引を分割して、複数のサーバーに分散させます。この後、1つの検索リクエストを(通常は並列に)各サーバーに発行し、その結果を検出して、読者用に1つの結果としてまとめます。Lucene には、分散検索に役立つクラスがいくつか用意されています。また、Solr には簡易で、しかも何百億という文書に対応可能な、本格的な仕組みが用意されています。

シャードによる分散
分散構成では、「シャード」と呼ばれる1台のサーバーがクエリーのリクエストを受け取り、それ自体、および構成内のほかのシャードで検索を実行して、各シャードからの結果をまとめて返します。
分散 Lucene
Lucene の分散に関する対応は広範ではありませんが、役に立つ道具が用意されています。 Lucene には RemoteSearchable の実装がありあります。これにより、MultiSearcher、または通常は ParallelMultiSearcher を使った分散検索が可能になります。MultiSearcher を使っていくつかのローカル(同一マシン内)の Searchables を検索する代わりに、MultiSearcher を使って複数の RemoteSearchables を検索することができます。ローカルの MultiSearcher の検索と同様、各子 Searchable も検索の対象になり、結果はまとめて返されます。このスケールの方法は、今まで多くの分散構成に使われてきましたが、理想的な解とは言えません。サーバー間に多量のデータのやり取りが生じるという問題があり、これによって真の大規模の拡張が妨げられています。しかし、多くの読者にとって、これは簡単、しかも十分な解です。前述のとおり、Lucene プロジェクトでは、分散検索は今まで中心的な存在ではなかったため、読者自身がコードを作成することになる可能性は大です。実は、RemoteSearchable は、実際の問題解決のために開発する必要のある仕組みの一部でしかありません。通例のとおり、Lucene の分散のベストプラクティスに関しては、Solr に頼るのが最善策と言えるでしょう。ちなみに、アプローチはほかにもあること、および実際に使われているものもあることを覚えていてください。
分散 Solr
Solr は、規模の変更に柔軟に対応できる、非常に簡単で、そのままで使える分散ソリューションです。Lucene は、「はじめに」節で説明したとおり、情報検索技術の中核であり、Solr は、Lucene を基にして構築された検索サーバーです。(独自の強力な技術も備えています。具体的には、ファセット処理を参照。) Solr には、Lucene に基づいて構築された、驚くほど簡単な分散サポートが含まれています。
分散Solrサーバー・ファームは、Solr を各コンピューターにインストールするだけで構築できます。Solr は、分散構成に「シャード」として設定された各サーバーを指し、サーバーファームは1〜n 個のシャードで構成されます。
全文書をサーバーファームの各「シャード」でどのように索引付けするかは、読者次第です。Solr は、そのままの状態では分散索引の生成に対応していませんが、各文書を各サーバーに順序索引付けしていく簡単な方法があります。また、簡単なハッシュ法を使うこともできます。この方法を使う場合、Solr Wiki では、 ID.hashCode() % サーバー数 が適切なハッシュ関数として推奨されています。
Solr では、サーバー・ファーム全体の項頻度や文書頻度(document frequency)は計算されないことに注意してください。大規模レベルでは、これらの頻度がシャードレベルで計算されないことが問題になるとは思われませんが、コレクションのサーバーへの分散が極端に偏っている場合は、関連性の結果が信頼性を欠く可能性があります。文書はランダムにシャードに分散するのが、多分最善策でしょう。
文書を各シャードに索引付けした後は、複数のシャードにわたる検索作業は非常に簡単です。
http://localhost:8983/solr/select?shards=localhost:8983/solr,localhost:7…
各シャードの URL を含んだシャードのパラメーターを、コンマで区切って追加するだけです。これにより、select の RequestHandler によって、リストされた各 URL が別々に検索され、その後、あたかも1つの大規模索引に対して1つの検索リクエストを発行したように、その結果がまとめられます。リクエストは、各サーバーの負荷が均衡するように分散してください。ただし、一般的には、URL を使用してシャードを指定することは避けるのが得策です。シャードを多数設定した場合、または Solr の GET リクエストに多数の URL を書きたくない場合は、solrcofig.xml 内で SearchHandler のシャードのパラメーターを設定する方が、はるかに簡単です。こう設定すれば、しばらくの間は URL のことは忘れていられます。
SearchHandler を拡張した RequestHandler ならどれでも SearchComponent を使って分散検索を実行できます。ただし、分散検索で使えるのは、分散化に対応した SearchComponent のみです。現在の時点で分散検索に対応している SearchComponent は、次のとおりです。
- クエリー・コンポーネント:クエリーに一致する文書を返します。
- ファセット・コンポーネント:ファセットが一致数によりソートされる(デフォルト)facet.query および facet.field のリクエスト。Solr の次のバージョン(1.4)では、名前によるソートにも対応します。
- 強調表示処理コンポーネント
- デバッグ・コンポーネント
最高の結果を得るには、やってくるリクエストをシャードに均等に振り分けてください。1台のシャードに来たリクエストは、そのシャードにより、そのシャード自体とほかのシャードに分散され、その結果が統合されます。リクエストは全部のシャードに均等に分散してください。ただし、これを実行する場合は、Solr Wiki に掲載されているデッドロックに関する警告に気をつけてください。HTTP コンテナー内のリクエストを処理するスレッドの数は、そのシャード自体と構成中にある他のシャード全部から取得できるリクエストの数を上回っている必要があります。そうでないと、デッドロックが発生する恐れがあります。
Solrの分散検索の設定に関する詳細は、Solr Wiki の記事を参照してください。
大規模の索引と大量のクエリー
索引の規模が大きすぎて1台のコンピューターでは処理しきれなくなり、クエリーの量が多くなって単一のシャードで処理できなくなったら、分散検索の設定で各シャードのレプリケート処理を開始する必要があります。この概念は Lucene 単体のシステムでも使えますが、Solr はすでにこの種の用途に向いているので、Solr について説明します。
ここで説明するのは、分散検索とレプリケーション機能を結合するという概念です。下の分散レプリケーションの図を見てください。各シャードに対して「マスター」サーバーがあり、マスターからレプリケートする1 〜n台のスレーブがあります。この構成により、マスターは、クエリー処理のパフォーマンスに悪影響を及ぼすことなく更新と最適化を処理することができます。クエリーリクエストは、シャード の各スレーブの負荷が均等になるよう振り分けてください。負荷均等により、クエリー処理能力が向上する上、サーバーがダウンした際にフェイルオーバーのバックアップが可能になります。

分散レプリケーション
分散レプリケーションでは、マスターシャードはお互いを知りません。それぞれのマスターを索引付けして、その索引を各スレーブにレプリケートし、その後実行する検索は、各マスター/スレーブシャードから1つのスレーブを使って、スレーブ全部に分散されます。
可用性を高くするために、ロードバランサーを使って、各シャードのスレーブの組に仮想IPを設定することができます。負荷の分散に関して詳しくない読者には、ロードバランサーのオープンソースソフトウェアとして HAProxy をお勧めします。スレーブサーバーがダウンした場合、優良なロードバランサーは、何らかのテクニック(通常は、ハートビート=鼓動検出)を用いて故障を検知し、すべてのリクエストを、故障したスレーブと同じグループで稼働中のほかのスレーブに転送します。リクエストは、設定した1つの仮想IPに対して行なわれ、各検索スレーブ・グループ内のサーバーに負荷が均等に分散されます。
この構成により、検索に関しては完全にバランスのとれた耐障害システムができあがります。(Solr は索引付けの耐障害性には未対応です。)外から来る検索リクエストは、機能中のスレーブの1つに渡され、そのスレーブは、受け取った検索リクエストを構成内の各シャードのスレーブに振り分けます。スレーブは各シャードに対し仮想 IP でリクエストを発行し、ロードバランサーが使用可能のスレーブを1台選択します。最後に、結果が1つの集合に統合されて返されます。ダウンしたスレーブはロードバランサーの対象から外され、残りのスレーブだけが使われます。シャードマスターがダウンした場合でも、マスターが復旧するまで、スレーブを通して検索は続行することができます。
まとめ
ほとんどの用途においては、Lucene を使ってスケール可能なソリューションを開発し始めると、独自のサーチエンジンを作成することになりがちですが、これは通常いいことではありません。Lucene はどちらかと言えばツールキットの役目を果たす一方、Solr はそのままで使用可能な検索ソリューション向けです。それではどうして Solr だけでなく Lucene のスケーラビリティーについても説明するのかということになりますが、それは(Lucene だけで書かれた)レガシーコードを受け継いだ場合や特定要件のために Solr を使えない場合、Lucene をスケール可能にする必要が出てくる可能性があるからです。しかし、一般的には、複数のコンピューターにわたってLucene をスケール可能にするには、かなりの量の作業が必要となります。Solr ではこのスケール可能にする作業、およびほかの多くの高レベルの作業がすでに済んでいるため、Solr の機能を利用するのが得策と言えます。ただし、Lucene の動作スケーラビリティーについて理解しておくことは Solr の内部動作とスケーラビリティーを理解することにつながるため、重要です。Lucene は、容易にスケールできるようにするソリューションを構築するための道具を提供し、Lucene のサブプロジェクトである Solr は、Lucene を使ってそういったソリューションを構築している、ということを覚えていてください。
私が1台のコンピューターのパフォーマンスを最大化することについて最初に説明した理由が、今までの説明でおわかりいただけたでしょうか。少々当然なことですが、最初から1台のサーバーで処理しきれない要件があった場合でも、1台のコンピューターのパフォーマンスを最大化する方法を知っていることは、非常に重要です。レプリケーション、または分散により、個々のサーバーに対して個々の検索が効果的に実行されます(その後、分散の場合は検索結果が結合されます)。したがって、分散検索とレプリケーション検索のパフォーマンスを最大化するための効果的な作業のほとんどは、1台のコンピューターのパフォーマンスを最大化する作業と同じになります。
Lucene と Solr がどちらも規模の変更に容易に対応できる検索ソリューションであることをおわかりいただけたことを願っています。大規模システムに関しては、読者独特の要件のために様々な試行錯誤やテストを行う必要はあるでしょうが、今までの説明で、少しは作業の方向性を得られたことを願っています。Lucene/Solr のパフォーマンスは、初期設定の状態でも驚異的なものであることをおわかりいただけると思います。さらに、Lucene/Solr は正しく調整、構成すれば、極度に大規模なデータの場合でも威力を発揮します。構成が正しければ、1秒未満の反応時間で何百万、何十億という数の文書の規模に容易に対応します。負荷が大きく、信頼性の要件が厳しい場合も、ニーズを容易に満たすことができます。
リンク集
Lucene Stack について学ぶ
SolrPerformanceFactors には、Solr のパフォーマンスを検討する際に考慮すべき一般的事項があります。
NIOFSDirectory は、非 Windows オペレーティングシステムにおけるマルチコア、マルチプロセッサのパフォーマンスを向上させる Lucene ディレクトリーの実装です。
FSDirectory は、Lucene ディレクトリーの標準実装です。
FieldCache は、Luceneにおける文書のフィールド値と序数のキャッシュです。
IndexDeletionPolicy は、Lucene において古い索引ビューの削除に関する独自のポリシーの使用を可能にするクラスです。
FieldSelector は、Lucene において特定フィールドだけをロードできるようにするクラスです。
RemoteSearchable は、Lucene において複数サーバーにまたがって索引の分散を可能にするクラスです。
Lucene のベンチマーク寄贈コンポーネントは、Lucene の変更や設定のベンチマークのための便利なフレームワークです。
Lucene:検索速度の改善には、Lucene の検索パフォーマンスを改善するための一般的なヒントがあります。
Solr Wiki:分散検索には、 Solr の優秀な分散検索機能に関する詳細情報があります。
Solr Wiki:コレクションの分散には、 Unix のスクリプトを使った Solr の索引のレプリケーションの説明があります。
Solr Wiki:Solr のレプリケーションには、 Solr の Java による索引のレプリケーションの説明があります。
Solr Wiki:Solrのキャッシングで Solr のキャッシュ対応に関する詳細を学びましょう。
Solr Wiki:LukeRequestHandlerで索引データの詳細を調べることができます。
SystemInfoHandlerは、Solr とサーバーに関する情報の迅速な取得を行ないます。
Solr Wiki: Solr のファセット処理には、Solr のファセット処理機能の概説があります。
Java のパフォーマンスには、Sun による Java のパフォーマンスに関するヒントがあります。
Lucene を使ったホットバックアップには、Lucene の索引を安全にバックアップする方法に関する情報があります。
Solr Wiki:分散検索コンポーネントの書き方は、Solr による分散 SearchComponent のプログラム方法を説明しています。
Sun の JVM のバグ:Windows 上で 1つの FileChannel を使った場合の方が、複数の FileChannel を使った場合に比べて速度が落ちる。
LUCENE-1483: IndexSearcher のマルチセグメントの検索を変更し、1つの HitCollector を使って個々のセグメントを検索するようにする。
SOLR-739:SolrのOmitTf() への対応追加
スキップリストは、スキップポインターを使用して、ポスティングリストの共通集合の計算を速くします。
Permuterm 索引は、効率的なワイルドカードのクエリーを実現する技法です。
BiWords は、効率的な句検索の技法です。
HAProxyは、オープンソースのロードバランサー・ソフトウェアです。


