« 2006年11月 | メイン | 2007年01月 »

2006年12月 アーカイブ

2006年12月01日

全文検索エンジン「JiroSearch」をオープンソース(GPL)として公開

メディア各位様
2006年12日1日
CRMソリューションズ株式会社

--------------------------------------------------------------
全文検索エンジン「JiroSearch」をオープンソース(GPL)として公開
--------------------------------------------------------------

 CRMソリューションズ株式会社(東京都港区:代表取締役 佐藤和己)
は、全文検索エンジン「JiroSearch」を2006年12月1日にGPL2(GNU
General Public License version 2)に従って、オープンソースソフト
ウエアとして自社Webサイトにて一般公開したことを発表します。
 「JiroSearch」 は、様々な全文検索ニーズに対応できることを目指し、
オープンソース(*1)とJavaで開発された全文検索エンジンです。
 面倒な設定作業やコーディング作業なしに、全文検索機能を導入す
ることが出来るとともに、様々な検索ニーズに合致したカスタマイズ
を行うことができます。


【JiroSearchの公開URL】
 http://www.crm.co.jp/jirosearch/

【JiroSearchの特徴的な機能】
 ・タグ機能 (---後述に詳細説明があります--)
 ・検索結果画面の容易なカスタマイズ
 ・検索語のログ保存機能
 ・検索者ごとの Session 管理(検索者の行動履歴のトレース)
 ・検索表示順のビジネスロジックの追加開発
 ・Linux/Windows双方への対応
 ・N-gram形式による検索インデックスの作成

【開発ロードマップ】
 ・タグによる動的ディレクトリー表示
 ・他の JiroSearch との連携・インデックス共有(グリッド検索)
 ・Session によるパーソナルな検索履歴(足跡機能)
 ・検索結果の ソーシャルブックマーク化
 ・検索語のログ機能によるトレンド分析
 ・リコメンデーション、アフィリエイトなどの誘導
 ・リンク切れの自動検出
 ・JSF 以外の view への対応 (例: 携帯対応)
 ・デスクトップサーチへの対応
 ・Ruby 等への移植、または連携

【タグ機能について】
 タグ機能とは、全文検索にディレクトリー型検索が合体した機能です。
 具体的には、htmlページごとにタグ名(tag/荷札)を付与しておくこ
とで、全文検索の検索結果ページにおいて、各タイトルの上段にタグが
表示されるようになります。そのタグをクリックすると、同じタグが付
与されているすべてのページが表示されます。
 たとえば、FAQサイトで、FAQごとに商品名のタグを付与しておくと、
検索結果のタイトルの上段に商品名タグが表示され、その商品名タグを
クリックすると、その商品に関するすべてのFAQが一括して表示されます。
このことにより、検索者は、その商品に関する全体のFAQの概要を掴んだ
上で、より的確なページにたどり着くことができるようになります。

*実際の動作は、弊社サイト内検索(http://www.crm.co.jp)でご覧いた
だけます。

【今後の展開について】
 今回の「JiroSearch」は、2006年10月に公開した Web2.0開発ツール
「JIRO」 がオープンソースの便益を受けて開発された製品であることか
ら、これらの技術をオープンソースコミュニティーに還元するとともに、
全文検索技術の向上および企業のオープンソースの利用推進に貢献する
ことを目的としております。
 また、弊社のビジネス面においては、「JiroSearch」をベースにした
CRM(カスタマー・リレーションシップ・マネジメント)を起点に、Web
サイトの検索ソリューションの提供、ECM(エンタープライズ・コンテン
ツ・マネジメント)、ESP(エンタープライズ・サーチ・プラットフォーム)
など、企業システム構築の有償サポートを行います。

2006年12月02日

ant を実行したときに Unknown argument -cp というエラー

JiroSearch がリリースされたが、 手順の詳細など、 正式ページに出ていない情報をここに書き足していく予定ですので、よろしくお願いします。

環境によっては、 タイトルのようなエラーが出ることがある。

[webmaster]$ ant createIndex
Unknown argument: -cp
ant [options] [target [target2 [target3] ...]]
Options:

これは ant のデフォルト設定が何かを邪魔しているのが原因らしい。 その場合は、 --noconfig というオプションを付けると実行できる。 例えば、こんな感じ。

ant --noconfig filecrawl

次のようになる。

[webmaster]$ ant --noconfig createIndex
Buildfile: build.xml

createIndex:
     [java] 18:16:05,981  INFO org.springframework.core.CollectionFactory:76
         - JDK 1.4+ collections available
     [java] 18:16:06,003  INFO org.springframework.core.CollectionFactory:80
         - Commons Collections 3.x available
     [java] 18:16:06,136  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:347
         - Loading XML bean definitions from file [/usr/local/jiro/search/search/WEB-INF/applicationContext.xml]
     [java] 18:16:07,475  INFO org.springframework.beans.factory.xml.XmlBeanDefinitionReader:347
         - Loading XML bean definitions from file [/usr/local/jiro/search/search/WEB-INF/systemProperties.xml]
     [java] 18:16:07,601  INFO org.springframework.context.support.AbstractRefreshableApplicationContext:100
         - Bean factory for application context [org.springframework.context.support.FileSystemXmlApplicationContext;hashCode=7043360]: 
        org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sessionBean,queries,highlightFilter,searchBean,fileCrawler,fileInfoUpdater,stubSearcher,analyzer,searcher,documentSearcher,htmlDocumentFactory,textDocumentFactory,indexUpdater,indexManager,xmlParser,propertyParser,propertyXmlWriter,fileInfoXmlWriter,systemProperties,facade,simpleTextParser,mappingFilter,tagMapper,titleParser,bodyParser,webPageParser]; root of BeanFactory hierarchy
     [java] 18:16:07,625  INFO org.springframework.context.support.AbstractApplicationContext:322
         - 26 beans defined in application context [org.springframework.context.support.FileSystemXmlApplicationContext;hashCode=7043360]
     [java] 18:16:07,703  INFO org.springframework.context.support.AbstractApplicationContext:473
         - Unable to locate MessageSource with name 'messageSource': using default [org.springframework.context.support.DelegatingMessageSource@1db7df8]
     [java] 18:16:07,715  INFO org.springframework.context.support.AbstractApplicationContext:495
         - Unable to locate ApplicationEventMulticaster with name 'applicationEventMulticaster': 
        using default [org.springframework.context.event.SimpleApplicationEventMulticaster@79717e]
     [java] 18:16:07,725  INFO org.springframework.beans.factory.support.DefaultListableBeanFactory:261
         - Pre-instantiating singletons in factory [org.springframework.beans.factory.support.DefaultListableBeanFactory defining beans [sessionBean,queries,highlightFilter,searchBean,fileCrawler,fileInfoUpdater,stubSearcher,analyzer,searcher,documentSearcher,htmlDocumentFactory,textDocumentFactory,indexUpdater,indexManager,xmlParser,propertyParser,propertyXmlWriter,fileInfoXmlWriter,systemProperties,facade,simpleTextParser,mappingFilter,tagMapper,titleParser,bodyParser,webPageParser]; root of BeanFactory hierarchy]
     [java] 18:16:26,514  INFO jp.co.crm.util.xml.XmlWriterImpl:35
         - write: filename = /usr/local/jiro/search/fileInfo.xml

BUILD SUCCESSFUL
Total time: 22 seconds

タグを指定した状態で検索語を指定するとタグで絞り込んだ結果になる

これは間違いなく仕様通りの動作なのだが、 検索語を入れたときに、 タグで絞り込まれていることに気付かずに指定してしまって、 期待した結果が得られないことがある。 つまり、ユーザビリティ的な問題がないわけではない。

名案がないのだが、 例えば[検索]というボタンを [絞り込んで検索][タグを解除して検索] の2つにするという方法は可能である。 なお、検索してからタグを解除するのは単にタグの欄を空白にしてやればいい。

JiroSearch は検索ユーザーをセッションで管理しているので、 ブラウザを閉じたりセッションタイムアウトするまでの間の検索履歴を使おうと思えば使える。 パンくずのようなものを残しておけば、 少し前の条件に戻って検索するというUIも実装可能だと思う。

2006年12月04日

JapaneseAnalyzer を使う

JiroSearch は Spring Framework を使っているので、 設定の大部分を applicationContext.xml を編集して変更することができる。 JiroSearch は Lucene の CJKAnalyzer を使っているが、 これを JapaneseAnalyzer を使うように変更する方法は次の通り。

WEB-INF/applicationContext.xml 内に、次のような記述がある。

  <bean id="analyzer"
    class="org.apache.lucene.analysis.cjk.CJKAnalyzer"/>

これを、次のように変更する。

  <bean id="analyzer"
    class="org.apache.lucene.analysis.ja.JapaneseAnalyzer">
    <constructor-arg type="java.lang.String"
      value="/usr/local/jiro/sen-1.2.2.1/conf/sen.xml"/>
  </bean>

変更箇所はここだけで、 Java のプログラムの再コンパイル等は一切必要ない。 ただし、 analyzer を入れ替えたら今までの index は使えないので、 index の再構築が必要になる。 具体的には、 fileInfo.xml と、index ディレクトリの中身を削除して、 ant で fileCrawler、createIndex を実行する。 もちろん、sen を事前にインストールしておく必要がある。

sen は環境変数をみてインストールされたディレクトリを判定する仕組だが、 コンストラクタに設定ファイル sen.xml を指定すれば、 環境変数を指定しなくても使うことができる。 Spring Framework でコンストラクタを使った初期化を行うには、 前述の例のように constructor-arg を使う。 例は /usr/local/jiro/ に sen1.2.2.1 をインストールした場合のもので、 他の場所にインストールした場合は、 インストールしたディレクトリの conf/sen.xml を指すように指定すればよい。

JiroSearch 側の設定変更はこれだけだが、 動作させるためには、 sen.jar と lucene-ja.jar が必要になるので、 これらを WEB-INF/lib にコピーしておく。

これらのライブラリは、 https://sen.dev.java.net/servlets/ProjectDocumentList?folderID=755&expandFolder=755&folderID=0 からダウンロードする。 こちらの動作確認には、 lucene-ja-2.0test2.zip の Japa に入っている lucene-ja.jar と、 sen-1.2.2.1.zip に入っている sen.jar を使った。 lucene-ja-2.0test2.zip にも sen.jar が入っているので、 そちらを使ってもいいと思われるが、 sen-1.2.2.1.zip に入っているものの方が日付が新しいので、 そちらを使った。

ant 実行中に OutOfMemoryError

方法1: 環境変数 ANT_OPTS の値を設定する。

Windows の場合、 setpath.bat に次の行を追加する。

set ANT_OPTS=-Xmx256m

linux の場合、 setpath.bash の最後に次の行を追加する。

ANT_OPTS=-Xmx256m
export ANT_OPTS

方法2: build.xml の java タスクでメモリサイズを指定する。

    <java classname="jp.co.crm.jirosearch.lucene.IndexUpdater"
      classpathref="project.class.path"/>

上記の箇所を、 次のように1行追加する。

    <java classname="jp.co.crm.jirosearch.lucene.IndexUpdater"
      fork="true" maxmemory="256m"
      classpathref="project.class.path"/>

XHTML のコメントの書き方

これはもちろん正しい。

<!-- hoge -->

しかし実はこれも正しい。

<!-- hoge --     >

w3c の validator を通すと、validという評価になる。 ただし、 S separatior in comment declaration という警告は出る。

ということは、 XHTML のコメントを除去するソフトは、 このようなコメントも正しく認識する必要があるということだ。 例えば、

-->

という3文字連続を終わりとして認識するようなロジックだと、 正しい XHTML を処理できないおそれがある。

2006年12月05日

JDKのインストール先に注意

Windows版のJDKは、Administrator権限でないとインストールできません。このため
JiroSearchのインストール用に、\usr\local\jiro\javaに直接インストールする場合、直後にインストールが促されるJREで、JDKを上書きしないように注意が必要です。

Windows版のJDKをインストールするとき、インストール先を\usr\local\jiro\javaにすると、bin\のディレクトリは以下のようになります。

2006/11/09 14:40 53,370 appletviewer.exe
2006/11/09 14:40 53,359 apt.exe
2006/11/09 15:22 36,975 beanreg.dll
2006/11/09 14:40 53,368 extcheck.exe
2006/11/09 15:07 24,576 HtmlConverter.exe
2006/11/09 14:40 53,370 idlj.exe
2006/11/09 14:40 53,351 jar.exe
2006/11/09 14:40 53,372 jarsigner.exe
2006/11/09 14:40 49,248 java.exe
2006/11/09 14:40 53,365 javac.exe
2006/11/09 14:40 53,371 javadoc.exe
2006/11/09 14:40 53,365 javah.exe
2006/11/09 14:40 53,361 javap.exe
2006/11/09 14:40 53,346 javaw.exe
2006/11/09 15:07 127,078 javaws.exe
2006/11/09 14:40 53,370 jconsole.exe
2006/11/09 14:40 53,374 jdb.exe
2006/11/09 14:40 53,355 jps.exe
2006/11/09 14:40 53,361 jstat.exe
2006/11/09 14:40 53,364 jstatd.exe
2006/11/09 14:40 53,368 keytool.exe
2006/11/09 14:40 53,364 kinit.exe
2006/11/09 14:40 53,364 klist.exe
2006/11/09 14:40 53,362 ktab.exe
2006/11/09 14:40 53,382 native2ascii.exe
2006/11/09 14:40 53,383 orbd.exe
2006/11/09 14:40 53,376 pack200.exe
2006/11/09 15:07 69,745 packager.exe
2006/11/09 14:40 53,374 policytool.exe
2006/11/09 14:40 53,356 rmic.exe
2006/11/09 14:40 53,362 rmid.exe
2006/11/09 14:40 53,374 rmiregistry.exe
2006/11/09 14:40 53,373 serialver.exe
2006/11/09 14:40 53,395 servertool.exe
2006/11/09 14:40 53,392 tnameserv.exe
2006/11/09 14:40 127,101 unpack200.exe
36 個のファイル 2,035,770 バイト
2 個のディレクトリ 14,371,528,704 バイトの空き領域

直後にJREのインストールを促すパネルが表示されます。これをキャンセルしてもJiroSearchは正常に動作しますが、もし\usr\local\jiro\java\にインストールしてしまうと、bin\が以下のようになります。

2006/11/09 15:21 1,339,493 awt.dll
2006/11/09 15:07 94,321 axbridge.dll
2006/12/05 19:40 <DIR> client
2006/11/09 15:21 192,613 cmm.dll
2006/11/09 15:21 143,462 dcpr.dll
2006/11/09 15:21 69,632 deploy.dll
2006/11/09 15:21 32,872 dt_shmem.dll
2006/11/09 15:21 28,778 dt_socket.dll
2006/11/09 15:21 61,545 eula.dll
2006/11/09 15:21 262,262 fontmanager.dll
2006/11/09 15:21 32,878 hpi.dll
2006/11/09 15:21 122,983 hprof.dll
2006/11/09 15:21 57,466 instrument.dll
2006/11/09 15:21 28,802 ioser12.dll
2006/11/09 15:21 69,768 j2pkcs11.dll
2006/11/09 15:21 24,698 jaas_nt.dll
2006/11/09 15:21 118,890 java.dll
2006/11/09 13:28 49,248 java.exe
2006/11/09 15:07 45,171 javacpl.exe
2006/11/09 13:28 53,346 javaw.exe
2006/11/09 15:21 147,456 JavaWebStart.dll
2006/11/09 15:07 127,078 javaws.exe
2006/11/09 15:21 32,881 java_crw_demo.dll
2006/11/09 15:21 24,679 jawt.dll
2006/11/09 15:21 53,365 JdbcOdbc.dll
2006/11/09 15:21 200,800 jdwp.dll
2006/11/09 15:21 127,079 jpeg.dll
2006/11/09 15:21 86,129 jpicom32.dll
2006/11/09 15:07 49,265 jpicpl32.cpl
2006/11/09 15:21 94,321 jpiexp32.dll
2006/11/09 15:21 86,127 jpinscp.dll
2006/11/09 15:21 49,266 jpioji.dll
2006/11/09 15:22 77,937 jpishare.dll
2006/11/09 15:22 147,567 jsound.dll
2006/11/09 15:22 32,883 jsoundds.dll
2006/11/09 15:07 241,775 jucheck.exe
2006/11/09 15:07 49,263 jusched.exe
2006/11/09 13:47 53,368 keytool.exe
2006/11/09 13:47 53,364 kinit.exe
2006/11/09 13:48 53,364 klist.exe
2006/11/09 13:48 53,362 ktab.exe
2006/11/09 15:22 32,897 management.dll
2006/11/09 15:22 77,926 net.dll
2006/11/09 15:22 36,967 nio.dll
2006/11/09 15:22 69,743 NPJava11.dll
2006/11/09 15:22 69,743 NPJava12.dll
2006/11/09 15:22 69,743 NPJava13.dll
2006/11/09 15:22 69,743 NPJava14.dll
2006/11/09 15:22 69,743 NPJava32.dll
2006/11/09 15:21 75,528 NPJPI150_10.dll
2006/11/09 15:07 69,743 NPOJI610.dll
2006/11/09 14:18 53,383 orbd.exe
2006/11/09 14:19 53,376 pack200.exe
2006/11/09 13:47 53,374 policytool.exe
2006/11/09 15:38 143,473 RegUtils.dll
2006/11/09 15:22 24,677 rmi.dll
2006/11/09 14:10 53,362 rmid.exe
2006/11/09 14:10 53,374 rmiregistry.exe
2006/11/09 14:18 53,395 servertool.exe
2006/11/09 15:21 440,056 ssv.dll
2006/11/09 14:18 53,392 tnameserv.exe
2006/12/05 19:39 245,400 unicows.dll
2006/11/09 15:22 61,566 unpack.dll
2006/11/09 14:19 127,101 unpack200.exe
2006/11/09 15:22 49,252 verify.dll
2006/11/09 15:22 24,694 w2k_lsa_auth.dll
2006/11/09 15:22 61,547 zip.dll
66 個のファイル 6,862,755 バイト
3 個のディレクトリ 14,088,044,544 バイトの空き領域

この結果、tomcatの起動時に以下のエラーが表示されます。

C:\usr\local\jiro\tomcat\bin>startup
The JAVA_HOME environment variable is not defined correctly
This environment variable is needed to run this program
NB: JAVA_HOME should point to a JDK not a JRE

JREはインストールしないようにするか、またはJDKとは異なるディレクトリにインストールする必要があります。ご注意ください。

2006年12月06日

JiroSearch が検索結果を表示する手順

JiroSearch が検索結果を表示するときに、 本文の一部を表示している。

一般に、検索結果の一部を表示するには、 本文を適当な長さのブロック(チャンク、パラグラフ)に分割し、 それぞれに対して、 与えられた検索語に対する評価を行い、 関連性が高いものを選択する、 という処理を行う。 このためのクラスが Lucene には用意されている。 ( org.apache.lucene.search.highlight.Highlighter )

ただし、 現在の版の JiroSearch は、 このクラスを使っていない。 文章の真ん中にある検索語の前後を表示する という単純なロジックで抜き出しているのだ。 例えば「検索」という言葉が文章中に5個出現したら、 3番目の「検索」という文字の前後を表示しているのである。

最初は、 見つかった検索語の前後を表示してみたのだが、 サイトの多くがヘッダやフッタのような定型的な内容を含んでいて、 意外とそこに検索語があることが多いので、 あまりいい結果が得られない。 ということで、真ん中あたりを出した方がいいだろ、 という安直な発想でそのような処理に変更したのだ。 結果はご覧の通りで、そう悪くはないような気がする。

現在の処理は、 検索語の個数の真ん中に注目しているのだが、 もう一つ似た考え方で、 テキストの真ん中に一番近いもの、という方法もある。 JiroSearch のソースでは、 jp.co.crm.jirosearch.util.TextUtils クラスの getExcerpt メソッドがその処理になっているので、 ここを変更すれば抜き出す内容を変えることができる。

ファイル走査時に除外するパス

ant filecrawl を実行するときに、 パスに含まれる文字列を指定して、 対象から外すことができる。 この指定は、 systemProperties.xml 内の次の箇所で指定している。

    <property name="excludeCrawlPatterns">
      <list>
        <value>/intra/</value>
      </list>
    </property>

この例だと、/intra/ という文字列が含まれているパスを走査対象から外すことになる。 仮に /hoge/intra.html のようなパスがあったら、 ここには「/intra/」という文字列は含まれていないので除外されない。 /hoge/intra/foo.html なら「/intra/」という文字列が含まれるので、走査対象から除外される。

Windows で実行する場合は、 パスが「\」になることに注意。 「\hoge\intra\foo.html」 というパスには「/intra/」が含まれていないため、除外されない。 これを除外するには 「\intra\」という指定が必要になる。

2006年12月11日

seLinux を無効にする

/etc/selinux/config を編集する。

2006年12月12日

Linux 起動時のサービスの調整 (chkconfig)

chkconfig --list で状況を確認して、 起動時に稼動させたいサービスに対して、

chkconfig サービス名 on

のようにする。 稼動しないようにする場合は off。

xinetd 管理下のサービスは、 xinetd の設定に書かれているファイルをチェックして、 disable という所を編集する。

Eclipse 3.1.x から 3.2.1 へ upgrade

・Eclipse

http://www.eclipse.org/downloads/ から Eclipse SDK 3.2.1 をダウンロードして、 \java\eclipse に配置する。

起動するときに、 3.1.2 のときに使っていた \java\workspace を workspace として指定する。

・PMD

http://pmd.sourceforge.net/eclipse/ に書いてある手順を実行する。 概要は次の通り。

Eclipse を起動して、 Help>Sotfware Updates>Find and Install... メニューを開く。

Search for new features to install を選択。

New Remote Site を選択し、 name を PMD Eclipse Site、 URL を http://pmd.sourceforge.net/eclipse とする。

後は手なりで何とかなるはず。

・DBViewer

DB を使わないのなら必要ないが、 JIRO の開発にはあると便利。

http://www.ne.jp/asahi/zigen/home/plugin/dbviewer/about.html

最新の jar を plugin の下に配置する。

・GEF

http://download.eclipse.org/tools/gef/downloads/ から、バージョン 3.2.1 を選択し、 GEF-ALL-3.2.1.zip をダウンロードして、適切な場所に展開。

・Amateras htmleditor

http://amateras.sourceforge.jp/cgi-bin/fswiki/wiki.cgi
の Products から EclipseHTMLEditor
を選択し、
ダウンロードページの「こちら」と書いてあるリンクをクリックする。

・tomcat plugin

http://www.sysdeo.com/eclipse/tomcatplugin

3.2 beta3, tomcatPluginV32beta3.zip というのが 2006-11-20 付けで出ているので、これを使う。

Eclipse 3.1.2 から 3.2.1 に upgrade したらソース行表示でエラー

プログラム実行時、 Exception が発生したときにスタックトレースが表示されるが、 ソース行へのリンクをクリックしたらエラーが発生するようになった。

An exception occurred while follwing link.

Reason:
  Error logged from JDK Debug UI

Error logged from JDT Debug UI: 
  Unable to access archive D:\java\eclipse\plugins\org.eclipse.jdt.source_3.1.1\src\org.junit_3.8.1\junitsrc.zip

Eclipse 3.1.2 で使っていたプロジェクトの .classpath を見ると、次のような行がある。

<classpathentry sourcepath="ECLIPSE_HOME/plugins/org.eclipse.jdt.source_3.1.1/src/org.junit_3.8.1/junitsrc.zip"
 kind="var" path="JUNIT_HOME/junit.jar"/>
eclipse を終了して、 エディタでこの行を削除してから再起動すると、 JUnit へのパスがなくなっているため、 JUnit test case を使ったソースでエラーが発生する。 改めてJUnit を指定し直せば解決する。

2006年12月18日

QueryParser の field の謎

org.apache.lucene.queryParser.QueryParser は、どうやって Field.Index.TOKENIZED と Field.Index.UN_TOKENIZED の判断をしているのだろうか?

というのは、 API reference manual の、UN_TOKENIZED のところには、次のように書いてある。

Index the field's value without using an Analyzer, so it can be searched. As no analyzer is used the value will be stored as a single term. This is useful for unique Ids like product numbers.

ということで、JiroSearch ではタグの検索するために Field に追加する場合、 UN_TOKENIZED を指定していたのだが、 これがどうもうまく検索してくれない。 というか、 UN_TOKENIZED を指定した field も analyzer を call しているような気がする。

ただし、 index を作る時には analyzer を呼んでいないと思われる。 マニュアルの記述をよく読めば、 index を作る時に呼ばないと書いてあるだけであって、 検索する時にも呼ばないとは書いてないような気もする。 ただ、仮にそうだとして、 検索時だけ analyzer を必ず通すというような実装でもうまく検索できるものなのだろうか、 という疑問が生じる。

QueryParser のソースを追いかけてみても、 どこで field (ただしこれはフィールドの名前を示す String であり、Field ではない) を使っているのか分からないというか、 他に丸投げしているだけで、 QueryParser の中では一切 field に関する判定をしていないような気がする。 関口さんのブログの「関口宏司のLuceneブログ | 独自QueryParserの作成(5)」に独自実装した MyQueryParser というクラスのソースが公開されているのだが、 そちらを見ても、 field という String の中身がどうあれ analyzer を呼び出しているような気がする。

じっくり見たわけではないので私が何か見落としている可能性も高い。 一番ありそうなのは、 field が UN_TOKENIZED の場合はそもそも QueryParser に飛んでこない、 というような実装が上位でされている、というような状況の見落としだが、 動かしていると実際呼ばれているとしか思えないような振る舞いをしているような気がしてしょうがない。

また、もし前述のような実装になっているとしたら、 field という String を何のために analyzer を呼び出すときの引数に指定しているのかが分からない。 もしかして、 そのあたりに何か仕掛けがあるのだろうか?

2006年12月19日

tag の field は Analyzer を使わず body の field には CJKAnalyzer を使う

昨日書いた QueryParser の field の謎の件は簡単に解決しそうにない。 多少工夫してみたのだが、 どうあっても Analyzer を呼び出してしまうので、 Analyzer を呼ばないようにするのは諦めた。 要するに Analyzer が何もしなければいいのだから、 何もしない Analyzer を用意して、それを指定すればいいのでは。 という逆転の発想である。

試しに SimpleAnalyzer とか StandardAnalyzer を使ってみたのだが、 殆どうまくいく。 しかし、レアケースではあるが、 例えば文字列に white space が入っているといけない。 space は区切りと解釈してしまうからである。 例えば "DS Lite" は、"DS" と "Lite" の2つの Token に分かれてしまう。 tag に登録されている String は "DS Lite" だからうまくない。

何もしない Analyzer というのはないのか。 なさそうな気がしたので、 作った方が早いだろと思って作ってみた。 まず、何もしない Tokenizer を作る。

package jp.co.crm.jirosearch.lucene;

import java.io.Reader;

import org.apache.lucene.analysis.CharTokenizer;

public class NoActionTokenizer extends CharTokenizer {

	public NoActionTokenizer(Reader input) {
		super(input);
	}

	@Override
	protected boolean isTokenChar(char c) {
		return true;
	}
}

isTokenChar(char c) が必ず true を返す。 ということは、この input には切れ目がなく、 全てが有効な文字ということだから、 CharTokenizer は与えられた文字列をそのまま単独の Token にしてくれるはずだ。 次に、これを使った Analyzer を作る。

import java.io.Reader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;

public class NoAnalyzer extends Analyzer {

	@Override
	public TokenStream tokenStream(String fieldName, Reader reader) {
		return new NoActionTokenizer(reader);
	}
}

この NoAnalyzer を Analyzer として指定すれば、 与えた文字列を空白等も全部ひっくるめてそのまま1つの Token にするから、 結果的には Analyzer を呼ばなかったのと同じことになる。

これでタグに対して何もしない Analyzer を指定する準備が整った。 さて、タグに対してこの NoAnalyzer を指定し、 body には CJKAnalyzer を指定したい、 というような場合はどうすればよいか。

複数の Field に対して検索をかけたい場合は、 org.apache.lucene.queryParser.MultiFieldQueryParser を使う。 このクラスには、いくつか static な method が用意されている。 例えば、

static Query parse(String[] queries, String[] fields, Analyzer analyzer);

このように、 フィールドごとに query を指定することはできるのだが、 analyzer は一つしか指定できない。 Field によって異なる Analyzer を使い分けたい場合は、 org.apache.lucene.analysis.PerFieldAnalyzerWrapper を使ってラップすればいい。 このクラスには、 次のようなメソッドが用意されている。

void addAnalyzer(String fieldName, Analyzer analyzer);

手順としては、 PerFieldAnalyzerWrapper のインスタンスをまず作成しておき、 その後、 addAnalyzer メソッドを呼び出して、 デフォルトではない Analyzer を使いたいフィールドを指定することになる。

さて、これはこれでいいのだが、 JiroSearch は Spring Framework を使っているから、 フィールドに対する Analyzer の指定は applicationContext.xml に書きたいところだ。 ところが、 PerFieldAnalyzerWrapper には setter も getter もないから、 Analyzer を指定するのが面倒である。 ということで、 もともと小さいクラスだし、 勝手に setter、getter を追加した。

public class PerFieldAnalyzerWrapper extends Analyzer {
    private Analyzer defaultAnalyzer;

    private Map<String, Analyzer> analyzerMap = new HashMap<String, Analyzer>();

    public PerFieldAnalyzerWrapper() {
        super();
        this.defaultAnalyzer = new SimpleAnalyzer();
    }
    
    /**
     * Constructs with default analyzer.
     * 
     * @param defaultAnalyzer
     *            Any fields not specifically defined to use a different
     *            analyzer will use the one provided here.
     */
    public PerFieldAnalyzerWrapper(Analyzer defaultAnalyzer) {
        this.defaultAnalyzer = defaultAnalyzer;
    }

    /**
     * Defines an analyzer to use for the specified field.
     * 
     * @param fieldName
     *            field name requiring a non-default analyzer
     * @param analyzer
     *            non-default analyzer to use for field
     */
    public void addAnalyzer(String fieldName, Analyzer analyzer) {
        analyzerMap.put(fieldName, analyzer);
    }

    public TokenStream tokenStream(String fieldName, Reader reader) {
        Analyzer analyzer = analyzerMap.get(fieldName);
        if (analyzer == null) {
            analyzer = defaultAnalyzer;
        }

        return analyzer.tokenStream(fieldName, reader);
    }

    public String toString() {
        return "PerFieldAnalyzerWrapper(" + analyzerMap + ", default="
                + defaultAnalyzer + ")";
    }

    public Map<String, Analyzer> getAnalyzerMap() {
        return analyzerMap;
    }

    public void setAnalyzerMap(Map<String, Analyzer> analyzerMap) {
        this.analyzerMap = analyzerMap;
    }

    public void setDefaultAnalyzer(Analyzer defaultAnalyzer) {
        this.defaultAnalyzer = defaultAnalyzer;
    }
}

このように手を入れた class を使えば、 Spring の設定を次のように書くことができる。

  <bean id="analyzer"
    class="jp.co.crm.jirosearch.lucene.PerFieldAnalyzerWrapper">
    <property name="defaultAnalyzer"><ref local="simpleAnalyzer"/></property>
    <property name="analyzerMap">
      <map>
        <entry key="body"><ref local="cjkAnalyzer"/></entry>
        <entry key="tag"><ref local="simpleAnalyzer"/></entry>
      </map>
    </property>
  </bean>

このようにして、 tag を割り当てた field は Analyzer を通さずに検索し、 body の field に対しては、CJKAnalyzer を使う、 という処理になった。

2006年12月22日

システムの負荷チェック (Linux, Fedora core 6)

1. システムモニターを見る。 今どうなのか、というのが分かる。

2. top を実行する。 負荷を高くしている原因のプロセスが何か絞ることができる。

3. vmstat を実行する。 連続して確認したい場合は、vmstat 10 のように、 間隔を秒単位で指定できる。 負荷がディスクi/oなのか、ネットワークなのか、CPUなのか、 というような原因の切り分けができる。

org.apache.lucene.analysis.KeywordAnalyzer

先の投稿 で analyze しない NoAnalyzer というアナライザを使う話を書いたが、 別の掲示板を見るとやはりそういう時は KeywordAnalyzer を使えという話が書いてあって、 実際、NoAnalyzer を使う前に KeyworkAnalyzer を使ってみたのだが、 うまく行かなかったので、わざわざあのようなクラスを作ったのである。

ただ、その後普通に KeywordAnalyzer を使ってみたら、 何の問題もなくうまく動作する。 つまり、次のように指定すればいい。

  <bean id="simpleAnalyzer"
    class="org.apache.lucene.analysis.KeywordAnalyzer"/>

  <bean id="analyzer"
    class="jp.co.crm.jirosearch.lucene.PerFieldAnalyzerWrapper">
    <property name="defaultAnalyzer"><ref local="simpleAnalyzer"/></property>
    <property name="analyzerMap">
      <map>
        <entry key="body"><ref local="cjkAnalyzer"/></entry>
        <entry key="tag"><ref local="simpleAnalyzer"/></entry>
      </map>
    </property>
  </bean>

では元のコードはどこがおかしかったのか、 というのが変更しすぎてよく分からない。

Lucene - index を生成する速度

JiroSearch の現在公開している版は、 index の生成を、最も基本的な方法、つまり、 単一のスレッドで indexWriter を順に呼ぶだけの処理を行っている。 おそらく、これは最も効率のよくない方法で、 大量のインデックスを作ると時間がかかりすぎるのではないか。

ということで、 マルチスレッドを使って org.apache.lucene.store.RAMDirectory を使って index を生成ながら、 最後に FSDirectory にマージする、 という方法がよく紹介されているようだ。 試しに、 インデックスの生成を RAMDirectory を使った処理にまず置き換えてみた。 すると、 当然だが大量のインデックスを作るときに、 うまくやらないと大量にメモリを使うことになる。

この時点でまだスレッドは単一である。 ターゲットになっているファイルは約60万あって、 これを約5万個に分けてインデックスを別々に作ってみると、 全て生成するのに、約 174分かかった。

この結果、15個の index に分かれた状態になっている。 Lucene は複数 index を同時検索することができるから、 このままでも使えるのだが、 試しに、1つの index にマージしてみた。 これには約 30 分かかった。 処理時間を見ていると、 最終結果になるインデックスに1つずつインデックスを追加していくため、 後になればなるほど時間がかかっている。

高速化する余力があるなら、 このあたり(60万ファイル / 200分) を一つの目安にすればいいと思う。 もちろん、状況によってこの時間は大きく変化すると思われる。 今回使ったマシンは Fedora 6 (linux)、 CPU が Pentium 4 2.8MHz、 メモリが 1GB、 という環境である。

index を生成するときに、 CJKAnalyzer を使ったが、 たまに MaxFieldLength をオーバーしているようだ。 この値はデフォルトで 10,000 なのだが、 10,000 だとちょっと少ないと思ったので 30,000 に増やしたのだが、 まだ足りなかった。

このメッセージを出すには、 IndexWriter の setInfoStream メソッドを使って、 例えば次のようにすればいい。 ただし、こうすると他の情報も大量に出てくる。

    indexWriter.setInfoStream(System.out);

2006年12月27日

tomcat で OutOfMemory が発生

未検証ですが、これで試してみてください。

\usr\local\jiro\search\setpath.bat に次の行を追加する。

set CATALINA_OPTS=-Xms256m -Xmx256m

linux の場合は、/usr/local/jiro/search/setpath.bash に同様の変更を行う。

JIRO_HOME=/usr/local/jiro
JAVA_HOME=$JIRO_HOME/jdk1.5
ANT_HOME=$JIRO_HOME/ant
CATALINA_OPTS="-Xms256m -Xmx256m"

CLASSPATH=.
CLASSPATH=$CLASSPATH:$ANT_HOME/lib/ant.jar
PATH=$JAVA_HOME/bin
PATH=$PATH:$ANT_HOME/bin
PATH=$PATH:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
export JAVA_HOME CLASSPATH PATH CATALINA_OPTS

開発製品

jirologos.gif

About 2006年12月

2006年12月にブログ「三田ブログ」に投稿されたすべてのエントリです。新しい順に並んでいます。

前のアーカイブは2006年11月です。

次のアーカイブは2007年01月です。

他にも多くのエントリがあります。メインページアーカイブページも見てください。

Powered by
Movable Type