関口宏司のLuceneブログ

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

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

| スポンサードリンク | - | | - | - |
DVDレンタルのNetflixはSolr 1.4-devを利用の件
Solr 1.4のリリースが遅れだしてきており、私の周りでも「Solrのどのバージョンを使えばいいか」という質問が多く聞かれるようになった。現在はLucene 2.9のリリースを待つような気配となっている。

そんな中、日本では「集合知プログラミング」ですっかりおなじみとなったDVDレンタルのNetflixに勤務するサーチ・ソフトウェア・エンジニアのWalter Underwoodさんが「NetflixではSolr 1.4のnightly buildを使用している」とメーリングリストで言及したのでここで紹介しよう:

http://www.nabble.com/Upgrade-to-solr-1.4-td24221475.html

ちなみにNetflixは一日で200万クエリ以上をSolrでさばいているサイトである。

Netflixで使用しているSolr 1.4のバージョンは2009-05-11のnightly buildで、利用に当たっては通常の品質保証テストを行い、その後5台のうちの1台にSolr 1.4を適用し、2週間動作確認をして問題がないことを確認したあと5台全部にデプロイしたのが昨日。Solr 1.4にしてCPU使用率を10%減らしつつ、10%の処理トラフィック向上を達成したということである。
| 関口宏司 | Solr | 09:35 | comments(0) | trackbacks(0) |
Luceneバージョンの後方互換性に関するポリシーの変更の提案(3.0)
Lucene 3.0から後方互換性のポリシーを変更(緩める)しようかという提案が行われている:

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

現在のポリシーは前の記事を参照していただくとして、ここでは提案されている変更部分を示す:

  • あるバージョンでdeprecatedとなったpublicおよびprotectedのAPIはマイナーバージョンアップを含む1つあとのリリースでは削除可能とする。たとえば、3.1のAPIが3.2でdeprecatedとなった場合、これまでは4.0のリリースで削除されていたのが3.3のリリースでも削除される可能性が出てきた。そのようなAPIには@deperecatedアノテーションの表示とともに、削除される最小バージョンを明示する。
  • リリースノートに「非互換変更点(Incompatible changes)」という項目を設け非互換のAPI変更点を明示する。その際、アプリケーションをどのように書き換えるべきかも解説する(Eclipseのリリースノートのように)。


なお、インデックスフォーマットに関するポリシーの変更はない。

これまでのLuceneユーザにとって、マイナーバージョン間でのAPIの非互換は結構インパクトが大きいと思われる。本提案はコミッター内での簡単な投票が行われ、上記JIRAでユーザ向けに公開/提案されているものであり、特に反対意見がなければこのようにポリシー変更がなされると思われる。
| 関口宏司 | Luceneとは? | 09:12 | comments(0) | trackbacks(0) |
インデックス全体のユニークターム数を取得する(2.9)
Lucene 2.9のIndexReaderに、インデックス全体のユニークターム数を取得するgetUniqueTermCount()というメソッドが追加された:

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

「インデックス全体」とはいっても、複数セグメントからなるインデックスに対してこのメソッドを呼ぶとUnsupportedOperationExceptionが発行されてしまう。なぜなら複数セグメントからなるインデックスのIndexReaderの実体はMultiSegmentReaderとなり、個々のセグメントには同じTermが存在する可能性があり、マージ(optimize())しなければ同定が困難だからである。

なお、このメソッドは全フィールドに対するユニークターム数をカウントする。
| 関口宏司 | Luceneクラス解説 | 10:17 | comments(0) | trackbacks(0) |
よりソートが速くなったLucene 2.9
Lucene 2.9はソートがより速くなった。まず先日の記事で紹介したとおり、スコアが不要の時はScorerを呼ばないようにすることができるようになった。

また複数セグメントのインデックスにおいて、IndexReaderのreopen()時に変更のないセグメントは再読み込みしないようにしてwarm-up時間を短縮できるようになっている:

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

つまり、ソート時に使用するFieldCacheの内容が引き継がれるのでIndexReaderのreopen()後のソートがこれまでより速くなることが期待できる。
| 関口宏司 | Luceneパフォーマンス | 09:54 | comments(0) | trackbacks(0) |
"{x TO y]" や "[x TO y}"の範囲検索
現在のLuceneの範囲検索では"{"(中カッコ)と"]"(カギカッコ)を組み合わせて"{3 TO 6]"という検索をしようとすると、QueryParserによって次のように怒られる:
Exception in thread "main" org.apache.lucene.queryParser.ParseException: Cannot parse '{3 TO 6]': Encountered "" at line 1, column 8.
Was expecting:
    "}" ...
これをLucene 3.1にて可能にしようという提案がある:

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

Lucene 2.9以前で"{3 TO 6]"をするにはどうすればよいかという質問がMLに流れていて、それへの回答が"{3 TO 6} OR 6"でできますね、とあった。う〜ん、なるほど。

上記Q&AはQueryParserを使う前提である。しかしプログラムによりRangeQueryを次のように作成すれば、Lucene 2.9以前でも"{3 TO 6]"を表現することはできる:

RangeQuery query = new RangeQuery( F, "3", "6", false, true );

以下にQueryParserやRangeQueryを使った範囲検索のプログラム例を示す:

public class MixIncExRange {
  
  static Directory dir = new RAMDirectory();
  static Analyzer analyzer = new WhitespaceAnalyzer();
  static final String F = "f";
  static final String[] DOCS = { "1", "2", "3", "4", "5", "6", "7", "8" };

  public static void main(String[] args) throws Exception {
    makeIndex();
    testQuery();
    testParser();
  }

  static void makeIndex() throws IOException {
    System.out.println( "***** DATA *****" );
    IndexWriter writer = new IndexWriter( dir, analyzer, true, MaxFieldLength.LIMITED );
    for( String s : DOCS ){
      System.out.print( s + " " );
      Document doc = new Document();
      doc.add( new Field( F, s, Store.YES, Index.ANALYZED ) );
      writer.addDocument( doc );
    }
    writer.close();
    System.out.println();  // print '¥n'
  }
  
  static void testQuery() throws IOException {
    System.out.println( "¥n***** testQuery *****" );
    // 3 < n < 6
    RangeQuery rq1 = new RangeQuery( F, "3", "6", false, false );
    exec( "3 < n < 6", rq1 );
    // 3 <= n <= 6
    RangeQuery rq2 = new RangeQuery( F, "3", "6", true, true );
    exec( "3 <= n <= 6", rq2 );
    // 3 < n <= 6
    RangeQuery rq3 = new RangeQuery( F, "3", "6", false, true );
    exec( "3 < n <= 6", rq3 );
  }
  
  static void testParser() throws Exception {
    System.out.println( "¥n***** testParser *****" );
    // 3 < n < 6
    execParser( "3 < n < 6", "{3 TO 6}" );
    // 3 <= n <= 6
    execParser( "3 <= n <= 6", "[3 TO 6]" );
    // 3 < n <= 6
    execParser( "3 < n <= 6", "{3 TO 6} OR 6" );
  }
  
  static void exec( String q, Query query ) throws IOException {
    System.out.print( q + " ---> ");
    System.out.print( query.toString() + " ---> " );
    IndexSearcher searcher = new IndexSearcher( dir );
    TopDocs docs = searcher.search( query, 10 );
    for( ScoreDoc scoreDoc: docs.scoreDocs ){
      int docId = scoreDoc.doc;
      Document doc = searcher.doc( docId );
      System.out.print( doc.get( F ) + " " );
    }
    searcher.close();
    System.out.println();  // print '¥n'
  }
  
  static void execParser( String q, String parsedQuery ) throws Exception {
    QueryParser parser = new QueryParser( F, analyzer );
    Query query = parser.parse( parsedQuery );
    System.out.print( parsedQuery + " ---> " );
    exec( q, query );
  }
}

このプログラムを実行すると、次のように表示される:

***** DATA *****
1 2 3 4 5 6 7 8 

***** testQuery *****
3 < n < 6 ---> f:{3 TO 6} ---> 4 5 
3 <= n <= 6 ---> f:[3 TO 6] ---> 3 4 5 6 
3 < n <= 6 ---> f:{3 TO 6] ---> 4 5 6 

***** testParser *****
{3 TO 6} ---> 3 < n < 6 ---> f:{3 TO 6} ---> 4 5 
[3 TO 6] ---> 3 <= n <= 6 ---> f:[3 TO 6] ---> 3 4 5 6 
{3 TO 6} OR 6 ---> 3 < n <= 6 ---> f:{3 TO 6} f:6 ---> 6 4 5 
| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 18:34 | comments(0) | trackbacks(0) |
フィールド値を持つ文書を検索する
あるフィールドに値がある(値が何であるかは気にしない)文書を検索したい場合は、ConstantScoreRangeQueryを使って次のようなQueryを作成し、それを使ってsearch()すればよい:
Query query = new ConstantScoreRangeQuery( "field", null, null, false, false );
テストプログラムを以下に示す。
public class GetAllDocsThatHasValues {

  static Directory dir = new RAMDirectory();
  static Analyzer analyzer = new WhitespaceAnalyzer();
  static final String F_ID = "id";
  static final String F_NAME = "name";
  static final String[] IDS = {
    "1", "2", "3", "4", "5"
  };
  static final String[] NAMES = {
    "a", "b", "", null, "c"
  };

  public static void main(String[] args) throws IOException {
    makeIndex();
    searcIndex();
  }

  static void makeIndex() throws IOException {
    IndexWriter writer = new IndexWriter( dir, analyzer, true, MaxFieldLength.LIMITED );
    for( int i = 0; i < IDS.length; i++ ){
      Document doc = new Document();
      doc.add( new Field( F_ID, IDS[i], Store.YES, Index.ANALYZED ) );
      if( NAMES[i] != null )
	doc.add( new Field( F_NAME, NAMES[i], Store.YES, Index.ANALYZED ) );
      writer.addDocument( doc );
    }
    writer.close();
  }

  static void searcIndex() throws IOException {
    Query query = new ConstantScoreRangeQuery( F_NAME, null, null, false, false );
    IndexSearcher searcher = new IndexSearcher( dir );
    TopDocs docs = searcher.search( query, 10 );
    for( ScoreDoc scoreDoc : docs.scoreDocs ){
      int docId = scoreDoc.doc;
      Document doc = searcher.doc( docId );
      System.out.println( doc.get( F_ID ) + ":" + doc.get( F_NAME ) );
    }
    searcher.close();
  }
}
実行結果は次の通りとなる。
1:a
2:b
5:c
| 関口宏司 | Hello, Lucene (入門者向けプログラム例) | 23:29 | comments(0) | trackbacks(0) |
HitCollectorからCollectorへ(2.9)

Lucene 2.9ではHitCollectorがdeprecatedになり、代わりにCollectorを使うことが推奨されている。

CollectorはHitCollector同様collect()メソッドを持っているが、その引数はdoc(int)だけであり、scoreがない。その代わりにsetScorer(Scorer)メソッドを持っていてcollect()が呼ばれ始める前にLuceneからsetScorer()が呼ばれる。Collectorユーザは渡されたScorerのscore()メソッドをcollect()内で呼び出し、スコアを得ることができる。

Collectorではこのようにcollect()でスコアが渡されないのでscore()を呼ぶ面倒があるが、collect()からスコアが分離されたため、スコア計算が不要なアプリケーションではその分検索を高速化できるようになった。

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

なお、従来のHitCollectorユーザのためのHitCollectorWrapperクラスが用意されており、HitCollectorWrapperのコンストラクタにHitCollectorオブジェクトを渡してnewすると、新しいCollectorクラスとしてHitCollectorWrapperを使うことができるようになっている(しかしこれもdeprecatedクラスであり、HitCollectorがなくなるときに一緒になくなる運命にある)。

スコア計算を行うCollectorはLucene提供のTopScoreDocCollectorを使うといいだろう:

TopScoreDocCollector collector = TopScoreDocCollector.create(10,false);
searcher.search(query,collector);

また、Searchable.search(Weight, Filter, int, Sort)を使うとスコア計算がされなくなり、指定されたソートのみを行うようになった:

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

もしソートもスコア計算もしたいのであれば、TopFieldCollectorを使ってSearchable.search(Weight, Filter, Collector)を次のように使う必要がある:

TopFieldCollector tfc = TopFieldCollector.create(sort, numHits, fillFields,
true /* trackDocScores */,
true /* trackMaxScore */,
false /* docsInOrder */);
searcher.search(query, tfc);
TopDocs results = tfc.topDocs();
| 関口宏司 | Luceneクラス解説 | 11:47 | comments(0) | trackbacks(0) |
ほぼリアルタイムサーチ(2.9)
「ほぼリアルタイムサーチ(Near Real Time Search; NRT)」はLucene 2.9に加わった機能である。「リアルタイムサーチ」機能とは、コンテンツ管理システムなどで管理されているドキュメントが追加/変更/削除などされた場合、検索結果がコンテンツとリアルタイムに連動している検索システムの検索機能のことである。

Lucene 2.9のリアルタイムサーチは「ほぼ」リアルタイムを実現するために、IndexWriterにgetReader()メソッドを加えた。これにより、インデックスにIndexWriterオブジェクトを使って追加したドキュメントが、同オブジェクトのgetReader()で取得したIndexReaderを使って即検索できるようになった:

writer.addDocument(doc); // ドキュメントの追加
IndexReader reader = writer.getReader(); // このreaderを使えば追加したdocが検索可能


「ほぼリアルタイム」の「ほぼ」がついている意味は、getReader()によりどのくらい素早くIndexReaderを返せるか保証していないためである。今後もコミュニティのフィードバックを見ながら改良していくことが予定されている機能である。

http://wiki.apache.org/lucene-java/NearRealtimeSearch

| 関口宏司 | Luceneクラス解説 | 11:58 | comments(0) | trackbacks(0) |
+ Solrによるブログ内検索
+ PROFILE
 123456
78910111213
14151617181920
21222324252627
282930    
<< June 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