Sunspotではじめる
Solr入門
2013/08/15
baba@bps
1
はじめに
• 本資料は社内勉強会で使った資料です
• 一部ページや解説を追加しています
• Rails 4.0をベースに書いていますが、Rails 3.1 / 3.2でも動作確認
をしています
• 再配布等のご相談はbaba@bpsinc.jpまでお願いします
2
はじめに
• Railsは分かるけどSolrは全然分からない人向けです
• 対象
• Railsの検索を高速化したい!Sunspotが良いと聞いたけど何これ?
• Sunspotは試したことあるけどSolr分からないから設定を知りたい
• Sunspot使ったサービスを公開したいけど、サーバ設定が分からん
• 対象外
• Rails何それおいしいの
• データは30件しかありません
• 5億件のデータを分散処理する方法を探している
• 全文検索エンジンの比較データが欲しい
3
これは何?
• Sunspot
• SolrをRailsで使いやすくするgem
• Solr
• Luceneに管理画面やキャッシュなどのフロントエンドを追加したもの
• Lucene
• Apache Projectで開発されている全文検索エンジン
→ 大量データの検索を速くするやつ
4
これは違います
「Solrで始めるSunspot入門」
じゃないの?
• Railsの人にとっては、理屈よりさっさとSunspot動かす方が簡単
• その後にSolrを直接触ってみよう
5
Sunspotを使ってみる
6
Gemfile
# これが本体
gem ‘sunspot_rails’
# 自分でSolr入れるのめんどくさい
# これを入れておくとrakeコマンドでSolrを起動できる
gem ‘sunspot_solr’
7
app/models/user.rb
class User < ActiveRecord::Base
searchable do
text :email
text :fullname do
“#{first_name} #{last_name}”
end
boolean :is_admin
integer :age
# 他にもdateとかlatlonとか
# multiple: trueで配列も使える
end
end
8
app/controllers/users_controller.rb
def search
@search = User.search do
with(:is_admin, false)
with(:age).greater_than_or_equal_to 20
fulltext “太郎”
end
end
9
app/views/users/search.html.erb
<% @search.results.each do |user| %>
<%= user.email %>
<br>
<% end %>
10
Run
bundle
rails g sunspot_rails install
# => config/sunspot.ymlが生成されるだけ
rake sunspot:solr:start
rake sunspot:solr:reindex
11
速い?
12
どのくらい速い?
0
2000
4000
6000
8000
10000
12000
100件 1万件 100万件 1億件
MySQL
Solr
13
100件なら意味ない
1万件だと意味ある
100万件だと無いと死ぬ
なんで速い?
MySQL Where LIKE
• 先頭から順番に探す
• O(n)
Solr
• 転置インデックスで探す
• 索引
• O(log n)
14
Sunspotの動作
15
Sunspotの動作
save
① INSERT INTO users (email, age)
VALUES (‘test@example.com’, 30);
rails
sunspot ② POST /solr/mycore/update
ここでインデックス作る
Commitまですると結構重い
16
Sunspotの動作
search
② SELECT * FROM user
WHERE id IN (1,3);
rails
sunspot ① GET /solr/mycore/select
超速い
IDがとれる
17
実際に使うデータはDBから
※Storedを使うと、①のみで終わらせることもできる
About Solr
18
Solr
• Web API
• Web管理画面
• Javaサーブレットコンテナで動く
• dist/solr-4.4.0.war
• Tomcat, Jetty, …
19
Solrのバージョン
• 1.3
• マルチコア
• 分散検索
• 1.4
• Javaベースレプリケーション
• 3.1
• Luceneと統合されたのでバージョン飛んだ
• 4.0
• 管理画面が格好良く
• インデクシング高速化
• 4.4
• 現在の最新版 MANGA REBORN 1.3
この前やった某案件 4.3
20
3つの起動方法
• sunspot_solr
• お手軽
• version 1.3 (sunspot_solr 1.3)
• version 3.5 (sunspot_solr 2.0)
• パッケージインストール
• 管理しやすい
• root必要
• version 1.4
• apache.orgからダウンロード
• 自由度高い
• version 4.4.0
21
schema.xmlをSunspot用に書き換える必要あり
sunspot_solr
bundle install
rake sunspot:solr:start
=> default port:
8981(test), 8982(development), 8983(production)
=> config/data directory:
${RAILS_ROOT}/solr/conf
${RAILS_ROOT}/solr/data
実体はstart.jarを実行しているだけ
jettyが起動する
22
Ubuntu Package
sudo apt-get install solr-tomcat
sudo service tomcat6 start
=> default port: 8080
=> config/data directory:
/etc/solr/conf
/var/lib/solr/data
23
Download Archive
wget ftp://ftp.riken.jp/net/apache/lucene/solr/4.4.0/solr-4.4.0.tgz
tar xzf solr-4.4.0.tgz
cd solr-4.4.0/example
java –jar start.jar
=> default port: 8983
=> config/data directory:
solr-4.4.0/example/solr/conf
solr-4.4.0/example/solr/data
jettyが起動する
24
Download Archive
# 別の設定を使う
java –Dsolr.solr.home=multicore –jar start.jar
# 自分でちゃんとセットアップする
solr-4.4.0/dist/solr-4.4.0.war
25
管理画面
26
27
28
29
30
31
Ping
ステータスチェック(nagiosに便
利)
Query
検索を試せる
Analysis
保存されたデータの統計
Schema Browser
動的フィールドを含め確認
32
検索してみる
33
検索してみる
34
検索してみる
• すべてのAdminユーザを取得
• http://localhost:8983/solr/mytest/
select?q=*%3A*&fq=type%3AUser
&fq=is_admin_b%3A1&wt=json&i
ndent=true
• q
• *:*
• fq
• type:User
• is_admin_b:1
35
検索してみる
• 20歳未満
• q=*:*
• fq=type:User
• fq=age_i:{* TO 20}
• 名前検索「太郎」
• q=太郎
• fq=type:User
• df=fullname_text
36
Solr上ではすべてのモデルのデータがフラットに格納されている
Sunspotが”type”フィールドを追加して、検索時に絞り込んでいる
Solrの設定
37
フォルダ構成
• solr
• solr.xml
• myproject1
• core.properties
• conf
• schema.xml
• solrconfig.xml
• …
• data
• …
• myproject2
• core.properties
• conf
• schema.xml
• solrconfig.xml
• …
• data
• …
38
重要な設定ファイル
• solr.xml
• 全体設定
• あまり変更しない
• 4.3まではここにcore一覧を定義した
• schema.xml
• データ型
• TokenizerやFilterもここで設定
• Sunspot用のdynamicFieldを正しく設定しないと動かないので注意
• solrconfig.xml
• ログ設定
• autoCommit設定
39
MultiCore
40
これじゃないです →
1つのサーバで複数プロジェクトのデータを扱えるようにする機
能。
• RDBMSでいう「データベース」
• コアごとに設定を変えられる
MultiCore
• 初期のSolr
• プロジェクトA
• production:8983, development: 8982, test: 8981
• プロジェクトB
• production: 8984, development: 8985, test: 8986
• 管理めんどくさい、リソースもったいない
• マルチコア
• 1つのポートでOK
• localhost:8983/solr/projecta-production
• localhost:8983/solr/projecta-development
• localhost:8983/solr/projecta-test
• わかりやすい、省メモリ
• 1個に負荷かけると他にも影響する
• 本番運用は注意
41
マルチコアのときのsunspot.yml
development:
solr:
hostname: localhost
port: 8983
path: /solr/myproject-development
production:
solr:
hostname: localhost
port: 8983
path: /solr/myproject-production
42
coreを増やす
• solr
• solr.xml
• mycore
• core.properties
• conf
• schema.xml
• solrconfig.xml
• ….
• collection1
• core.properties
• conf
• …
cd example/solr
mkdir mycore
cat “name=mycore” > mycore/core.properties
cp –r collection1/conf mycore/conf
# dataディレクトリはコピーしない!
solr 4.4未満では
core.propertiesではなくsolr.xmlの<cores>に定義する
増えていなければ、管理画面のCoreAdmin→Addで登録してやる
43
フォルダコピーで増やせる
solrconfig.xml
• http://wiki.apache.org/solr/SolrConfigXml
• キャッシュ設定
• ログ設定
• autoCommit設定
<autoCommit>
<maxDocs>10000</maxDocs>
<maxTime>1000</maxTime>
</autoCommit>
明示的にcommitしなくても、10000更新または1000msごとに自動commit
44
schema.xml
<schema>
<types>
<fieldType name="text" class="solr.TextField" omitNorms="false">
<analyzer>
<tokenizer class="solr.WhitespaceTokenizerFactory"/>
<filter class="solr.StandardFilterFactory"/>
<filter class="solr.LowerCaseFilterFactory"/>
<filter class="solr.NGramFilterFactory" minGramSize="1" maxGramSize="1"/>
</analyzer>
</fieldType>
<fieldType name="boolean" class="solr.BoolField" omitNorms="true"/>
<fieldType name="date" class="solr.DateField" omitNorms="true"/>
<fieldType name="sint" class="solr.SortableIntField" omitNorms="true"/>
<fieldType name="sfloat" class="solr.SortableFloatField" omitNorms="true"/>
</types>
45
schema.xml (つづき)
<fields>
<field name="id" stored="true" type="string" multiValued="false" indexed="true"/>
<field name="type" stored="false" type="string" multiValued="true" indexed="true"/>
<field name="class_name" stored="false" type="string" multiValued="false" indexed="true"/>
<field name="text" stored="false" type="string" multiValued="true" indexed="true" />
<field name="lat" stored="true" type="tdouble" multiValued="false" indexed="true" />
<field name="lng" stored="true" type="tdouble" multiValued="false" indexed="true" />
<dynamicField name="*_text" stored="false" type="text" multiValued="true" indexed="true"/>
<dynamicField name="*_texts" stored="true" type="text" multiValued="true" indexed="true"/>
<dynamicField name="*_b" stored="false" type="boolean" multiValued="false" indexed="true"/>
<dynamicField name="*_bm" stored="false" type="boolean" multiValued="true" indexed="true"/>
<dynamicField name="*_bs" stored="true" type="boolean" multiValued="false" indexed="true"/>
<dynamicField name="*_bms" stored="true" type="boolean" multiValued="true" indexed="true"/>
<dynamicField name="*_i" stored="false" type="sint" multiValued="false" indexed="true"/>
</fields>
46
schema.xml (つづき)
<uniqueKey>id</uniqueKey>
<defaultSearchField>text</defaultSearchField>
<solrQueryParser defaultOperator="AND"/>
</schema>
47
schema.xml
• tokenizerを設定できる
• KeywordTokenizer
• 文字列を分割しない
• LetterTokenizer
• 非文字で分割
• WhitespaceTokenizer
• 空白文字で分割
• StandardTokenizer
• UAX#29に従って分割
• UAX29URLEmailTokenizer
• URLやemailを認識
• JapaneseTokenizer
• 日本語を分かち書き
48
schema.xml
• filterを設定できる
• LowerCaseFilter
• 大文字を小文字に
• TrimFilter
• 前後の空白を除去
• StopFilter
• ストップワードを除去
• EdgeNGramTokenFilter
• 指定文字数のN-Gramを生成
• JapaneseBaseFormFilter
• 「走れ」を「走る」のように基本形に変換
• JapaneseReadingFormFilter
• 「日本」を「ニホン」のように読みに変換
49
dynamicField
• フィールドを事前に定義しなくても、dynamicFieldの定義に一致
すれば自動的に作ってくれる
• Sunspotが大量に定義
• 自前SolrでSunspotを運用するときは、dynamicFieldの整合性が保たれて
いることが必須
• デフォルトのexampleにあるものとは衝突するので注意
• *_dはdate?double?
• これがないと、検索対象フィールド増やすたびにschema.xmlの
変更が必要になる
50
Sunspot Note
51
index系メソッド
• @user.solr_index
• このユーザのsolr indexを更新
• after_saveで自動で呼ばれるが、関連テーブルは手動で呼んでやる必要がある
• User.solr_index
• 全ユーザのsolr indexを更新
• 非常に重い(データ量によっては1日で終わらない)
• 検索対象(searchable)フィールドを増やしたときなど
• User.solr_reindex
• 全ユーザのsolr indexをいったん全削除してからindex
• 検索対象(searchable)フィールドの意味を変更して互換性が失われたときなど
• 処理中、検索がほとんどヒットしなくなるので注意
• rake sunspot:solr:reindex
• 全モデルに対してsolr_reindex → インデックスを全部作り直す
• solr_indexとindex
• 単なるalias
• indexとindex!
• index!は、即座にSolr Commitを行う
52
動かない、壊れた
• ERROR: unknown field “type”
• schema.xmlが間違っている可能性大
• sunspot_solrのものを使う
• dataディレクトリを丸ごと消してみる
• 当然インデックスは全部消えます
• Connection refused
• Solrは起動しているか
• config/sunspot.ymlのport設定は正しいか
• 404 Not Found
• config/sunspot.ymlのpath設定は正しいか
• No field configured for User with ‘name’
• searchのコードが間違っている
• 一度もindexされていない
• 全然ヒットしない
• reindexしてみる
• 管理画面で検索してみる
53
更新中の検索
• dynamicFieldは、indexする際に自動的にスキーマが追加される
• 一度もindexする前に検索すると
• No field configuredなエラー
• index処理中に検索すると
• 最初の1件ですでにスキーマは生成されているので、エラーにならない
• index済みのものしか検索にヒットしない
• 検索するたびにヒット数が増えていく
• 検索対象(searchable)フィールドを増やすとき
• 順番が大事
• searchableを追加 → 最低1件indexしてスキーマ更新 → search部分デプロイ
• 逆にすると検索がエラーになる
• 検索対象(searchable)フィールドを減らすとき
• 余分なスキーマがあっても(データ量以外)問題ない
• 検索対象(searchable)フィールドの型を変更するとき
• 型を変えるとSunspotが別名をつけてSolrでは別フィールドとして扱われる
54
まとめ
55
まとめ
• Sunspotを使うとすぐにSolr検索を始められる
• サーバで公開するなら最低限チェックしよう
• どのSolrを使うか(sunspot_solr, パッケージ, マニュアルダウンロード)
• アップデートや将来的なスケールにも影響する
• バージョンが古いと一部機能が制限される
• 設定ファイルはどれを使うか
• 適当に選ぶとSunspotに不適切だったり検索結果で日本語がヒットしにくかったり
• dataディレクトリはかなり膨らむので、ディスクスペースとIO負荷に注意
• ログはデフォルトだと非常に肥大化する
• ポートは何番で、coreのパスはどうするか
• この辺決めないと死活監視もアップデートもできない
• 管理画面を使えるようになろう
• デバッグには必須です
• もっと色々調べてみよう
• スケール、可用性、Tokenizer/Filter、パフォーマンスチューニング
• とっても奥が深い
56

SunspotではじめるSolr入門