リリースの投票がまもなく始まろうとしている。boosts?https://issues.apache.org/jira/browse/LUCENE-6819タイトルだけ見ると、「インデクシング時ブースト(重み付け)を止めよう」、もっと深読みすると「クエリ時に重み付けが指定できるからインデクシング時に重み付けしなくてもいんじゃね?」ととらえてしまう方もいるかもしれない。インデクシング時重み付けとクエリ時重み付けでは、大きく意味や目的が異なる。どのようになるか、簡単に解説しよう。LUCENE-6819このチケットでは、タイトル通り、インデクシング時のboostを止めよう、というものである。いう、各ドキュメントの各フィールド(ただし、omitNorms=trueのフィールドを除く)ごとに1バイトでエンコーディングされた値の、次の式からboostを取り除こう、というものである。標準であったベクトル空間モデルによるランキング計算(各ドキュメントのクエリに対する類似度計算)に強く依存している。考慮されている文書長はとっくに加味されている。[2]さて、これからboostを除けば、次のようになる。デフォルトで次のように計算される。sqrt(numTerms)numTermsは当該フィールドの単語数である。なってしまうことも考えられる。ほとんどのユーザーはインデクシング時重み付けなどしないので、上図グレーの部分がもったいない。boostがかけられる可能性を考えて1バイトの半分を残しているのだが、ほとんどのユーザーはboost=1なので、非常にもったいない、かつ、低精度な使い方になっている。し、boostに1でない値が投入されようとすると、「将来バージョンで使えなくなる」旨の警告が表示される。そのものが記録されるようになった。1バイトが有効活用されていない。そこでこのチケットでは、フィールド長(単語数)をSmallFloat.intoTobyte4(numTerms)でエンコードして保存するように修正された。単語数が1000のときは、SmallFloat.intoTobyte4(1000)が87となり、これが記録される。デコードにはSmallFloat.byte4ToInt()が使われる。SmallFloat.byte4ToInt(87)は984となる。ちなみに、1〜40はエンコードしても1〜40を出力する(変わらない)。長さ41から徐々にエンコードによる誤差が大きくなる(下図)。そのままエンコードされて1バイトに記録される。計算する意味自体がない、ということだ。指定は相変わらず有効である。ロンウイットの社内勉強会で説明に使用したスライドも公開しているので、合わせて読んで欲しい。ロンウイット主催のトレーニングコース「Solr基礎」では、このような大事な基礎を丁寧に教えています。お申し込みはこちらから。Sparkではじめる機械学習」では、機械学習のさまざまなモデルやアルゴリズムを丁寧に解説しています。ベーシックな機械学習の解説にとどまらず、本記事にあるような、検索エンジンのランキングの元になるスコア計算の考え方なども含み、ソフトウェア開発の実際の現場で役に立つ内容を網羅しています。お申し込みはこちらから。2.9.2から約2740億件に拡張されることになった:https://issues.apache.org/jira/browse/LUCENE-2257インデックスファイルフォーマットにはこのような制限はないが、検索時に部分的にintでシーク位置の計算結果を保持している部分があったためintの上限の約21億が上限となっていたものである。これをlongで受け取るように変更された。約2740億件、となっている。これまでこの制限に当たっていたサイトはセグメントをマージしないようにするくらいしか方法がなかったが、これでだいぶ運用が楽になるだろう。ThursdaySSDを効果的に使い検索を高速化する方法http://old.nabble.com/Distributing-index-over-N-disks-td26504452.htmlLucene/Solrのインデックスが完成されたもので変更がない(追加/更新/削除がなく、マージもされない)ならば、SSDを用意して.frqファイルと.prxファイルを配置し、残りのインデックスファイルはHDDに配置する。HDDのインデックスディレクトリにはSSDへのシンボリックリンクを配置する。.frqファイルには各TermのDocumentごとの出現数が記録されている。.prxファイルにはさらにその詳細としてその出現毎のフィールドにおけるTermの位置情報が記録されている。いずれもomitTfがfalseのときに.fdtファイルに次いでサイズが大きく、また検索実行時に頻繁にアクセスされるため、これらのファイルを選択的にSSDに配置することで検索コストパフォーマンスを最大化できると期待できる。3.0では削除され、インデックスのバージョン番号が上がったためである。3.0で検索に使うことができる。3.0でインデックスを更新すると、新しいセグメントファイルは新フォーマットで作成される。4.9まで使える予定である。4.0になるとサポートされなくなる。Tuesdayインデックスのサイズを見るシミュレーションプログラムLuceneのインデックスは検索しながら更新していると、古いインデックスを参照しているSearcherがいる間はセグメントを消すことができない。そのため、大きなマージが走るときはハードディスクの容量を一時的に大量に消費する。その様子(マージの部分のみ)をシミュレーションする(Luceneを一切使用しない)プログラムを作成した。プログラムを実行するとCSVファイルが出力される。それをEXCELで表示したのが下の図である:EXCELで表示できるよう、この図ではマージ係数を3に設定している。Wednesday並列インデックスを利用したDocumentの更新Luceneのインデックスに登録されている既存Documentを更新する場合、LuceneではDocumentをいったん削除し、再度新規にDocument全体を追加しなければならない。このため、サイズの小さなフィールドの追加や内容変更をする場合でも、変更の必要がないサイズの大きなフィールドにまでその影響がおよぼされてしまう。IDが割り振られているインデックスを指す。ひとつのDocumentをフィールド別に複数のインデックスに分けて登録する場合、そのDocumentが同じIDを割り振られるようにするには、インデックスごとに同じ順序でDocumentを追加していく必要がある。このような「並列インデックス」をオープンして検索に使用するために、ParallelReaderというIndexReaderがある。}}サンプルプログラムでは、インデックスに3つのフィールドを順次追加している。通常であれば既存のインデックスのDocumentの全体を削除して再登録しなければならないが、サンプルプログラムでは新しいフィールドを新しいインデックスに登録し、ParallelReaderを使ってopenしている。実際にこの方法を拡張してDocumentのフィールドを更新するにはいくつかの壁を乗り越える必要がある。IDを複数にまたがったインデックスで同じ値に保つのは非常に難しい。現在のLuceneでは、マージはDocument数ではなくサイズ基準で発生するのがデフォルトになっているが、これではインデックスごとにマージ実行のタイミングがずれてしまうので、flush/delete/mergeのタイミングは同一に保つ必要がある。そのためのアイディアとしては、複数のインデックスのそれぞれを担当するIndexWriterを、ひとつのマスターと複数のスレーブに分ける。スレーブのIndexWriterは、マスターのIndexWriterのタイミングに合わせてマージを行ったらどうか、という提案の内容になっている。Wednesdayoptimize()中は元のインデックスの2倍の空き容量が必要な件IndexReaderがオープンしているインデックスに対してoptimize()を実行するときは、ハードディスクの空き容量が元のインデックス容量の2倍必要である。10GBのインデックスをIndexReaderがオープンしていると、optimize()には20GBの容量が必要となる。ユーザからの指摘で、IndexReaderでオープン中でないインデックスも同じピーク容量が必要となるケースが報告された。それはautoCommit=falseのIndexWriterでオープンしているインデックスである。この指摘を受け、optimize()のJavadocが変更された。WednesdayIndexWriterのautoCommitパラメータこれはまだtrunkバージョンの話だが、IndexWriterにautoCommitモードなるものが追加された。autoCommitはtrue|falseのどちらかをIndexWriterを作成するときのコンストラクタで指定する。2.1以前の動作と同じとなる。ここでいうコミットとは、segments_Nファイルを書き込んでセグメントを確定させることを指す。2.0以前ではsetMaxBufferedDocs()(デフォルトは10)で指定したドキュメント数に達したときにメモリからインデックスにドキュメントが書き出されてsegments(当時は"_N"はつかなかった)にも反映されたので、この時点でIndexReaderをオープンすれば、インデックスに書き出されたドキュメントは検索にも反映された。2.1では、IndexWriterにflush()というメソッドが追加され、これはsetMaxBufferedDocs()にドキュメント数が達しない場合でも、flush()により強制的にメモリからインデックスにドキュメントを書き出させるものである。これにより、書き出しのタイミングコントロールがプログラムからできるようになった。trunkバージョンではさらにautoCommit=falseを指定することができるようになり、この場合、メモリからインデックスにドキュメントが書き出されてもsegments_Nを書き換えない。することによりIndexReaderにはコミット前の古いセグメントを見せることができる。autoCommit=falseを指定した場合に限り、IndexWriterをclose()またはabort()することを選択できる。close()はもちろん、IndexWriterを閉じてaddDocument()を確定させる(従来どおり)。abort()はやはりtrunkバージョンで加わったメソッドで、これを呼ぶとそれまでのaddDocument()はすべて破棄され、セグメントは古いままで更新されない。先週末にアンチウィルスソフトのためにインデックスファイルが読めなくなったという報告がLuceneメーリングリストに流れていたので、簡単に状況を記しておきたい。DeniedでIndexWriterがFileNotFoundExceptionをスローするというもの。報告者の報告がどうもはっきりしないが、インデックスに索引付けしようとしているファイルのひとつがウィルスに感染していたようだとある。Denied」という状況を考えると、segments_Nファイルの内容がウィルスパターンと酷似しており、アンチウィルスソフトがそのファイルのリードアクセスを不許可にしたのが真の原因のようだ。このことから得られる教訓は、Luceneインデックスディレクトリはアンチウィルスソフトのチェックからはずす。ウィルス感染の可能性があるファイルをLuceneに登録する場合は、登録する前にその前段でウィルスチェックを終了しておく。感染している場合は、Luceneインデックスに登録してはいけない。2.1より、インデックスセグメントのコミット時にロックをしなくなった。インデックスセグメントのコミットとは、インデックスファイルの構成が記されているsegmentsファイルを読み書きすることである。従来はコミット動作時はcommit.lockというファイル(実際のファイル名はもっと長い)がIndexWriterやIndexReaderらによって作成され、IndexReaderが読み込みオープン中のsegmentsファイルとインデックスセグメントがIndexWriterにより書き換わったり消されることを防いできた(逆にIndexWriterにより書き込み中(マージなど)のときにIndexReaderが読まないようにする役割もある)。commit.lockでインデックスセグメントの信頼のコミットメント(約束)を取り付けていたのだ。ローカルファイルシステムではこのしくみによりひとつのWriterと複数のReaderがインデックスを共有することになんら問題がないが、NFSやSambaなどを使って別筐体でインデックスを共有しようとしたときに問題が起こることがしばしば報告されてきた。Deniedが発生する。Unixの場合は削除は成功するが、実際にファイルが削除されるのは、すべてのIndexReaderがファイルをクローズした時点となる。NFSの場合は(Sambaはどうか不明)どうやらキャッシュが効いてしまうらしく、IndexWriterによる古いセグメントファイルの削除は成功し、NFS経由のIndexReaderがオープンしているファイルが実際に消されてしまう。本当に読み込もうとしたときにFileNotFoundExceptionが発生する、というのがNFSの環境下で報告されてきたエラーの原因である。これを考えると、いくらコミットロックをがんばっても解決できないのであった。NFSは手っ取り早くLuceneアプリケーションをスケールアップさせたいときに食指が動くツールである。NFSでインデックスを共有し、ひとつのWriterがインデックスを更新し、複数のSearcherが適当な間隔でopen/closeしながら検索を行えばよさそうだからだ。上記の理由から、すぐにSearcher側からFileNotFoundExceptionがスローされてしまう。2.1ではコミット時のロックを廃止したのである。したのかというと、segmentsファイルのファイル名をsegmentsという固定の名前ではなく、segments_Nという具合に変更し、Nをインクリメントする命名方法を導入した。すれば既存の(IndexReaderが読んでいるかもしれない)古いsegments_Nファイルを書き換えるためにコミットロックを作成する必要がなくなる。IndexWriterは古いsegments_Nファイルと古いセグメントファイルを削除する(前述の通りWindowsではこれが失敗することもあるが、その場合はあとで消すようだ)。すると、IndexReaderが古いsegments_Nの情報から取得したセグメントファイルをオープンしようとしたときにFileNotFoundExceptionが発生してしまう可能性がある。2.1ではIndexReaderがその例外に遭遇したときは新しいsegments_Nファイルを探してそこからロードする再試行をするように変更された。比較のポイント商用検索エンジンを購入した企業担当者は読まないでください。McCandlessFastVectorHighlighterについて解説記事を寄稿しました。