Lucandraを使ってみる

5,235 views

Published on

Published in: Technology
0 Comments
5 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
5,235
On SlideShare
0
From Embeds
0
Number of Embeds
18
Actions
Shares
0
Downloads
24
Comments
0
Likes
5
Embeds 0
No embeds

No notes for slide

Lucandraを使ってみる

  1. 1. Lucandra を使ってみる 2010/6/25 佐藤 史彦
  2. 2. Agenda Lucandra ってなに? Lucandra の構成 できること 使ってみる まとめ
  3. 3. Lucandra ってなに?
  4. 4. A Cassandra-based Lucene backend Author : Jake Luciani
  5. 5. カサンドラベースのルシーンバックエンド 作者 : ジェイク ルシアーニ
  6. 6. Cassandra にインデックス機能を 追加する、というより Lucene/Solr のインデックスを リアルタイムに作成、かつ 手軽にスケールさせる目的で インデックスのストア先に Cassandra を採用したもの
  7. 7. 実装例 http://sparse.ly/
  8. 8. Lucandra の構成
  9. 9. Lucene Disk Java Application Hits Document Document Document Field Field Field インデックス作成 QueryParser Document Document Document 検索 Analyzer Query Lucene Index IndexReader IndexWriter Analyzer IndexSearcher
  10. 10. Luc andra Cassandra Java Application Hits Document Document Document Field Field Field インデックス作成 QueryParser Document Document Document 検索 Analyzer Query Lucene Index IndexReader IndexWriter Analyzer IndexSearcher
  11. 11. 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 : 当該文書中の当該単語の出現位置
  12. 12. できること
  13. 13. 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 では不可
  14. 14. 現状できないこと You can't walk the documents with index reader. 現状遅いこと Indexes with many documents and very dense terms.
  15. 15. 使ってみる
  16. 16. 環境 Cassandra は 0.6.2 ( 単体 )
  17. 17. ビルド 下記より tar ball を DL します http://github.com/tjake/Lucandra 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
  18. 18. storage-conf.xml の差し替え storage-conf.xml を差し替えて Cassandra を立ち上げます ※ Cassandra のデータが空である前提 $ cp config/storage-conf.xml ¥ /usr/local/cassandra/conf/ $ /usr/local/cassandra/bin/cassandra
  19. 19. storage-conf.xml のポイント <Keyspace Name=&quot;Lucandra&quot; > <ColumnFamily CompareWith=&quot;BytesType&quot; Name=&quot;Documents&quot; KeysCached=&quot;10%&quot; /> <ColumnFamily ColumnType=&quot;Super&quot; CompareWith=&quot;BytesType&quot; CompareSubcolumnsWith=&quot;BytesType&quot; Name=&quot;TermInfo&quot; KeysCached=&quot;10%&quot; /> : : </Keyspace> <Partitioner> org.apache.cassandra.dht.OrderPreservingPartitioner </Partitioner> クラスタノードでは InitialToken も適切に設定すべき
  20. 20. Demo(BookmarksDemo) を試す Cassandra Bookmarks Demo Hits Document Document Document Field:url Field:title Field:tags -index QueryParser Document Document Document -search SimpleAnalyzer Query Lucene Index IndexReader IndexWriter SimpleAnalyzer IndexSearcher TSV File
  21. 21. 動作確認 $ ./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
  22. 22. ひとまず動作することが確認できたので、日本語のサンプルを作ってみる。
  23. 23. サンプルデータ 某飲食店検索 API を使ってこの近辺のデータを 980 件、 TSV にしておく id docID, INDEX, STORE name INDEX(ANALYZED), STORE url STORE address INDEX(ANALYZED), STORE tel INDEX(ANALYZED), STORE budget INDEX, STORE
  24. 24. サンプルプログラム BookmarksDemo をコピーして、 下記の変更を加えます * Analyzer を変更 SimpleAnalyzer -> CJKAnalyzer * インデックス名を変更 bookmarks -> shopsearch * ドキュメントフィールドを  サンプルデータにあわせて変更
  25. 25. サンプル実行 $ ./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:&quot; 丸の の内 &quot; 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 115 12: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 115 12:08:03,925 DEBUG LucandraTermEnum:246 - name の勘 has 2
  26. 26. サンプル実行 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) あれ? ヒットしない。。。 (途中まではいい感じにみえるけど)
  27. 27. 要因調査 CJKAnalyzer を使用した場合、 QueryParser.parse() は CJK 文字列を bi-gram に分割した Query を返却する name: 丸の内  ↓ name:&quot; 丸の の内 &quot;
  28. 28. 要因調査 この際の Query は、 PhraseQuery の インスタンスになっている PhraseQuery が使用される場合、 LucandraTermDocs.nextPosition() が うまく機能しない (?) ためか、 Hit した ドキュメントが抽出できていない
  29. 29. 要因調査 そこが問題のようだが、つっこんで 調査しないと影響範囲とか読めない ので、回避方法を検討。。。 解明しました。 詳細は付け足し資料 ( 補足編 ) にて。
  30. 30. 回避方法 そもそもなぜ bi-gram が PhraseQuery として扱われるのかを調べていたら、 下記の情報がありました 関口宏司の Lucene ブログ http://lucene.jugem.jp/?cid=5
  31. 31. 回避方法 これによると、 Lucene3.1 からは Analyzer により複数の単語が生成される場合、 PhraseQuery が生成される仕様を BooleanQuery に変えるべし と提案されており、 patch が提供されている
  32. 32. 回避方法 このパッチを強引にも 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
  33. 33. 回避方法 このままではビルドが通らないので 下記 2 ファイルを Lucene の レポジトリからとってきます org/apache/lucene/util/ Version.java VirtualMethod.java ※ メソッドのバージョニング関連クラスで  本処理にはあまり影響なさそう?
  34. 34. 回避方法 パッチのあたったソースは Java5 以降の記述になっているため、 javac の オプションを変更してビルドします common-build.xml: 61: <property name=&quot;javac.source&quot; value=&quot; 6 &quot;/> 62: <property name=&quot;javac.target&quot; value=&quot; 6 &quot;/> 63: 64: <property name=&quot;javadoc.link&quot; value=&quot;http://java.sun .com/ javase / 6 /docs/api/&quot;/> $ ant
  35. 35. 回避方法 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, &quot;name&quot;, analyzer); qp.setDefaultOperator( Operator.AND );
  36. 36. $ ./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
  37. 37. Search matched: 115 item(s) 09:52:16,739 DEBUG IndexReader:293 - Document read took: 10ms 1. 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 : :
  38. 38. おお、なんかできてるっぽい
  39. 39. ソートも試してみる sort オプションを指定した場合に、 IndexSearcher.search() メソッドにて budget( 予算 ) フィールド値の降順で ソートされるようにしてみます 動かしてみます ☞ ShopSearchDemo.java: TopDocs docs = indexSearcher.search(q, null, 10, new Sort(new SortField(&quot;budget&quot;, SortField.INT, true)));
  40. 40. $ ./run_shop.sh -search name: 丸の内 sort Search matched: 115 item(s) 09:59:17,396 DEBUG IndexReader:293 - Document read took: 9ms 1. レストラン モナリザ 丸の内店 ~丸ビル~ http://r.gnavi.co.jp/g763345/ ¥10000 09:59:17,398 DEBUG IndexReader:293 - Document read took: 1ms 2. センチュリーコート丸の内 http://r.gnavi.co.jp/g038917/ ¥10000 09:59:17,399 DEBUG IndexReader:293 - Document read took: 1ms 3. Luxor 丸の内 http://r.gnavi.co.jp/g763393/ ¥9000 09:59:17,401 DEBUG IndexReader:293 - Document read took: 1ms 4. 丸の内 やんも http://r.gnavi.co.jp/g763373/ ¥8000 09:59:17,402 DEBUG IndexReader:293 - Document read took: 1ms 5. たまさか 丸の内店 http://r.gnavi.co.jp/e533319/ ¥8000 09:59:17,403 DEBUG IndexReader:293 - Document read took: 1ms 6. ワインショップエノテカ丸の内 ザ・ラウンジ http://r.gnavi.co.jp/g763382/ ¥6300 09:59:17,405 DEBUG IndexReader:293 - Document read took: 1ms 7. 寿し屋の勘八 旬 ~丸の内~ http://r.gnavi.co.jp/g763366/ ¥6000 :
  41. 41. ソートされてるっぽい
  42. 42. まとめ
  43. 43. わかったこと 1 Lucandra は謳い文句通り Lucene の バックエンドに Cassandra を採用した ものであり、アプリケーションは Lucene の資産 (API) をほぼそのまま 利用することができる # PhraseQuery は要調査
  44. 44. わかったこと 2 Lucene の機能を十分に使うには、 OrderPreservingPartitioner を選択する必要がある Partitioner は現状 Cluster で共通であり RandomPartitoner のシンプルで効果的なデータ分散の恩恵を受けられないので、共用環境への導入は要検討
  45. 45. わかったこと 3 Cassandra の内部特性を利用することで インデックスの最適化を不要とし、 リアルタイム性を高める構造である Twitter クライアントや RSS リーダーのような、ユーザーごとにインデックスが分かれていて総データ量が多く、 即時に検索が必要な場面に向いていると思われる
  46. 46. わかったこと 4 当然だが、 Java でしか使えない 他環境では、同梱の Solrandra を使って HTTP で利用するのだろう Java でも SolrJ を使って Solr のインデックス管理、キャッシュ機構を利用するのがベターなのかも
  47. 47. 今後の課題 もう少し Lucene/Solr 勉強したら? Solrandra ベースでの実用性検証 データ量とパフォーマンス検証 RandomPartitioner での動作検証 PhraseQuery . . .
  48. 48. おしまい
  49. 49. 参考 ■ 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/
  50. 50. Lucandra を使ってみる  〜補足編〜 PhraseQuery を調べました ... 2010/7/15 佐藤 史彦
  51. 51. 前回のあらすじ <ul><li>Lucandra とは、 Lucene の使い勝手はそのままで Index を Cassandra に格納するようにしたもの </li></ul><ul><li>Lucandra では CJKAnalyzer (のように複数単語が生成される Analyzer )を使ってパースされる PhraseQuery ではうまく検索ができなかった。 </li></ul><ul><li>PhraseQuery ではなく BooleanQuery を生成する パッチを Lucene にあてて、現象を回避した。 </li></ul>
  52. 52. でもなんかひっかかる ... <ul><li>実装例の http://sparse.ly/ では問題なさそう。 ( 自前でクエリをパースしているとも思えないし ) </li></ul><ul><li>solrandra ( Solr の実装)に持ってったら なんだかうまくいかない。。 </li></ul><ul><li>Lucandra を見直す方が早い気がしてきた。。。 </li></ul>
  53. 53. ということでソースを追いかけ ... TermInfo (転置インデックス)に Position (単語の出現位置)がないと PhraseQuery が機能しない ことがわかりました。 ※ Position を記録するには、インデクシング時に  指定する必要がある。 ※ 本家 Lucene はなくてもいけるのに ...
  54. 54. 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 : 当該文書中の当該単語の出現位置 コレ
  55. 55. で、どうする? <ul><li>Lucandra の場合 (.java) </li></ul><ul><li>Solrandra の場合 (schema.xml) </li></ul>doc.add(new Field(&quot;name&quot;, name, Store. YES , Index. ANALYZED , Field.TermVector. WITH_POSITIONS )); <field name= &quot;name&quot; type= &quot;text_cjk&quot; indexed= &quot;true&quot; stored= &quot;true&quot; termPositions= &quot;true&quot; /> インデックス生成時に Position s を指定する
  56. 56. 実行結果 ( 一部省略 ) $ ./run_shop -index data.tsv $ ./run_shop.sh -search name: 丸の内 name:&quot; 丸の の内 &quot; 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
  57. 57. 無事、検索できました <ul><li>前回、もうちょっとがんばっていれば、 こちらのほうが解決が早かったような気がする。 </li></ul><ul><li>嫌な予感がしてもソースに立ち向かおう。 </li></ul>以上です。

×