Lucandra を使ってみ
る
2010/6/25
佐藤 史彦
Agenda
Lucandra ってなに?
Lucandra の構成
できること
使ってみる
まとめ
Lucandra ってなに?
A Cassandra-based Lucene backend
Author : Jake Luciani
カサンドラベースのルシーンバックエン
ド
作者 : ジェイク ルシアーニ
Cassandra にインデックス機能
追加する、というより を
Lucene/Solr のインデックスを
リアルタイムに作成、かつ
手軽にスケールさせる目的で
インデックスのストア先に
Cassandra を採用したもの
実装例 http://sparse.ly/
Lucandra の構成
Disk
Java
Application
HitsHits DocumentDocument Document Document
Lucene
Document Document
Field Field
Field Field
Field Field
インデックス作
成
QueryParser QueryParser Document
Document Document Document
Document Document
検索
Analyzer Analyzer
Query Query
Lucene Index Lucene Index IndexReader
IndexReader
IndexWriter IndexWriter Analyzer
Analyzer IndexSearcher
IndexSearcher
Cassandra
Java
Application
HitsHits DocumentDocument Document Document
Lucandra
Document Document
Field Field
Field Field
Field Field
インデックス作
成
QueryParser QueryParser Document
Document Document Document
Document Document
検索
Analyzer Analyzer
Query Query
Lucene Index Lucene Index IndexReader
IndexReader
IndexWriter IndexWriter Analyzer
Analyzer IndexSearcher
IndexSearcher
Index 構成
Keyspace : Lucandra
ColumnFamily : Document
Key : インデックス名のハッシュ + ドキュメント ID Column Name : フィールド名
Value : フールド値 SuperColumnFamily : TermInfo
Key :( インデックス名 + フィールド名 ) のハッシュ + フィールド名 + 単語SuperColumn : ドキュメント ID
Column Name :Frequencies
Value : 当該文書中の当該単語の出現頻度 Column Name :Norms
Value : 当該単語における文書のノルム Column Name :Offsets
Value : 当該文書中の当該単語のバイト位置オフセット Column Name :Position
Value : 当該文書中の当該単語の出現位置
できること
README より
1 Real-Time indexing
(documents become available almost
immediately)
2 No optimizing
3 Search
4 Sort
5 Range Queries
6 Delete
7 Wildcards and other Lucene magic
8 Faceting/Highlighting
4,5,7 -> RandomPartitioner では不可
現状できないこと
You can't walk the documents with
index reader.
現状遅いこと
Indexes with many documents and
very dense terms.
使ってみる
環境
Cassandra は 0.6.2 ( 単体 )
ビルド
下記より tar ball を DL します
http://github.com/tjake/Lucand
ra
ant で lucandra.jar をビルドしま
す
対応バージョン
Lucene-2.9.1, Cassandra-0.6
$ tar xztf ls tjake-Lucandra-c632677.tar.gz
$ cd tjake-Lucandra-c632677
$ ant lucandra.jar
storage-conf.xml の差し替え
storage-conf.xml を差し替えて
Cassandra を立ち上げます
※Cassandra のデータが空である前
提
$ cp config/storage-conf.xml ¥
/usr/local/cassandra/conf/
$ /usr/local/cassandra/bin/cassandra
storage-conf.xml のポイント
<Keyspace Name="Lucandra">
<ColumnFamily CompareWith="BytesType" Name="Documents" KeysCached="10%" />
<ColumnFamily ColumnType="Super" CompareWith="BytesType" CompareSubcolumnsWith="BytesType"
Name="TermInfo" KeysCached="10%" /> :
:
</Keyspace>
<Partitioner>
org.apache.cassandra.dht.OrderPreservingPartitioner
</Partitioner>
クラスタノードでは InitialToken も適切に設定すべき
Cassandra
Bookmarks
Demo
HitsHits DocumentDocument Document Document
Demo(BookmarksDemo) を試す
Document Document
Field:url Field:url Field:title Field:title Field:tags Field:tags
-index
QueryParser QueryParser Document
Document Document Document
Document Document
-search
SimpleAnalyzer SimpleAnalyzer
Query Query
Lucene Index Lucene Index IndexReader
IndexReader
IndexWriter IndexWriter SimpleAnalyzer SimpleAnalyzer IndexSearcher
IndexSearcher
TSV File TSV File
動作確認
$ ./run_demo.sh -index bookmark.tsv
$ ./run_demo.sh -search title:linu* Search matched: 5 item(s)
1. ZFS on FUSE/Linux
http://zfs-on-fuse.blogspot.com/
2. Set Up Postfix For Relaying Emails Through Another Mailserver | HowtoForge - Linux Howtos and Tutorials
http://www.howtoforge.com/postfix_relaying_through_ another_mailserver
3. Debian GNU/Linux System Administration Resources http://www.debian-administration.org/
4. Linux Scalability
http://www.cs.wisc.edu/condor/condorg/linux_scalabi lity.html
5. LinuxDevCenter.com -- Cache-Friendly Web Pages
http://www.linuxdevcenter.com/pub/a/linux/2002/02/ 28/cachefriendly.html
ひとまず動作することが確認できた
ので、日本語のサンプルを作ってみ
る。
サンプルデータ
某飲食店検索 API を使ってこの近辺の
データを 980 件、 TSV にしておく
id docID, INDEX, STORE
name INDEX(ANALYZED), STORE
url STORE
address INDEX(ANALYZED), STORE
tel INDEX(ANALYZED), STORE
budget INDEX, STORE
サンプルプログラム
BookmarksDemo をコピーして、
下記の変更を加えます
* Analyzer を変更
SimpleAnalyzer →
CJKAnalyzer
* インデックス名を変更
bookmarks → shopsearch
* ドキュメントフィールドを
サンプルデータにあわせて変更
サンプル実行
$ ./run_shop.sh -index data.tsv
$ ./run_shop.sh -search name: 丸の内
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
12:08:03,436 INFO CassandraProxyClient:145 - Connected to cassandra at 127.0.0.1:9160
name:" 丸の の内 "
12:08:03,863 DEBUG LucandraTermEnum:237 - Found 2 keys in range:OxSo2Td8name 丸の to in 95ms
12:08:03,863 DEBUG LucandraTermEnum:246 - name 丸の has 11512:08:03,869 DEBUG LucandraTermEnum:246 - name 丸ノ has 10 12:08:03,871 DEBUG LucandraTermEnum:285 - loadTerms:
OxSo2Td8name 丸の (3) took 103ms
12:08:03,872 INFO IndexReader:153 - docFreq() took: 232ms
12:08:03,872 INFO IndexReader:153 - docFreq() took: 232ms
12:08:03,916 DEBUG LucandraTermEnum:237 - Found 2 keys in range:OxSo2Td8name の内 to in 43ms
12:08:03,916 DEBUG LucandraTermEnum:246 - name の内 has 11512:08:03,925 DEBUG LucandraTermEnum:246 - name の勘 has 2
サンプル実行
12:08:03,927 DEBUG LucandraTermEnum:285 - loadTerms: OxSo2Td8name の内 (3) took 54ms
12:08:03,927 INFO IndexReader:153 - docFreq() took: 54ms 12:08:03,947 DEBUG LucandraTermEnum:176 - Found
OxSo2Td8name 丸の in cache
12:08:03,953 DEBUG LucandraTermEnum:176 - Found OxSo2Td8name の内 in cache
Search matched: 0 item(s)
あれ? ヒットしない。。。
(途中まではいい感じにみえるけど)
要因調査
CJKAnalyzer を使用した場合、
QueryParser.parse() は CJK 文字
列を bi-gram に分割した Query を返却
する
name: 丸の内
↓ name:" 丸の の内 "
要因調査
この際の Query は、 PhraseQuery
の インスタンスになっている
PhraseQuery が使用される場合、
LucandraTermDocs.nextPositio
n() が
うまく機能しない (?) ためか、 Hit
した ドキュメントが抽出できていない
要因調査
そこが問題のようだが、つっこんで
調査しないと影響範囲とか読めない
ので、回避方法を検討。。。
解明しました。
詳細は付け足し資料 ( 補足編 ) にて。
回避方法
そもそもなぜ bi-gram が
PhraseQuery
として扱われるのかを調べていた
ら、 下記の情報がありました
関口宏司の Lucene ブログ
http://lucene.jugem.jp/?cid=5
回避方法
これによると、 Lucene3.1 からは
Analyzer により複数の単語が生成さ
れる場合、 PhraseQuery が生成さ
れる仕様を BooleanQuery に変え
るべし
と提案されており、
patch が提供されている
回避方法
このパッチを強引にも 2.9.1 にあて
ます
$ tar xzf lucene-2.9.1.tar.gz$ cd lucene-2.9.1
$ curl -O
https://issues.apache.org/jira/secure/attachment /12445136/LUCENE-2458.patch
$ patch -b -p1 < LUCENE-2458.patch
回避方法
このままではビルドが通らないので
下記 2 ファイルを Lucene の
レポジトリからとってきます
org/apache/lucene/util/
Version.java
VirtualMethod.java
※ メソッドのバージョニング関連クラスで
本処理にはあまり影響なさそう?
回避方法
パッチのあたったソースは Java5 以
降の記述になっているため、 javac
の オプションを変更してビルドします
common-build.xml:61: <property name="javac.source" value="6"/> 62: <property name="javac.target" value="6"/> 63: 64: <property name="javadoc.link"
value="http://java.sun
.com/javase/6/docs/api/"/>
$ ant
回避方法
build/lucene-core-2.9.1-dev.jar
を Lucandra の lib/lucene-core-
2.9.1.jar と
差し替えます
QueryParser のデフォルトオペ
レータを AND にして、再チャレンジ!!
ShopSearchDemo.java: QueryParser qp =
new QueryParser(Version.LUCENE_CURRENT, "name", analyzer);
qp.setDefaultOperator( Operator.AND );
$ ./run_shop.sh -search name: 丸の内
Picked up _JAVA_OPTIONS: -Dfile.encoding=UTF-8
12:08:03,436 INFO CassandraProxyClient:145 - Connected to cassandra at 127.0.0.1:9160
+name: 丸の +name: の内
18:03:39,127 DEBUG LucandraTermEnum:237 - Found 2 keys in range:OxSo2Td8name 丸の to in 109ms
18:03:39,127 DEBUG LucandraTermEnum:246 - name 丸の has 115 18:03:39,128 DEBUG LucandraTermEnum:246 - name 丸ノ has 10 18:03:39,130 DEBUG LucandraTermEnum:285 - loadTerms:
OxSo2Td8name 丸の (3) took 112ms
18:03:39,131 INFO IndexReader:153 - docFreq() took: 222ms 18:03:39,189 DEBUG LucandraTermEnum:237 - Found 2 keys in range:OxSo2Td8name の内 to in 57ms
18:03:39,189 DEBUG LucandraTermEnum:246 - name の内 has 115 18:03:39,190 DEBUG LucandraTermEnum:246 - name の勘 has 2 18:03:39,190 DEBUG LucandraTermEnum:285 - loadTerms:
OxSo2Td8name の内 (3) took 58ms
18:03:39,190 INFO IndexReader:153 - docFreq() took: 59ms
18:03:39,196 DEBUG LucandraTermEnum:176 - Found OxSo2Td8name 丸の in cache
18:03:39,202 DEBUG LucandraTermEnum:176 - Found OxSo2Td8name の内 in cache
Search matched: 115 item(s)
09:52:16,739 DEBUG IndexReader:293 - Document read took: 10ms1. Luxor 丸の内
http://r.gnavi.co.jp/g763393/ ¥9000
09:52:16,741 DEBUG IndexReader:293 - Document read took: 1ms 2. the Pantry 丸の内店
http://r.gnavi.co.jp/g763381/ ¥1300
09:52:16,743 DEBUG IndexReader:293 - Document read took: 1ms 3. MAISON・BARSAC 丸の内
http://r.gnavi.co.jp/g763375/ ¥5500
09:52:16,750 DEBUG IndexReader:293 - Document read took: 2ms 4. 丸の内 やんも
http://r.gnavi.co.jp/g763373/ ¥8000
09:52:16,751 DEBUG IndexReader:293 - Document read took: 1ms 5. Vinpicoeur ~丸の内~
http://r.gnavi.co.jp/g763372/ ¥3500
09:52:16,753 DEBUG IndexReader:293 - Document read took: 1ms 6. DEAN&DELUCA ~丸の内~
http://r.gnavi.co.jp/g763365/ ¥1500
09:52:16,755 DEBUG IndexReader:293 - Document read took: 2ms 7. S.Stefano ~丸の内~
http://r.gnavi.co.jp/g763359/ ¥4500 : :
おお、なんかできてるっぽい
ソートも試してみる
sort オプションを指定した場合に、
IndexSearcher.search() メソッド
にて budget( 予算 ) フィールド値の降順
で ソートされるようにしてみます
動かしてみます ☞
ShopSearchDemo.java:
TopDocs docs = indexSearcher.search(q, null, 10,
new Sort(new SortField("budget", SortField.INT, true)));
$ ./run_shop.sh -search name: 丸の内 sort Search matched: 115 item(s)
09:59:17,396 DEBUG IndexReader:293 - Document read took: 9ms1. レストラン モナリザ 丸の内店 ~丸ビル~
http://r.gnavi.co.jp/g763345/ ¥10000
09:59:17,398 DEBUG IndexReader:293 - Document read took: 1ms2. センチュリーコート丸の内
http://r.gnavi.co.jp/g038917/ ¥10000
09:59:17,399 DEBUG IndexReader:293 - Document read took: 1ms3. Luxor 丸の内
http://r.gnavi.co.jp/g763393/ ¥9000
09:59:17,401 DEBUG IndexReader:293 - Document read took: 1ms4. 丸の内 やんも
http://r.gnavi.co.jp/g763373/ ¥8000
09:59:17,402 DEBUG IndexReader:293 - Document read took: 1ms5. たまさか 丸の内店
http://r.gnavi.co.jp/e533319/ ¥8000
09:59:17,403 DEBUG IndexReader:293 - Document read took: 1ms6. ワインショップエノテカ丸の内 ザ・ラウンジ
http://r.gnavi.co.jp/g763382/ ¥6300
09:59:17,405 DEBUG IndexReader:293 - Document read took: 1ms7. 寿し屋の勘八 旬 ~丸の内~
http://r.gnavi.co.jp/g763366/ ¥6000 :
ソートされてるっぽい
まとめ
わかったこと 1
Lucandra は謳い文句通り Lucene
の バックエンドに Cassandra を採用
した ものであり、アプリケーションは
Lucene の資産 (API) をほぼそのま
ま 利用することができる
# PhraseQuery は要調査
わかったこと 2
Lucene の機能を十分に使うには、
OrderPreservingPartitioner を選
択する必要がある
Partitioner は現状 Cluster で共通
であり RandomPartitoner のシンプルで
効果的なデータ分散の恩恵を受けら
れないので、共用環境への導入は要
検討
わかったこと 3
Cassandra の内部特性を利用するこ
とで インデックスの最適化を不要とし、
リアルタイム性を高める構造である
Twitter クライアントや RSS リー
ダーのような、ユーザーごとにイン
デックスが分かれていて総データ量
が多く、 即時に検索が必要な場面に向いてい
ると思われる
わかったこと 4
当然だが、 Java でしか使えない
他環境では、同梱の Solrandra を
使って HTTP で利用するのだろう
Java でも SolrJ を使って Solr のイ
ンデックス管理、キャッシュ機構を
利用するのがベターなのかも
今後の課題
もう少し Lucene/Solr 勉強したら?
Solrandra ベースでの実用性検証
データ量とパフォーマンス検証
RandomPartitioner での動作検証
PhraseQuery . . .
おしまい
参考
■ A Cassandra-based Lucene backend
http://blog.sematext.com/2010/02/09/lucandra-a-cassandra-based-
lucene-backend/
■ slideshare - Lucandra
http://www.slideshare.net/otisg/lucandra
■ Cassandra: RandomPartitioner vs OrderPreservingPartitioner
http://ria101.wordpress.com/2010/02/22/cassandra-
randompartitioner-vs-orderpreservingpartitioner/
■ 関口宏司の Lucene ブログ
http://lucene.jugem.jp/
Lucandra を使ってみる 〜補足
編〜
PhraseQuery を調べました ...
2010/7/15
佐藤 史彦
前回のあらすじ
➲
Lucandra とは、 Lucene の使い勝手はそのまま
で Index を Cassandra に格納するようにしたも
の
➲
Lucandra では CJKAnalyzer (のように複数単語
が生成される Analyzer )を使ってパースされる
PhraseQuery ではうまく検索ができなかった。
➲
PhraseQuery ではなく BooleanQuery を生成す
る パッチを Lucene にあてて、現象を回避した。
でもなんかひっかかる ...
➲
実装例の http://sparse.ly/ では問題なさそう。
( 自前でクエリをパースしているとも思えない
し )
➲
solrandra ( Solr の実装)に持ってったら
なんだかうまくいかない。。
➲
Lucandra を見直す方が早い気がしてきた。。。
ということでソースを追いかけ ...
TermInfo (転置インデックス)に
Position (単語の出現位置)がないと
PhraseQuery が機能しない
ことがわかりました。
※Position を記録するには、インデクシング時に
指定する必要がある。
※ 本家 Lucene はなくてもいけるのに ...
Keyspace : Lucandra
ColumnFamily : Document
Key : インデックス名のハッシュ + ドキュメント ID Column Name : フィールド名
Value : フールド値 SuperColumnFamily : TermInfo
Key :( インデックス名 + フィールド名 ) のハッシュ + フィールド名 + 単語SuperColumn : ドキュメント ID
Column Name :Frequencies
Value : 当該文書中の当該単語の出現頻度 Column Name :Norms
Value : 当該単語における文書のノルム Column Name :Offsets
Value : 当該文書中の当該単語のバイト位置オフセット Column Name :Position
Value : 当該文書中の当該単語の出現位置
Index 構成(前回資料より抜粋)
コレ コレ
で、どうする?
➲ Lucandra の場合 (.java)
➲ Solrandra の場合 (schema.xml)
doc.add(new Field("name", name,
Store.YES,
Index.ANALYZED,
Field.TermVector.WITH_POSITIONS));
<field name="name" type="text_cjk"
indexed="true" stored="true"
termPositions="true" />
インデックス生成時に Positions を指定す
る
実行結果 ( 一部省略 )
$ ./run_shop -index data.tsv
$ ./run_shop.sh -search name: 丸の内 name:" 丸の の内 "
Search matched: 115 item(s) 1. Luxor 丸の内
http://r.gnavi.co.jp/g763393/ ¥9000 2. the Pantry 丸の内店
http://r.gnavi.co.jp/g763381/ ¥1300 3. MAISON・BARSAC 丸の内
http://r.gnavi.co.jp/g763375/ ¥5500 4. 丸の内 やんも
http://r.gnavi.co.jp/g763373/ ¥8000 5. Vinpicoeur ~丸の内~
http://r.gnavi.co.jp/g763372/ ¥3500 6. DEAN&DELUCA ~丸の内~
http://r.gnavi.co.jp/g763365/ ¥1500 7. S.Stefano ~丸の内~
http://r.gnavi.co.jp/g763359/ ¥4500
無事、検索できました
➲
前回、もうちょっとがんばっていれば、
こちらのほうが解決が早かったような気がする。
➲