刺身の上にたんぽぽ乗せる日記

プログラミングしたり、自販機の下に落ちてる小銭を集めたりしてます

apache solrでまじめな検索つくるよー!

なんだかんだで結構自分でも使ったりするので、真面目に作ることにしてみた。
一応目標は、

  • 日付でソートされた結果
  • ブックマーク数によりフィルタリング
  • 日付・ブックマーク数を利用した重み付け付きの検索結果

が出せればいいと思ってる。最後のはどういう式を使えばいいのかわかんないから、そのうち考えるかな、という感じだけど。

mysqlというかtritonnのデータを読み込んでindex作るところまでできた。
使ったのはapache-solr-3.1.0。
以下参考にした記事。

MySQLからインポート
http://ochien.seesaa.net/article/153191074.html

HTMLタグを削除する
http://wiki.apache.org/solr/DataImportHandler#HTMLStripTransformer

テキストを日本語でindex
http://d.hatena.ne.jp/knaka20blue/20080813/1218615351

日付が近いものをブースト
http://d.hatena.ne.jp/knaka20blue/20081210/1228898458

あと、tritonnで記事タイトル、サイト名、内容でindexして、一括でmatchできるようにしてたけど、これと同じ事やろうと思うときは、fieldを別に一つ定義して、そこにcopyFieldでつなげられる。



...







tritonn導入しようとした時はすんげー大変だったきがして、今見てみたけど大変そうだった。
http://d.hatena.ne.jp/kudzu/20100815/1281848093
http://d.hatena.ne.jp/kudzu/20100815/1281887444
Luceneって面倒くさそう、って思ってたけど、solrのことを最初から知ってればこんなに苦労しなかったのになぁ...。

追記の色々作業メモ

日付の範囲選択は、

date:[2011-05-01T00:00:00.000Z TO 2011-05-02T00:00:00.000Z]

でできる。ここでTOを小文字にしてエラーがでて困った。
同様にスコアによるフィルタも

score:[10 TO 1000000000]

みたいなのでできる。range query万歳!
日付はpdateとtdateがあって、dateを指定するとtdateになるけど、2chnaviみたいにバックログを年・月で検索することがあるのであれば、trieを使ってるtdateほうが多分速い。
intも同様にtintとpintがある。今のところは検索とバックログだけ移行する予定だから、こっちはtintにする必要はないんだけど、速度によっては考えたほうがいいかもしれないので、とりあえずtintにしてみた。

まだ5月分のデータでしか使ってない上、macbook proのメモリがサーバの8倍くらいあるので、実際の環境でどれだけ動くのかは割と不安。うーん。

あと、今気づいたけど、板・カテゴリの部分が、SQLだと相当効率が悪いので、ここはindexしたほうがいい気がしてきた。
板とカテゴリがm:nの変態な関係なので、記事一つに対して、カテゴリ一覧を取得するためにもう一度クエリ投げ直すのが時間がかかりすぎる。どちらかというとクライアント側から投げるクエリでうまく対処するほうが正しいっぽい。というかSQLで普通に解決できる方法を思いついた。

さらに追加作業メモ

クライアント側は
http://code.google.com/p/solr-php-client/
を使う。

http://code.google.com/p/solr-php-client/wiki/ExampleUsage
のコードを試してみたけど、割とすんなり動いてびびる。
一応つまづいたところは一箇所だけあって、value側にmultivalueがあったので、それをhtmlspecialcharしようとして、こけてたので、var_dumpに変えるだけで大丈夫だった。

さらに追記

UbuntuJavaをインストール
http://d.hatena.ne.jp/Yoshiori/20100505/1273040380

別にどれでもいいんだろうけど。
あと、なんかmacからコピーしたせいか._*.jarが大量で生成されてて、読み込み時にこけまくってた。

find ./|grep /\\._*|xargs rm

でまとめて消した。

毎回fullimportすると時間かかりそうだから、delta importで差分だけ読み込む。

mysqlのテーブルにtimestamp型のカラムを作成する
http://blog.s21g.com/articles/1277

テーブルが巨大だから、永遠と時間がかかる。勘弁して欲しい。あと、このtimestampをベースに差分をとるから、index作ったほうがいい。これまた時間かかりそう。

solr側では、deltaQueryで差分を取得するためのクエリーを指定する。
http://d.hatena.ne.jp/bowez/20100405#p2
上で新たに作成したカラムのタイムスタンプがsolr側の前回のindex更新タイムスタンプより新しければ読み込む、という感じでいいんだと思う。

select * from source where timestamp >= '${dataimporter.last_index_time}'

まだ試してないけど。

現在HDDがいっぱいなので、色々開放中。クリックスルーのデータがやたらでかいので、それを圧縮中。

solr側から必要なカラムだけしか取らないにはfl=パラメータを使う。
http://ja.w3support.net/index.php?db=so&id=264907

PHP側からはこれでいいのかね?。なんか普通にインターフェース用意してくれてもいい気がするけど。
http://code.google.com/p/solr-php-client/wiki/FAQ#How_Can_I_Use_Additional_Parameters_(like_fq,_facet,_etc)

ソートは、flと同様追加パラメータのsortに"fieldname desc"とか書けばok。

dbからもう少し多めにデータとろうと思ったら、

SEVERE: Ignoring Error when closing connection
com.mysql.jdbc.PacketTooBigException: Packet for query is too large (3948066 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.

これは/etc/my.cnfとか/etc/mysql/my.cnfとかのmysqldのセクションにmax_allowed_packetの行を追加して、必要なだけ大きくすればいい。

SEVERE: Full Import failed:org.apache.solr.handler.dataimport.DataImportHandlerException: java.lang.OutOfMemoryError: Java heap space

http://wiki.apache.org/solr/DataImportHandlerFaq#I.27m_using_DataImportHandler_with_a_MySQL_database._My_table_is_huge_and_DataImportHandler_is_going_out_of_memory._Why_does_DataImportHandler_bring_everything_to_memory.3F

batchSizeを-1にすればok。


とりあえず今年のデータは読み込み終了。incrementalでデータ読み込もうと思っていたけど、冷静に考えるとアップデートされるのはせいぜい一週間分だから、一週間分だけ調べればいいのかね?
あと、差分のインポートはdeltaQueryだけど、ただインクリメンタルにやりたいだけなら、clean=falseをパラメータに渡せば普通にできるっぽい。

solr側で日付のカラムに何故かテキストを突っ込もうとしていて、何故かよく考えてみると丁度importしながらmysql側でimportしているテーブルをalter tableしていたなぁ、ということに気づいた。
ちなみに、

select * from aaa;

という風にselectしている場合、*に対応するカラムが全てキャッシュされているのかわからないけど、*の代わりに全部カラムを書き出さないと正しく動かなかった。

DeltaImportQueryの例が混乱を招くんだけど、
http://wiki.apache.org/solr/DataImportHandler#Using_delta-import_command
別にdeltaQueryでidを取得して、deltaImportQueryでそのidをベースにデータを引っ張る必要はなさそう。

The deltaImportQuery gives the data needed to populate fields when running a delta-import
The deltaQuery gives the primary keys of the current entity which have changes since the last index time

つまり、

deltaQuery="select id from table where date > '${dataimporter.last_index_time}'"
deltaQuery="select * from table where date > '${dataimporter.last_index_time}'"

書くだけでも問題ないはず。書いたら、多分アップデートする件数^2の処理が発生している感じがする。delta.idを使って一行一行インポートする手法の場合、クエリ数が2nになったけど、比較的高速に終わる*1

deltaの読み込みはすぐ終わるのに、Building documentsが始まってからが長くて、8分くらいかかった。
どうやらoptimize=falseをつけないとindexの最適化が発生して、すごく時間がかかっているらしい。デフォルトでoffにしろよ、と思うけど、一応最適化はこまめにすべきなのかね?


solrのqueryをエスケープするPHP関数
http://e-mats.org/2010/01/escaping-characters-in-a-solr-query-solr-url/


ブックマーク数でフィルタするのは、単純に(bookmarks:[n TO *])をクエリに追加するだけでok。

*1:4164件の変更処理で、クエリ自体は1分かからず終了