Pull to refresh

Rails+Sphinx=? Часть I

Reading time 4 min
Views 6.4K
Поговорим о поиске в Ruby on Rails?

Я решил разбить повествование на две части: в первой скучная настройка проекта и простой поиск по одному полю одной модели. Во второй подробнее остановимся на тонкостях и я постараюсь рассказать про все, что может плагин. Кстати в исходниках (ссылка в тексте) проект уже немного изменен для второй части, но проблем это не вызовет.

Установка


Устанавливаем Rails не ниже 2.0.2
Скачиваем сфинкс 0.9.8: www.sphinxsearch.com/downloads.html и собираем самостоятельно, или используем порты/портажи/<вставить нужное>
$ sudo port install sphinx

Sphinx поддерживает две субд — MySQL и PostgreSQL, но достаточно легко можно добиться поддержки любой базы данных.
Проверка после установки:
Macintosh:sphinx-0.9.8 kronos$ searchd -h
Sphinx 0.9.8-release (r1371)
Copyright (c) 2001-2008, Andrew Aksyonoff
...

Путь до searchd и indexer должен быть в переменной окружения path.

Сфинкс состоит из нескольких утилит, некоторые из них:
searchd — поисковый демон
search — консольный аналог searchd для отладки/теста поиска.
indexer — индексатор.
Создаем проект:
$ rails sphinxtest -d mysql
$ cd sphinxtest/

Незабудьте отредактировать config/database.yml

Для удобства для работы со сфинксом будем использовать плагин.
Я считаю, что адекватных плагина для Rails два — ultrasphinx и Thinking Sphinx (кстати пока писал статью вышел RailsCast про него). Так как последний из-за внутренних именований второй конфликтует с другим плагином «redhill on rails», то я использую первый. Но возможно второй лучше — выбирайте сами. :)
Установка плагина:
$ script/plugin install git://github.com/fauna/ultrasphinx.git


Настройка


$ mkdir config/ultrasphinx
cp vendor/plugins/ultrasphinx/examples/default.base config/ultrasphinx/

default.base — заготовка для конфигурационного файла sphinx-а. В первой части просто настроим пути до логов/пидов/индексов:
# ...
searchd
{
# ...
log = /opt/local/var/db/sphinx/log/searchd.log
query_log = /opt/local/var/db/sphinx/log/query.log
pid_file = /opt/local/var/db/sphinx/log/searchd.pid
# ...
}
# ...
index
{
# путь где будут лежать индексы
path = /opt/local/var/db/sphinx/
# ...
}
# ...

Пишем код


Для простоты сделаем один контроллер с формой, которая с помощью ajax-а, будет искать скажем… Артистов по имени. Модель артиста будет состоять из одного поля — title:
$ script/generate controller home index search
$ script/generate model artist

Код миграции, пусть у артиста будет только одно поле title(db/migrate/..._create_artists.rb):
class CreateArtists < ActiveRecord::Migration
  def self.up
    create_table :artists do |t|
      t.string :title, :null => false
      t.timestamps
    end
  end

  def self.down
    drop_table :artists
  end
end

Теперь скажем сфинксу, что искать мы будем по одному полю (app/models/artist.rb):
class Artist < ActiveRecord::Base
is_indexed :fields => ['title']
end

Запись «is_indexed :fields => ['title']» означает, что индексирование будет происходить по одному полю.

Ну и создаем базы и выполняем миграции:
$ rake db:create
$ rake db:migrate

Так же стоит настроить роуты в файле config/routes.rb:
map.root :controller => 'home'
map.search 'search', :conditions => {:method => :get}, :controller => 'home', :action => 'search'


Код контроллера(app/controllers/home_controller.rb):
class HomeController < ApplicationController
  def index
  end

  def search
    query = params[:query].split(/'([^']+)'|"([^"]+)"|\s+|\+/).reject{|x| x.empty?}.map{|x| x.inspect }*' && '
    @artists = Ultrasphinx::Search.new(:query => query, 
                                      :sort_mode => 'relevance', 
                                      :class_names => ["Artist"])    
    @artists.run
    respond_to do |format|
      format.js #search.js.erb
    end
  end
end

Первым регулярным выражением мы разбираем поисковый запрос, разбивая слова по пробелам, игнорируем пустые слова( например ,,) и добавляем ко всем слова кавычки. Операция && означает лишь набор слов, ну например запросу
«Bleed it out» => 'Bleed' && 'it' && 'out' будет соответствовать и запись «Sell it out» (два слова из трех совпали), т.е. && не диктует список обязательных слов, а лишь перечисляет их (если вам необходимо обязательное наличие всех слов, то нужно использовать AND, но об этом во второй части).
Коротко пробежимся по параметрам:
:query — поисковый запрос
:sort_mode — тип сортировки результатов
:class_names — массив имен классов моделей которые будут созданы в результате поиска. Sphinx внутри себя хранит каждый документ как набор полей и их значений. В Rails с таким представлением работать не удобно, а куда удобнее с готовым объектом модели. Ultrasphinx сам определит к какой модели относиться найденный документ и создаст его экземпляр, таким образом сам поиск ничем не отличается от Artist.find(...) или Artist.paginate (да, результаты поискового запроса совместимы с will_paginate-ом).
Команда @artists.run выполняет запрос. Запросы выполняются очень быстро. На семимилионной базе — тысячные секунды.
Представления(шаблоны) можно посмотреть в готовом проекте

теперь можно что нибудь добавить в базу:
$ script/console
>> Artist.create(:title => 'Tiesto')
>> Artist.create(:title => 'Armin')
>> Artist.create(:title => 'ATB')
>> exit

Выполним необходимые приготавления для работы плагина (это нужно делать каждый раз когда вы что либо меняете в моделях в описании is_indexed):
$ rake ultrasphinx:configure
$ rake ultrasphinx:index
$ rake ultrasphinx:daemon:start (либо restart если уже запущен)

Запускаемся и тестируемся)
$ mongrel_rails -p 3001 -d

Одно маленькое но


Индексы отдельно данные в базе отдельно. Когда мы удаляем/изменяем/добавляем в базу индексы не изменяются. Чтобы изменения базы отразились на индексах полную реиндексацию базы:
$ rake ultrasphinx:index

Конечно это не очень хорошо. Но могу заверить, что решение проблемы существует и называется delta-индексированием. Об этом в следующей части.

Резюме


Sphinx — очень крутая штука :). Open source, бесплатна, шустро ищет и индексирует. Must have!

Аналоги


Из аналогов могу отметить acts_as_ferret, для маленьких проектов он подойдет идеально (например мы использовали его на Хакфесте Рамблера), но для больших объемов данных он ведет себя мягко говоря неважно — очень долго индексирует.
Для постгресткого tsearch2 есть вроде бы не плохой плагин: Acts as tsearch, в бою не применял, не знаю. Еще есть acts_as_solr
Tags:
Hubs:
0
Comments 6
Comments Comments 6

Articles