maeshimaの日記

メモ書きです

sunspot(solr)の設定方法

Advanced Fulltext Search Configuration - GitHubの意訳。

デフォルトの設定だと、スペースか他の区切り文字で区切られている前提。区切りにはStandardTokenizerというのを使ってる。大文字小文字を区別させないようにするためにはLowerCaseFilterというのを使ってる。

初回に rake sunspot:solr:start を叩いたときに solr/conf/schema.xml が出来る。これをいじることで全文検索の設定が出来る。

デフォルトの設定に追加するには

stemming(例:"run"で"runnning"が引っかかる)と同義語の設定を追加するには schema.xml を下記のようにする

<fieldType name="text" class="solr.TextField" omitNorms="false">
  <analyzer>
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

上記の fieldType の属性は sunspot で定義したテキストフィールドで使われる。analyzer は下記のように index を作る時とクエリを投げるときとで分けることが出来る。

<fieldType name="text" class="solr.TextField" omitNorms="false">
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
</fieldType>

ここまではインデックス作る時とクエリ投げるときで一緒の設定だし、デフォルトの設定と同じ。ここから、インデックスを作る時に同義語のサポートをするように修正する。

<fieldType name="text" class="solr.TextField" omitNorms="false">
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
  </analyzer>
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" />
  </analyzer>
</fieldType>

このまま同義語のサポートを続けるなら synonyms.txt を作る必要がある。詳しくは AnalyzersTokenizersTokenFilters - Solr Wiki

さらに stemming の機能を追加する。

<fieldType name="text" class="solr.TextField" omitNorms="false">
  <analyzer type="query">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.PorterStemFilterFactory"/>
  </analyzer>
  <analyzer type="index">
    <tokenizer class="solr.StandardTokenizerFactory"/>
    <filter class="solr.StandardFilterFactory"/>
    <filter class="solr.LowerCaseFilterFactory"/>
    <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" />
    <filter class="solr.PorterStemFilterFactory"/>
  </analyzer>
</fieldType>

設定を変更したら、Solr を再起動してインデックスを作り直す必要がある。これ以上の設定を知りたければ wiki をみるべし。

sunspotのチュートリアル意訳

インストール

solrのインストールはmacなら

brew install solr

でOK。Rails側はまずGemfileに

gem 'sunspot_rails'

を書いて bundle install。それから

rails g sunspot_rails:install

で config/sunspot.yml を作成。

solr のインスタンスを起動

下記の rake タスクで起動できる

rake sunspot:solr:start

初回実行時に Rails.root/solr ディレクトリが作成される。ここには Solr の設定とインデックスのデータファイルが置かれる。solr/data は.gitignoreに追加しておいた方がいいかも。Solr の設定いじりたい場合は solr/conf 配下のファイルをほげる。

モデルの設定

下記みたいに searchable メソッドで設定する。

class Post < ActiveRecord::Base
  searchable do
    text :title, :default_boost => 2
    text :body
  end
end

Postが作られたり更新されたりしたときに自動でインデックスを更新してくれるみたい。searchable の中身を変更したときなどは、手動でインデックスの更新が必要

rake sunspot:reindex

検索

こんな感じで検索すると

class PostsController < ApplicationController
  def search
    @search = Post.search(:include => [:comments]) do
      keywords(params[:q])
    end
  end
end

Sunspot::Search オブジェクトが @search に入る。view は下記のように書く。

.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)

これだと検索したときと検索してないときの処理を分けなきゃいけなくて面倒くさい気がする。

thinking_sphinx の設定

基本

config/sphinx.yml に設定を書く。下記のように database.yml っぽく environment 毎に設定を分けることが出来る。

development:
  port: 9312
test:
  port: 9312
production:
  port: 9312

rake ts:conf などとすると、このファイルを元に設定ファイル(config/{ENVIRONMENT}.sphinx.conf)が作られる。

インデックスファイルの場所

searchd_file_path オプションで設定する。デフォルトは db/sphinx/ENVIRONMENT

configuration, pid, log ファイルの場所

それぞれ下記のように設定する。下記の設定はデフォルト値。

development:
  config_file: "RAILS_ROOT/config/ENVIRONMENT.sphinx.conf"
  searchd_log_file: "RAILS_ROOT/log/searchd.log"
  query_log_file: "RAILS_ROOT/log/searchd.query.log"
  pid_file: "RAILS_ROOT/log/searchd.ENVIRONMENT.pid"
# ... repeat for other environments

searchd デーモンのアドレスとポート

別マシンで searchd を動かしている時に設定する。

production:
  address: 10.0.0.4
  port: 3200
# ... repeat for other environments if necessary

indexer のメモリ容量

index を作る indexer コマンドが使うメモリのリミットはデフォルトで64MB。下記のように変更できる。容量多くするとその分 index を作るのが早くなる

development:
  mem_limit: 128M
# ... repeat for other environments

word stemming / Morphology

think と thinking が同じ意味を示すという設定ができる。英語とロシア語用の設定がビルトインされてる。Snowballで他の言語との設定も入手できるらしいけど、当然日本語はないので使うことはなさげ

development:
  morphology: stem_en
# ... repeat for other environments

wildcard / Star Syntax

アスタリスクをワイルドカードとして使うことの出来る設定。デフォルトはオフ。

development:
  enable_star: true
# ... repeat for other environments

接頭字と接中字のインデックス化

上のワイルドカードをオンにしたときに使う設定。prefix か infix を一度に両方設定することは出来ない。
最低のinfixとprefixの長さを設定できる。その長さより大きい単語がインデックスに入る?

0 が設定されたらこの機能は無効になる。全部をインデックス化したい場合は min_infix_len を 1 にセットする。でもそうするとパフォーマンスが悪化する。

development:
  min_infix_len: 3
  # OR
  min_prefix_len: 3
# ... repeat for other environments

文字コード

デフォルトUTF-8

development:
  charset_type: sbcs
# ... repeat for other environments

charset_tableで文字のマッピングを設定出来る。UTF-8で、他の文字(ASCII以外のことかな?)を含めたい時にcharset_tableを設定する。デフォルトは英語とロシア語。

development:
  charset_table: "0..9, A..Z->a..z, _, a..z, \
U+410..U+42F->U+430..U+44F, U+430..U+44F"
# ... repeat for other environments

これに日本語の文字コードを指定してやっても、基本分かち書き(スペースで単語が分かれている)をベースにしているので、ngramの設定をしない限り日本語の検索は多分満足に出来ないと思う。

検索結果の件数が多い場合

検索を素早くするために、デフォルトではページネーションで使えるレコード数は1000をリミットにしている。変更したい場合は下記のようにして rebuild して、、

development:
  max_matches: 10000
# ... repeat for other environments

さらに検索時にオプションを付けてやる必要がある。

Article.search 'pancakes', :max_matches => 10_000

上記のコードは 10000 レコードを一発で得られるコードではない。あくまでページネーションの結果が 10000 レコードまで得られるというコード。一回で得たい場合は下記のようにする。

Article.search 'pancakes',
  :max_matches => 10_000,
  :per_page    => 10_000

ASCIIcasts - Template Inheritance

ASCIIcasts - “Episode 269 - Template Inheritance”を見て。

以前、Edge Rails.info :: Template Inheritanceを見て、3.1 から 「デフォルトのビューの位置にファイルがない場合は、親のコントローラのビューを見る」という仕様が追加されるのは知ってた。でもコントローラ間で継承ってそうそう使わないし意味あるんかなーとずっと思ってた。

RailcastsとASCIIcastsを見て疑問が氷解。全てのコントローラは ApplicationController を継承している!!!

というわけで、デフォルトで使いまわしたいビューを views/application 配下において、上書きしたい場合は各コントローラに直接書くことで、同一のビューファイルの使い回しが簡単にできるようになりました。

view_path

コントローラの継承以外でも、view_path をいじることで同じようなことが出来る。例えばASCIIcasts上では、サブドメインを見て、特定のパスに置いてあるビューファイルを先に見るような実装を紹介していた。これはどうやらRails2からできる模様。

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base  
  protect_from_forgery  
  before_filter :subdomain_view_path  
    
  def subdomain_view_path  
    if request.subdomain.present?  
      prepend_view_path "app/views/#{request.subdomain}_subdomain"  
    end  
  end    
end  

3.1のストリーミング機能の話

Automatic Flushing: The Rails 3.1 Plan « Katz Got Your Tongue?を読んで。3.1のストリーミング機能の実装が大体どんな感じか、非常に詳しく書かれていてためになった。Fiberを使っている手前、1.9でしか使えないのね。

thinking_sphinx の rake コマンドまとめ

インデックスとコンフィグの作成

下記のうちのどれでもOK。

rake thinking_sphinx:index
rake ts:index
rake ts:in

デフォルトでは、インデックスの作成に加えてコンフィグファイルも生成される。インデックスの作成だけしたいときには

rake thinking_sphinx:reindex

とする。逆にコンフィグだけ作りたいときには下記のどれかを実行する

rake thinking_sphinx:configure
rake ts:conf
rake ts:config

sphinx の実行と停止

rake thinking_sphinx:start
rake ts:start
rake thinking_sphinx:stop
rake ts:stop

インデックスの再構築

インデックスの元データを変更したときには再構築およびsphinxの再起動が必要。indexしてstopしてstartしてもいいけど、それらをひとまとめにした rake コマンドも用意されている。

rake thinking_sphinx:rebuild
rake ts:rebuild

増分インデックス

sphinxはフィールドの一部だけ更新することができない。そのようなときは全部更新する必要がある。thinking_sphinxではこの問題に対応するために、通常のインデックスの他に増分インデックスを用意している。

増分インデックスを使うための方法は下記の三つ。

  • 変更フラグを見る(デフォルト)
  • 更新時間を見る
  • 変更フラグを見つつ delayed_job を利用

delayed_job 使うなら下記のコマンドを実行する

rake thinking_sphinx:delayed_delta
rake ts:dd

cron等で定期的に実行するなら下記のコマンドを実行する。

rake thinking_sphinx:index:delta
rake ts:dd

増分インデックスの具体的なやりかたは別途まとめる予定。