スポンサーサイト

  • 2015.09.25 Friday

一定期間更新がないため広告を表示しています

  • -
  • -
  • -
  • スポンサードリンク

結局、Rails 3.2 で 大量のデータを検索する場合、Sunspot な Solr が一番 いい感じ。という面白くないオチ。

  • 2012.06.10 Sunday
  • 17:16
前回のエントリーで、Elasticsearch を、 tire で実装してみて。日本語の扱いがイケてない。という事実に直面し。。もちろん、Geekなあなたなら、なんとかしちゃうのかもしれませんが、エセ Geek な私は、そそくさと別の道を探ってみたわけです。

で、やっぱり落ち着いたのは Rails 3.2 + Sunspot + Solr 。Solr は、デフォルトの設定で普通に日本語扱えます。前回 Elasticsearch で出会ったように「中日」と検索して「中村」が引っかかることはありません。普通に、何不自由なく、MySQLで like 検索するように、日本語を扱えます。

Railsとの連携について。Sunspot の他にも選択肢があるみたいだけど。Google先生で質問しやすいのは、 Sunspot。とは言っても、英語でないと初心者向けの質問&回答は見つからないけど。。





以下、Rails 3.2 にて、インストールから実装(development 環境)まで参考にした情報。っていうか、Github 内の、オフィシャルドキュメント。 → Quickstart with Rails 3

1) Gemfile に、以下記載
gem 'sunspot_rails'
gem 'sunspot_solr' # development環境で使う、Solr のパッケージ。インストール推奨!
※ もし、Java が入ってない環境なら、先にインストールが必要だと思います。詳しくは、前回の Elasticsearch のエントリーを参照してください。

2) バンドルインストール
bundle install


3) デフォルトのコンフィグファイルを生成する
rails generate sunspot_rails:install


4)「1」にてインストールした Solr を起動する
bundle exec rake sunspot:solr:start # バックグラウンドでなくフォアグラウンドで起動するには「 sunspot:solr:run 」


5) model オブジェクトを、index に追加するための「searchable」ブロックを指定(以下、設定例)。
class Post < ActiveRecord::Base
 searchable do
  text :title, :body
  text :comments do
   comments.map { |comment| comment.body }
  end

  boolean :featured
  integer :blog_id
  integer :author_id
  integer :category_ids, :multiple => true
  double :average_rating
  time :published_at
  time :expired_at

  string :sort_title do
   title.downcase.gsub(/^(an?|the)/, '')
  end
 end
end

※「text」で指定したフィールドは、全文検索対象になります。controller ファイル内で「with」や「facet」で絞込み的にテキストを扱いたい場合は「string」で設定する必要があります。
※ Sunspot で扱えるクラスは、APIドキュメントのType項にて説明されています。ページ中央の「Defined Under Namespace」のところに書いてあります。

6) コントローラの指定(例)。
Post.search do
 fulltext 'best pizza'

 with :blog_id, 1
 with(:published_at).less_than Time.now
 order_by :published_at, :desc
 paginate :page => 2, :per_page => 15
 facet :category_ids, :author_id
end


7) 全文検索の、細かな設定(例)。
# 'pizza' を含む、すべての`text`フィールド (:title, :body, or :comments) のposts が対象
Post.search { fulltext 'pizza' }

# 'pizza' を含むpostsで、特に title 内で見つかったらスコアを上げる(優先させる)
Post.search do
 fulltext 'pizza' do
  boost_fields :title => 2.0
 end
end

# ':featured'(上記「5」のモデルファイル内の「searchable」で設定している boolean 値)が true の posts の場合、スコアを上げる(優先させる)
Post.search do
 fulltext 'pizza' do
  boost(2.0) { with(:featured, true) }
 end
end

# :title に'pizza' が含まれる posts だけを対象にする
Post.search do
 fulltext 'pizza' do
  fields(:title)
 end
end

# :title に pizza が含まれる posts をスコアを上げる(優先させる)。:body に含まれる場合は、スコアを上げない。
Post.search do
 fulltext 'pizza' do
  fields(:body, :title => 2.0)
 end
end



7-1) 全文検索での、フレーズ(Phrases)について。Solr は、近くに並んだ(close together な)単語を「フレーズ」として検索可能にする。Sunspotが使う、デフォルトのquery parser (dismax)では、フレーズ検索は、「" (ダブルクウォート)」で囲むことで有効となる。(→参考
# "great pizza" というフレーズを含んだ posts を検索する
Post.search do
 fulltext '"great pizza"'
end


7-2) 全文検索で、「 query_phrase_slop 」を使えば、フレーズ内の単語と単語の間に割り込める(入ってよい)単語を指定できる。
# フレーズ内の単語の間に、1単語のみ割り込み可能。つまり"great big pizza" も、検索対象となる。
Post.search do
 fulltext '"great pizza"' do
  query_phrase_slop 1
 end
end



7-3) 全文検索で、接近した単語のスコアを上げる。その単語は、必ずしもフレーズの中にある必要はないが、フレーズの中にあった方が、よりスコアは高くなる。
# 「great」と「pizza」を含んだドキュメントにマッチする。もし :title フィールド内のフレーズに、その単語が含まれれば、そのドキュメントのスコアは、より高くなる。
Post.search do
 fulltext 'great pizza' do
  phrase_fields :title => 2.0
 end
end

# 「great」と「pizza」を含んだドキュメントにマッチする。:title フィールドの中のフレーズ(あいだに1単語挟んでもOK)にその単語が含まれれば、そのドキュメントのスコアは、より高くなる。
Post.search do
 fulltext 'great pizza' do
  phrase_fields :title => 2.0
  phrase_slop 1
 end
end



8) スコープ (Scalar フィールド)について。全文検索の対象となる「text」以外(例:integer / boolean / time / etc...)の型は、検索クエリの範囲を指定したり、制限したりするのに使われる。ちなみに、全文検索よりも先に実行される。
# blog_id が 1 となる posts
Post.search do
 with(:blog_id, 1)
end

# 平均値が、3.0 〜 5.0 の posts
Post.search do
with(:average_rating, 3.0..5.0)
end

# category_id が、「1」「 3」「 5」のどれかにマッチする posts
Post.search do
with(:category_ids, [1, 3, 5])
end

# 1週間前にパブリッシュされた、posts
Post.search do
with(:published_at).greater_than(1.week.ago)
end


8-1) 「〜を含まない」というタイプのスコープ
# category_id が、 1 でも 3 でもないカテゴリー
Post.search do
 without(:category_ids, [1, 3])
end

# 「8)」のサンプルは、すべて「without」を使って「〜以外」とすることが可能。


8-2) 「Disjunctions」と「Conjunctions」(→参考
# 有効期限(expired)のない posts。もしく(OR)は、有効期限前の posts
Post.search do
 any_of do
  with(:expired_at).greater_than(Time.now)
  with(:expired_at, nil)
 end
end

# blog_id が「1 」で(AND)、author_idが「2」の posts
Post.search do
 all_of do
  with(:blog_id, 1)
  with(:author_id, 2)
 end
end


Disjunctions と conjunctions は、組み合わせも可能
Post.search do
 any_of do
  with(:blog_id, 1)
  all_of do
   with(:blog_id, 2)
   with(:category_ids, 3)
  end
 end
end


8-3) 全文検索の結果を含んだオブジェクトに対し、スコープによって範囲指定/制限をすることが可能
# blog_idが「1」で(AND)、 'pizza' を :title に含む posts
Post.search do
 with(:blog_id, 1)
 fulltext("pizza")
end



9) ページネーション( Pagination )について。Solr の検索結果は、すべてページ分割されている。検索結果の配列は、will_paginate や kaminari のようなページネーションライブラリと、シームレスに連動することができるメソッドを mixed in している。Sunspot はデフォルトで、Solr の検索結果の、最初の30件をリクエストする。
search = Post.search do
 fulltext "pizza"
end

# 全60件の検索結果があるとして。30件/1ページだとすると、全2ページとなる
results = search.results  # => Array with 30 Post elements

search.total  # => 60

results.total_pages  # => 2
results.first_page?  # => true
results.last_page?  # => false
results.previous_page  # => nil
results.next_page  # => 2
results.out_of_bounds?  # => false
results.offset  # => 0



次の結果のページを取得するため、検索(search)再度行い、paginate メソッドを使う。
search = Post.search do
 fulltext "pizza"
 paginate :page => 2
end

# 全60件あるとして。今回は 2ページ目が対象だ
results = search.results # => Array with 30 Post elements

search.total  # => 60

results.total_pages  # => 2
results.first_page?  # => false
results.last_page?  # => true
results.previous_page  # => 1
results.next_page  # => nil
results.out_of_bounds?  # => false
results.offset  # => 30


「 paginate: 」に「 :per_page 」オプションをつけると、結果ページ内の表示件数をカスタマイズできる
search = Post.search do
 fulltext "pizza"
 paginate :page => 1, :per_page => 50
end



10) ファセット(Faceting)とは、検索条件や絞り込み条件にマッチしたドキュメントの数を限定するための、Solr の機能だ。ドリルダウン型の検索インターフェイスを作る際などに使える。各 facet は、0 以上の row を返す。各 row は、検索結果 を絞り込む条件となる。
「 field facets 」の場合、各 row は、対象フィールド の特定の値 を 表す。「 query facets 」の場合、各 row は、不特定のスコープ を表す。実のところ、 facet とは、スコープの論理的グループにすぎない。

10-1) Field Facets
# 'pizza' にマッチした posts を、:author_id ごとにカウントして返す
search = Post.search do
 fulltext "pizza"
 facet :author_id
end

search.facet(:author_id).rows.each do |facet|
 puts "Author #{facet.value} has #{facet.count} pizza posts!"
end


10-2) Query Facets
# 平均値を、ファセットにより範囲指定した posts (のグループ)
Post.search do
 facet(:average_rating) do
  row(1.0..2.0) do
   with(:average_rating, 1.0..2.0)
  end
  row(2.0..3.0) do
   with(:average_rating, 2.0..3.0)
  end
  row(3.0..4.0) do
   with(:average_rating, 3.0..4.0)
  end
  row(4.0..5.0) do
   with(:average_rating, 4.0..5.0)
  end
 end
end

# 例)
# 平均値が、1.0 〜 2.0 のposts : 2件
# 平均値が、2.0 〜 3.0 のposts : 1件
search.facet(:average_rating).rows.each do |facet|
 puts "Number of posts with rating withing #{facet.value}: #{facet.count}"
end



11) 表示順( Ordering )について。標準では、Sunspot は "score" に基づいた表示順となる。ちなみに "score" とは、Solr 判断による、相対的な基準だ。ソートは「order_by」メソッドを使って実現する。
# 平均値(降順 - descending - )でソート
Post.search do
 fulltext("pizza")
 order_by(:average_rating, :desc)
end

# :score の相関によってソートする。相関が同点の場合、平均値(:average_rating)が高いものを上にする。
Post.search do
 fulltext("pizza")

 order_by(:score, :desc)
 order_by(:average_rating, :desc)
end

# Randomized ordering
Post.search do
 fulltext("pizza")
 order_by(:random)
end



12) Geospatial
TODO


13) 結果のハイライト( Highlighting )について。ドキュメント内の、検索クエリにマッチした部分を強調表示することができる。ハイライトしたいフィールドは「 :store 」する必要がある。
class Post < ActiveRecord::Base
 searchable do
  # ...
  text :body, :stored => true
 end
end


例えば、 :body フィールドにマッチした場合に、ハイライトするには。
search = Post.search do
 fulltext "pizza" do
  highlight :body
 end
end

# ハイライトのされ方は、こんな感じ;
# Post #1
# 私は、本当に *pizza* が大好きだ。
# *Pizza* は、私の好物だ
# Post #2
# ペペロニ *pizza* って、おいしいなあ。
search.hits.each do |hit|
 puts "Post ##{hit.primary_key}"

 hit.highlights(:body).each do |highlight|
  puts " " + highlight.format { |word| "*#{word}*" }
 end
end



14) Functions
TODO

15) More Like This
TODO


16) インデックスの、細かい点。
あとでやる。

16-1) スコアを上げるのは、インデックスの時に!( Index-Time Boosts )
どんなクエリの時にも、スコアを上げたいフィールドは、インデックスの時に :boost 設定をしよう
class Post < ActiveRecord::Base
 searchable do
  text :title, :boost => 5.0
  text :body
 end
end



17) Stored Fields について。 :stored したフィールドは;
・ Solr の内部でも 元のまま(tokenized されてない/ analyzed されてない)の値を保つ
・ 元となる Database(通常はSQL)を参照することなく、データを取得することができる。ハイライトするのにも使われる。
class Post < ActiveRecord::Base
 searchable do
  text :body, :stored => true
 end
end

# Database を参照することなく、:stored されたコンテンツを取得する
Post.search.hits.each do |hit|
 puts hit.stored(:body)
end



18) 「 #hits 」と「 #results 」
Sunspot はオブジェクトの型や Primary Key を Solr に保存する。検索結果を取得する際、Primary Key は、実際のオブジェクト(通常は、SQL の Database)をロードするために使われる。
#「 #results 」 を使って、オブジェクト・リレーショナル・マッパー(以下、ORM。 ORMの例としては、ActiveRecord + SQL のDatabase がある)から、該当レコードを取得する
Post.search.results.each do |result|
 puts result.body
end


元となるDatabase にクエリを投げずに、検索結果の情報にアクセスするには「 #hits 」を使う
# 「 #hits 」を使って、ORM からオブジェクトをロードすることなく、すべての情報を Solr から取得する
Post.search.hits.each do |hit|
 puts hit.stored(:body)
end


ORM からロードする「 #results 」と、ファセットやハイライトなど「 #hits 」の両方が必要な場合、「 each_with_result 」というコンビニメソッドが用意されている
Post.search.each_hit_with_result do |hit, result|
 # ...
end



19) 再インデックス( Reindexing Objects )について。Rails を使っていれば、 save のコールバックの一部として、オブジェクトは Solr に勝手に インデックスされる。ただし、「 searchable 」ブロック内のスキーマを変更した場合は、すべての オブジェクトの再インデックス( reindex )を行い、 Solr に変更を反映させる必要がある。
bundle exec rake sunspot:solr:reindex

# もしくは、特定のバッチサイズで、特定のモデルに対して再インデックスするなら
bundle exec rake sunspot:solr:reindex[500,Post]  # shell によっては、 [ with ¥[ and ] with ¥] をエスケープする必要がある



20) Use Without Rails
TODO


21) Solr のパラメータを、手動で設定するには、「 adjust_solr_params 」を使う
Post.search do
 adjust_solr_params do |params|
  params[:q] += " AND something_s:more"
 end
end



22) Session Proxies
TODO

23) Type Reference
TODO (→ 参照:APIドキュメントのType項


24) 開発について( Development )

24-1) 「 sunspot 」で、テストする。まず必要とされる gem をインストールする
cd /path/to/sunspot/sunspot
bundle install


Solr のインスタンスを、port 8983 で立ち上げる
bundle exec sunspot-solr start -p 8983
# フォアグラウンドでの実行は → `bundle exec sunspot-solr run -p 8983`


テストをはしらせる
bundle exec rake spec


必要があれば、Solr のインスタンスを止める
bundle exec sunspot-solr stop


24-2)「 sunspot_rails 」で、テストする。まず必要とされる gem をインストールする
cd /path/to/sunspot/sunspot
bundle install


Solr のインスタンスを、port 8983 で立ち上げる
bundle exec sunspot-solr start -p 8983
# フォアグラウンドでの実行は → `bundle exec sunspot-solr run -p 8983`


sunspot_rails のディレクトリに移動して
cd ../sunspot_rails


テストをはしらせる
rake spec # Rails の全バージョン
rake spec RAILS=3.1.1 # 特定バージョンの Rails だけ


必要があれば、Solr のインスタンスを止める
cd ../sunspot
bundle exec sunspot-solr stop




以上、Github 内の、オフィシャルドキュメント。 → Quickstart with Rails 3








ちなみに。ちょっと使ってて、便利だなあと思ったのは「 has_many :through 」とかのレベルまで Sunspot が理解してくれて、インデックスを作ってくれること。例えば、以下の様な感じ;
class Event < ActiveRecord::Base
 attr_accessible :xxxxxxx, :xxx, :xxxxx, :xxxxx, :xxxx, :xxxxxxx

 belongs_to :mother
 has_many :kids
 has_many :grandchildren, :through => :kids

 searchable do
  text :xxxx
  integer :xxxx
  string :xxxxxx

  string :xxxxxx, :multiple => true do  #子要素は、複数あるから、multiple
   kids.map(&:xxxxxx)
  end

  time :xxxxxxt, :trie => true, :multiple => true do
   grandchildren.map(&:xxxxxx)
  end
end

こんな感じで「has_many, :through」とかが設定されてれば、いきなり「grandchildren」とか出しても理解してくれるんです。






あと、こっちも参照。 →  Adding Sunspot search to Rails in 5 minutes or less

1) Gemfile 内に、以下記載
gem 'sunspot_rails', '~> 1.3.0'

2) バンドルインストール
$ bundle install

3) config/sunspot.yml を生成
$ rails g sunspot_rails:install

※ もし、Java が入ってない環境なら、先にインストールが必要だと思います。詳しくは、前回の Elasticsearch のエントリーを参照してください。

4) 現在の環境(おそらく development)で、solr を起動する
$ rake sunspot:solr:start

※ http://localhost:8982/solr/admin/ にて、Solr を操作することが可能。

5) model ファイルに、検索対象の記述を追加する(以下、例。)
class Post < ActiveRecord::Base
 searchable do
  text :title, :default_boost => 2
  text :body
 end
end


6) 検索用の index を作成する(※ ActiveRecord を使って保存されたデータについては、以後自動的に index に追加されるけど、初回なのでこれまでのデータを追加するために実行。)
$ rake sunspot:reindex


7) controller ファイルに、検索メソッドを追加する(以下、例。)
class PostsController < ApplicationController
 def search
  @search = Post.search(:include => [:comments]) do
   keywords(params[:q])
  end
 end
end


8) view ファイルに、検索部分を追加する(以下、Hamlでの例。)
.results
 - @search.each_hit_with_result do |hit, post|
  .result
   %h2= h post.title
   %h6== (#{h hit.score})
   %p= h truncate(post.body, :length => 100)
.pagination
 = will_paginate(@search.results)




以上、Adding Sunspot search to Rails in 5 minutes or less






あと。以下も参考になりました。

・『 Date range facets with Sunspot in Ruby on Rails 』
→ 日付を、ある範囲内で検索するような場合は、「 :trie => true 」を指定。「 time 」のみ対応( date では :trie を指定できない)。ちなみに、Database 上は、datetime を使ってると思いますが、その中に NULL( もしくは「0000-00-00 00:00:00」かも)があると「 $ rake sunspot:reindex 」した時に「 rake aborted! sunspot Error: Invalid Date String:'' 」みたいに出るから注意。日付の欄には、きちんと値を入れておく必要がある。みたい。
class Event
 searchable do
  time :start_time, :trie => true
 end
end

Event.search do
 facet :start_time do
  bod = Time.zone.now.beginning_of_day
  row :today do
   with :start_time, bod..(bod + 1)
  end
  row :tomorrow do
   with :start_time, (bod + 1)..(bod + 2)
  end
  # etc.
 end
end








あと。まだ詳しく診てないけど。 #hits を使った実例;
『Sunspot, Solr, Rails: Working with Results』






こちらも。実装のイメージを把握するのに、いい感じの実例;
『Full-text search in Rails with Sunspot』






こちらも、導入の参考にしました;
Railscast『 #278: Sunspotで全文検索 』






ちなみに Solr にはよさげな日本語の書籍があります。→『Apache Solr入門 ―オープンソース全文検索エンジン』。Rails関連の記述は古いですが、Solrについて理解を深めるには、良さ気な本だと思います。





・・・ぱーーーっと急いで書いたたので、誤字脱字。勘違いしてるぞ!などは、コメントくださいませ orz..
JUGEMテーマ:インターネット


スポンサーサイト

  • 2015.09.25 Friday
  • 17:16
  • -
  • -
  • -
  • スポンサードリンク
コメント
コメントする

PR

calendar

S M T W T F S
   1234
567891011
12131415161718
19202122232425
2627282930  
<< November 2017 >>

本が出てます☆

Twitter

selected entries

categories

archives

recent comment

  • Mac OS X Lion で、emacs を楽に使うために、Meta キーを「option」に設定したい。
    通りすがり (01/19)
  • 携帯キャリアの、アクセス制限(未成年保護)についてのまとめ
    たけのこの里 (10/01)
  • Mac OS X Lion で、emacs を楽に使うために、Meta キーを「option」に設定したい。
    イシカワ (07/16)
  • さくらインターネットで、gem install すると「chown/chgrp: Operation not permitted 」と叱られる件の対応
    sean (04/20)
  • さくらインターネットで、gem install すると「chown/chgrp: Operation not permitted 」と叱られる件の対応
    てっちー (03/24)
  • Mac OS X Lion で、emacs を楽に使うために、Meta キーを「option」に設定したい。
    JO (01/04)
  • Passbook(パスブック)on iOS6 (NDAに触れない範囲で...)
    ぱん (09/28)
  • Passbook(パスブック)on iOS6 (NDAに触れない範囲で...)
    宮腰睦美 (09/23)
  • magit を、Lion の emacs にインストール
    ぱん (05/08)
  • 【 Xcode4.2 】Interface Builder使わずに、座標を合わせたい(習作1)
    ぱん (12/21)

recommend

iOSプログラミング 第2版
iOSプログラミング 第2版 (JUGEMレビュー »)
アーロン・ヒレガス,ジョー・コンウェイ,Aaron Hillegass,Joe Conway
■独学で初心者を脱出するには、必読ではないでしょうか。翻訳でニュアンスが伝わらない部分があるので、原書と、サポートサイト(英語)を活用すべし!です。

recommend

iPhoneプログラミングUIKit詳解リファレンス
iPhoneプログラミングUIKit詳解リファレンス (JUGEMレビュー »)
所 友太
■内容古いですが、iOSプログラマー中級以上の階段を登るために、必要な本だと思います。iOS5対応版出ないかな。。

recommend

iOSプログラミング入門 - Objective-C + Xcode 4で学ぶ、iOSアプリ開発の基礎
iOSプログラミング入門 - Objective-C + Xcode 4で学ぶ、iOSアプリ開発の基礎 (JUGEMレビュー »)
大津 真
■Ch.1「iOS プログラミングを始めるための基礎知識」でXcodeの概要を理解して、Ch.2「Objective-C の基礎知識」で、Objective-Cの考え方を理解できます。iOSプログラミングのキックオフにぴったり。

recommend

去年ルノアールで
去年ルノアールで (JUGEMレビュー »)
せきしろ
■ルノアールで妄想が爆発

recommend

RailsによるアジャイルWebアプリケーション開発 第4版
RailsによるアジャイルWebアプリケーション開発 第4版 (JUGEMレビュー »)
Sam Ruby,Dave Thomas,David Heinemeier Hansson
■Railsのバイブル第4版の日本語版が2011年末にリリース!サーバサイドで準備するAPIや、Webサイト関連のもろもろは、やっぱRailsでしょう。

links

profile

search this site.

others

mobile

qrcode

powered by

無料ブログ作成サービス JUGEM