関口宏司のLuceneブログ

OSS検索ライブラリのLuceneおよびそのサブプロジェクト(Solr/Tika/Mahoutなど)について
スポンサーサイト

一定期間更新がないため広告を表示しています

| スポンサードリンク | - | | - | - |
インデックスのサイズを見るシミュレーションプログラム
Luceneのインデックスは検索しながら更新していると、古いインデックスを参照しているSearcherがいる間はセグメントを消すことができない。そのため、大きなマージが走るときはハードディスクの容量を一時的に大量に消費する。

その様子(マージの部分のみ)をシミュレーションする(Luceneを一切使用しない)プログラムを作成した。プログラムを実行するとCSVファイルが出力される。それをEXCELで表示したのが下の図である:

インデックスのサイズ

EXCELで表示できるよう、この図ではマージ係数を3に設定している。プログラムは以下の通りである:

public class MergeSimulator {
  
  static final String CSV_FILE = "segments.csv";
  static final int MERGE_FACTOR = 3;
  static final int MAX_BUFFERED_DOCS = 3;
  static final int NUM_DOCS = 3000;

  public static void main( String[] args ) throws FileNotFoundException {
    Directory dir = new Directory( MERGE_FACTOR );
    PrintStream ps = new PrintStream( CSV_FILE );
    dir.logHeader( ps );
    Writer writer = new Writer( MAX_BUFFERED_DOCS, dir, ps );
    for( int i = 0; i < NUM_DOCS; i++ ){
      writer.add( new Doc() );
    }
    ps.close();
  }

  static class Doc {
  }

  static class Segment {
    private final int level;
    private final int numDocs;
    private boolean deleted;
    public void delete(){ deleted = true; }
    public boolean isDeleted(){ return deleted; }
    public Segment( int numDocs ){
      this( 0, numDocs );
    }
    public Segment( int level, int numDocs ){
      this.level = level;
      this.numDocs = numDocs;
    }
    public int getSize(){ return numDocs; }
    public int getLevel(){ return level; }
  }
  
  static class Directory {
    private final int mergeFactor;
    private List> segmentsList = new ArrayList>();
    public Directory( int mergeFactor ){
      this.mergeFactor = mergeFactor;
    }
    public List> getSegmentsList(){ return segmentsList; }
    public int getNumSegments(){
      int num = 0;
      for( List segments : segmentsList ){
        if( segments == null ) continue;
        for( Segment segment : segments ){
          num++;
        }
      }
      return num;
    }
    public int getSize(){
      int size = 0;
      for( List segments : segmentsList ){
        if( segments == null ) continue;
        for( Segment segment : segments ){
          size += segment.getSize();
        }
      }
      return size;
    }
    public void addSegment( Segment segment ){
      int level = segment.getLevel();
      if( segmentsList.size() <= level )
        segmentsList.add( new ArrayList() );
      List segments = segmentsList.get( level );
      segments.add( segment );
      if( needMerge( level ) )
        merge( level );
    }
    public void merge( int level ){
      List segmentsToBeMerged = segmentsList.get( level );
      int totalDocs = 0;
      for( Segment segment : segmentsToBeMerged ){
        totalDocs += segment.numDocs;
        segment.delete();
      }
      Segment mergedSegment = new Segment( level + 1, totalDocs );
      addSegment( mergedSegment );
    }
    public boolean needMerge( int level ){
      List segments = segmentsList.get( level );
      if( segments == null ) return false;
      return segments.size() >= mergeFactor;
    }
    public void deleteMergedSegments(){
      for( List segments : segmentsList ){
        if( segments == null ) continue;
        for( Iterator ite = segments.iterator(); ite.hasNext(); ){
          Segment segment = ite.next();
          if( segment.isDeleted() ){
            ite.remove();
          }
        }
      }
    }
    public void logHeader( PrintStream ps ){
      ps.println( "number of segments, total size" );
    }
    public void log( PrintStream ps ){
      ps.println( getNumSegments() + ", " + getSize() );
    }
  }
  
  static class Writer {
    final int maxBufferedDocs;
    final Directory dir;
    final PrintStream ps;
    int docs;
    public Writer( int maxBufferedDocs, Directory dir, PrintStream ps ){
      this.maxBufferedDocs = maxBufferedDocs;
      this.dir = dir;
      this.ps = ps;
      docs = 0;
    }
    public void add( Doc doc ){
      if( ++docs >= maxBufferedDocs ){
        Segment segment = new Segment( docs );
        dir.addSegment( segment );
        dir.log( ps );
        dir.deleteMergedSegments();
        docs = 0;
      }
      else{
        dir.log( ps );
      }
    }
  }
}
| 関口宏司 | Luceneインデックス | 12:41 | comments(0) | trackbacks(0) |
setRAMBufferSizeMBの上限を明確化(2.9.1)
Lucene/Solrでヒープの消費に影響するパラメータのひとつであるIndexWriterのsetRAMBufferSizeMB()メソッドに渡す引数に、明確な上限値が設けられた:

https://issues.apache.org/jira/browse/LUCENE-1995

(今回のLUCENE-1995の問題は違うけれども)OutOfMemoryError(OOM)はLucene/Solrコミュニティでも常に話題の上位にランクインしている。繰り返し質問されるのは結局のところ、原理がよく理解できていないのが原因と思われる。インデックスサイズが増えたときにOOMに遭遇し、物理メモリを増やし-Xmxパラメータを増やすことでしのいでいる方は、ぜひ体系的にSolrを学び直すことをお勧めします。;-)


アフターサポート付き「速習 Solr 1.4」受講者募集中!


内容的にあまりに詰め込みすぎたため、年内に閉講するかもしれません。
| 関口宏司 | Luceneクラス解説 | 11:41 | comments(0) | trackbacks(0) |
ホワイトペーパー「Lucene 2.9の新機能」の公開
弊社のダウンロードページホワイトペーパー「Lucene 2.9の新機能」を公開した。
| 関口宏司 | Luceneリリース | 21:16 | comments(0) | trackbacks(0) |
BooleanQueryの不具合修正(2.9.1)
BooleanQueryで検索にヒットするはずのドキュメントが漏れる不具合が先日リリースされたばかりの2.9.0で発覚した。同じテストコードは2.4.1では出ないので、2.9.0で新たに入り込んだバグである。すでに修正され、2.9.1に含まれる。2.9.1にはFastVectorHighlighterの修正も含まれる。

BooleanQueryの不具合
https://issues.apache.org/jira/browse/LUCENE-1974

FastVectorHighlighterの不具合
https://issues.apache.org/jira/browse/LUCENE-1953

Solr 1.4はRCを現在準備中だが、Lucene 2.9.1を取り込む予定である。
| 関口宏司 | 不具合関連 | 10:10 | comments(0) | trackbacks(0) |
ほぼリアルタイムサーチの不具合
ほぼリアルタイムサーチ(NRT)はLucene 2.9に新しく加わった機能で、IndexWriterにgetReader()メソッドを持たせ、そのメソッドを使って取得したIndexReaderを使えば、それまでにIndexWriterに追加した未コミットの文書も検索対象にできる、というものである。

このNRTで取得したIndexReaderに不具合が報告された:

https://issues.apache.org/jira/browse/LUCENE-1976

NRTで取得したIndexReaderのisCurrent()とgetVersion()メソッドが正しい値を返さない、というのが不具合の内容である。現在はisCurrent()は常にtrueを返してしまい、getVersion()は最後のコミット時点のバージョンを返してしまう。

修正のアイディアとしては、isCurrent()についてはgetReader()よりあとにインデックスに変更があった場合はfalseを、そうでないときはtrueを返す。また、getVersion()についてはgetReader()を呼び出した時点でいいのではないか、とレポーターのMikeは言っている。

このNRTの不具合は検索自体は問題なく動作するので、ほとんどのNRTを利用しているアプリケーションはすぐに何か対応しなければならないということはないと思われる。

この不具合の修正は今のところ3.1に予定されている。Lucene 2.9を出したばかりのコミュニティでは現在、Lucene 3.0のリリースに向けた作業の真っ最中である。Lucene 3.0では2.Xのdeprecated APIが削除されるため、皆、「消す作業は楽しい!!」などといいながら激しいコミットログを日々飛ばしている。
| 関口宏司 | Luceneクラス解説 | 20:29 | comments(0) | trackbacks(0) |
(off topic)(メモ)svnコミットログの修正方法
Solrの不具合修正のコミットログを間違えてあせった。修正方法をメモしておく:

# まちがったコミットログのリビジョン番号を確認
$ svn log 修正をコミットしたファイル
# 上で得たリビジョン番号(824380とする)を指定してログメッセージを修正
$ svn propset --revprop -r 824380 svn:log "SOLR-670: Rollback should reset not only adds/deletesById/deletesByQuery counts but also cumulative counts of them."


コミットログを失敗した同じコンソールのワーキングコピーにて上記の通り操作する。コミットは不要で上の通り実行すればいきなり書き換わる。

(参考)http://svnbook.red-bean.com/en/1.1/re23.html
| 関口宏司 | その他(分類不能) | 00:40 | comments(0) | trackbacks(0) |
インデックス分割ツール IndexSplitter (3.0)
インデックスを分割するツールが今開発中のLucene 3.0に入った:

https://issues.apache.org/jira/browse/LUCENE-1959

ツールはNRT(ほぼリアルタイム)作者のJasonさんが提案したIndexSplitter(本日時点でコミット済み)と、それに反応してLuke作者のAndrzejさんが提案したMultiPassIndexSplitter(本日時点で未コミットだが入る模様)の2種類が含まれており、独立に動作する。
どちらもlucene-misc-3.0-dev.jarに入っているので、起動するにはこのJARファイルとlucene-core-3.0.jarをクラスパスに入れればよい。以下簡単にこれらのツールを紹介しよう。

IndexSplitter
IndexSplitterは、分割元のインデックスが複数セグメントであることを前提としたツールである。

使い方は、次のように引数なしで実行するとヘルプが表示される:

$ java -cp lib/lucene-core-3.0-dev.jar:lib/lucene-misc-3.0-dev.jar org.apache.lucene.index.IndexSplitter
Usage: IndexSplitter  -l (list the segments and their sizes)
IndexSplitter   +
IndexSplitter  -d (delete the following segments)


そこでまず、-lオプションを使って分割元のindexディレクトリのセグメントを表示してみる:

$ java -cp lib/lucene-core-3.0-dev.jar:lib/lucene-misc-3.0-dev.jar org.apache.lucene.index.IndexSplitter index -l
_vg 240
_vh 240
_vi 240


次に、_vhと_viのファイルをdestIndexディレクトリに移動する。この時点ではまだ分割元のインデックスディレクトリにセグメントファイルは残ったままである:

$ java -cp lib/lucene-core-3.0-dev.jar:lib/lucene-misc-3.0-dev.jar org.apache.lucene.index.IndexSplitter index destIndex _vh _vi
$ ls -l index
total 40
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vg.cfs
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vh.cfs
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vi.cfs
-rw-r--r--  1 koji  staff   20 10 10 12:58 segments.gen
-rw-r--r--  1 koji  staff  599 10 10 12:58 segments_3q
$ ls -l destIndex
total 32
-rw-r--r--  1 koji  staff  240 10 10 13:15 _vh.cfs
-rw-r--r--  1 koji  staff  240 10 10 13:15 _vi.cfs
-rw-r--r--  1 koji  staff   20 10 10 13:15 segments.gen
-rw-r--r--  1 koji  staff  410 10 10 13:15 segments_1


最後に、元のインデックスディレクトリに残ったままの_vhと_viのセグメントファイルを削除する。indexディレクトリの中を確認するとセグメントファイルとしては残っているが、新しいsegements_Nファイルができており、Luceneプログラムを作って中身を確認すれば、きちんと移動できていることがわかる:

$ java -cp lib/lucene-core-3.0-dev.jar:lib/lucene-misc-3.0-dev.jar org.apache.lucene.index.IndexSplitter index -d _vh _vi
$ ls -l index
total 48
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vg.cfs
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vh.cfs
-rw-r--r--  1 koji  staff  240 10 10 12:58 _vi.cfs
-rw-r--r--  1 koji  staff   20 10 10 13:18 segments.gen
-rw-r--r--  1 koji  staff  599 10 10 12:58 segments_3q
-rw-r--r--  1 koji  staff  221 10 10 13:18 segments_3r



MultiPassIndexSplitter
MultiPassIndexSplitterは分割元のインデックス(=入力インデックス)が複数セグメントであることを前提とはしていない。また、出力インデックスとして2つ以上のインデックスを指定して、入力インデックスを複数の出力インデックスに出力する。
MultiPassIndexSplitterの使い方も、引数なしで起動すればヘルプが表示される:

$ java -cp lib/lucene-core-3.0-dev.jar:lib/lucene-misc-3.0-dev.jar org.apache.lucene.index.MultiPassIndexSplitter
Usage: MultiPassIndexSplitter -out <outputDir> -num <numParts> [-seq] <inputIndex1> [<inputIndex2 ...]
	inputIndex	path to input index, multiple values are ok
	-out ouputDir	path to output directory to contain partial indexes
	-num numParts	number of parts to produce
	-seq	sequential docid-range split (default is round-robin)


-outの後ろに出力インデックスディレクトリの親ディレクトリを指定し、-numの後ろに出力するインデックス数を指定する(2以上でなければならない)。そして入力インデックスとして少なくとも1つの入力インデックスディレクトリ名を指定する。以上が必須のパラメータとなる。

さらに-seqを指定すると、出力インデックスへはそれぞれ連続したdocidのドキュメントが出力される。たとえば、入力インデックスにdocid=[0,1,2,3,4,5,6,7,8]のドキュメントがあり、-numに3を指定した場合、-seqとすると、3つの出力インデックスはそれぞれ、[0,1,2], [3,4,5], [6,7,8]のdocidとなる。

-seqを指定しないときのデフォルトは、ラウンドロビンであり、3つの出力インデックスはそれぞれ、[0,3,6], [1,4,7], [2,5,8]のdocidとなる。

IndexSplitter、MultiPassIndexSplitterともpublicなsplit()というインスタンスメソッドを持ち、適切なパラメータを渡せばAPIとしても使うことができる。
| 関口宏司 | Luceneツール | 17:30 | comments(0) | trackbacks(0) |
Solr 1.4 トレーニングコース
Solr 1.4トレーニングコースを開始しました。

http://www.rondhuit.com/training.html

第1回は、10月21日(水)開催となります。
| 関口宏司 | Solr | 22:44 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
    123
45678910
11121314151617
18192021222324
25262728293031
<< October 2009 >>
+ LINKS
検索エンジン製品 - 比較のポイント
商用検索エンジンを購入した企業担当者は読まないでください。ショックを受けますから・・・
>>製品比較 10のポイント
+ Lucene&Solrデモ
+ ThinkIT記事
+ RECOMMEND
Apache Solr入門 ―オープンソース全文検索エンジン
Apache Solr入門 ―オープンソース全文検索エンジン (JUGEMレビュー »)
関口 宏司,三部 靖夫,武田 光平,中野 猛,大谷 純
+ RECOMMEND
Lucene in Action
Lucene in Action (JUGEMレビュー »)
Erik Hatcher,Otis Gospodnetic,Mike McCandless
FastVectorHighlighterについて解説記事を寄稿しました。
+ RECOMMEND
+ SELECTED ENTRIES
+ RECENT COMMENTS
+ RECENT TRACKBACK
+ CATEGORIES
+ ARCHIVES
+ MOBILE
qrcode
+ SPONSORED LINKS