В одном из текущих проектов возникла задача поиска по данным разного типа, которая была успешно решена с помощью зарекомендовавшей себя поисковой машины Sphinx, но обо всем по порядку.
В проекте есть на данный момент 2 зоны:
Все решение описано для связки PHP5 (Symfony), MySQL, Sphinx. Как ставить Sphinx, я описывать не буду, эту информацию можно прочитать на официальном сайте. Скажу лишь, что под Mac OS X он легко ставиться с помощью macports.
Имеем такую модель БД (я ее упростил, чтобы было поближе к сути) с каким-то набором записей:
Конфигурируем sphinx для индексации и выдачи результатов поиска:
Чуть по-подробнее о параметрах конфигурации. Разделы source, как понятно из названия, задают хранилища данных, откуда будет извлекаться индексируемая Sphinx информация. Такими хранилищами могут быть базы данных, текстовые файлы, html-файлы, xml и даже почтовые ящики. Этот раздел также описывает, какие поля хранилища будут индексироваться, в каком формате будет производиться индексация (выборка разовая или порционная) и ряд других параметров. В моем случае описано 3 source, все они ведут в одну и ту же базу данных MySQL, но в разные таблицы.
Форматы конфигураций похожи, я опишу source article.
Далее идет описание атрибутов, которые можно использовать в качестве фильтров
Ну и последний параметр — это маска запроса, который будет извлекать нужную нам информацию по найденным id:
Далее в конфигурационном файле описывается самое важное — параметры индексации указанных нами source-ов с помощью секции index.
Очень важный момент — индекс может формироваться из нескольких source. Как показано выше, в индекс сливаются данные из трех таблиц. Представьте, как пришлось бы попотеть, чтобы организовать такой поиск с помощью БД! Здесь же мы просто можем делать запрос к данному индексу, получая при этом его отранжированные результаты.
Строчками
В чем еще одна прелесь Sphinx — он «из коробки» поддерживает английскую и русскую морфологию, позволяя приводить слова запроса к нормальной форме. При необходимости эту функциональность можно расширить.
Оставшиеся три параметра отвечают за вырезание html-тегов, кодировку индекса и минимальную длину слова соответственно.
Далее осталось только запустить индексацию.
В листинге выше мы сначала останавливаем демон на случай, если он запущен. Затем выполняем индексацию. Можно увидеть, насколько высока скорость индексации у Sphinx.
// В комментариях подсказали, что можно производить индексирование, не останавливая демон командой sudo indexer --rotate --all.
Затем запускаем демон и выполняем пробный запрос. Sphinx показывает, как он разбивает запрос и нормализует слова в нем. В моем примере он отработал нормально, но ничего не нашел :)
После того, как удостоверились, что демон работает, можно работать со Sphinx из Symfony.
Устанавливаем плагин sfSphinxPlugin, подключаем его в конфигурациях:
и пишем небольшой пример запроса к демону:
Надеюсь, из моего описания можно оценить все прелести Sphinx, я рассказал далеко не обо всех его возможностях, остальное при желании вы уже сможете самостоятельно изучить.
PS: Просьба к тем у кого достаточно кармы — создайте блог Sphinx, я бы перенес туда статью.
PS2: Спасибо всем! Блог создан, топик перенесен туда.
Постановка задачи
В проекте есть на данный момент 2 зоны:
- географическая зона, реализованная на базе Google Maps, которая отображает нанесенные пользователями на карту географические объекты (маркеры, маршруты и области);
- информационная зона, которая представляет собой большой иерархически организованный каталог, содержащий информационные материалы.
Решение задачи
Все решение описано для связки PHP5 (Symfony), MySQL, Sphinx. Как ставить Sphinx, я описывать не буду, эту информацию можно прочитать на официальном сайте. Скажу лишь, что под Mac OS X он легко ставиться с помощью macports.
Имеем такую модель БД (я ее упростил, чтобы было поближе к сути) с каким-то набором записей:
Конфигурируем sphinx для индексации и выдачи результатов поиска:
- #articles
- source article
- {
- type = mysql
- sql_host = localhost
- sql_user = root
- sql_pass = root
- sql_db = ili_lv
- sql_sock = /tmp/mysql/mysql.sock
- sql_query_range = SELECT MIN(id), MAX(id) FROM article
- sql_range_step = 500
- sql_query_pre = SET NAMES utf8
- sql_query = \
- SELECT id * 10 + 1 as id, category_id, 1 as row_type,\
- UNIX_TIMESTAMP(created_at) as created_at, title, descr \
- FROM article WHERE id >= $start AND id <= $end
- sql_attr_uint = category_id
- sql_attr_uint = row_type
- sql_attr_timestamp = created_at
- sql_query_info = SELECT title, descr \
- FROM article WHERE id = ($id - 1) / 10
- }
- #categories
- source category
- {
- #аналогичный блок параметров подключения к БД
- #...
- sql_query_range = SELECT MIN(id), MAX(id) FROM category
- sql_range_step = 500
- sql_query_pre = SET NAMES utf8
- sql_query = \
- SELECT id * 10 + 2 as id, tree_parent as category_id, 2 as row_type,\
- UNIX_TIMESTAMP(created_at) as created_at, title, descr \
- FROM category WHERE id >= $start AND id <= $end
- sql_attr_uint = category_id
- sql_attr_uint = row_type
- sql_attr_timestamp = created_at
- sql_query_info = SELECT title, descr \
- FROM category WHERE id = ($id - 2) / 10
- }
- #geo_objects
- source geo_object
- {
- #аналогичный блок параметров подключения к БД
- #...
- sql_query_range = SELECT MIN(id), MAX(id) FROM geo_object
- sql_range_step = 500
- sql_query_pre = SET NAMES utf8
- sql_query = \
- SELECT id * 10 + 3 as id, 0 as category_id, 3 as row_type,\
- UNIX_TIMESTAMP(created_at) as created_at, title, descr \
- FROM geo_object WHERE id >= $start AND id <= $end
- sql_attr_uint = category_id
- sql_attr_uint = row_type
- sql_attr_timestamp = created_at
- sql_query_info = SELECT title, descr \
- FROM geo_object WHERE id = ($id - 3) / 10
- }
- index site_search
- {
- source = category
- source = geo_object
- source = article
-
- path = /var/data/sphinx/site_search
- docinfo = extern
- morphology = stem_en, stem_ru
- html_strip = 0
- charset_type = utf-8
- min_word_len = 2
- }
* This source code was highlighted with Source Code Highlighter.
Чуть по-подробнее о параметрах конфигурации. Разделы source, как понятно из названия, задают хранилища данных, откуда будет извлекаться индексируемая Sphinx информация. Такими хранилищами могут быть базы данных, текстовые файлы, html-файлы, xml и даже почтовые ящики. Этот раздел также описывает, какие поля хранилища будут индексироваться, в каком формате будет производиться индексация (выборка разовая или порционная) и ряд других параметров. В моем случае описано 3 source, все они ведут в одну и ту же базу данных MySQL, но в разные таблицы.
Форматы конфигураций похожи, я опишу source article.
- sql_query_range = SELECT MIN(id), MAX(id) FROM article
- sql_range_step = 500
* This source code was highlighted with Source Code Highlighter.
Этими строками мы «указываем» Sphinx делать выборку из таблицы не полным select-ом, а порциями по 500 записей, чтобы не создавать избыточную нагрузку при индексации.
- sql_query = \
- SELECT id * 10 + 1 as id, category_id, 1 as row_type,\
- UNIX_TIMESTAMP(created_at) as created_at, title, descr \
- FROM article WHERE id >= $start AND id <= $end
* This source code was highlighted with Source Code Highlighter.
Это маска запроса, отправляемого Sphinx при индексации данных. Здесь важно 3 момента:- Определяется набор полей для индексации, в нашем случае это id, текстовые поля и поля-фильтры;
- Первое поле используется Sphinx-ом как id в формируемом индексе. Т.к. id из разных таблиц могут совпадать, то применен такой метод формирования уникального id;
- Поле row_type дает возможность определить, какого типа каждая из сохраненных записей в индексе Sphinx.
Далее идет описание атрибутов, которые можно использовать в качестве фильтров
- sql_attr_uint = category_id
- sql_attr_uint = row_type
- sql_attr_timestamp = created_at
* This source code was highlighted with Source Code Highlighter.
Ну и последний параметр — это маска запроса, который будет извлекать нужную нам информацию по найденным id:
- sql_query_info = SELECT title, descr \
- FROM geo_object WHERE id = ($id - 1) / 10
* This source code was highlighted with Source Code Highlighter.
Далее в конфигурационном файле описывается самое важное — параметры индексации указанных нами source-ов с помощью секции index.
- source = category
- source = geo_object
- source = article
* This source code was highlighted with Source Code Highlighter.
Очень важный момент — индекс может формироваться из нескольких source. Как показано выше, в индекс сливаются данные из трех таблиц. Представьте, как пришлось бы попотеть, чтобы организовать такой поиск с помощью БД! Здесь же мы просто можем делать запрос к данному индексу, получая при этом его отранжированные результаты.
Строчками
- path = /var/data/sphinx/site_search
- docinfo = extern
* This source code was highlighted with Source Code Highlighter.
указываются параметры хранения индекса и полный путь к нему.В чем еще одна прелесь Sphinx — он «из коробки» поддерживает английскую и русскую морфологию, позволяя приводить слова запроса к нормальной форме. При необходимости эту функциональность можно расширить.
- morphology = stem_en, stem_ru
* This source code was highlighted with Source Code Highlighter.
Оставшиеся три параметра отвечают за вырезание html-тегов, кодировку индекса и минимальную длину слова соответственно.
Далее осталось только запустить индексацию.
- muxx:~ muxx$ sudo searchd --stop
- Sphinx 0.9.8.1-release (r1533)
- Copyright (c) 2001-2008, Andrew Aksyonoff
- using config file '/usr/local/etc/sphinx.conf'...
- stop: succesfully sent SIGTERM to pid 5677
- muxx:~ muxx$ sudo indexer --all
- Sphinx 0.9.8.1-release (r1533)
- Copyright (c) 2001-2008, Andrew Aksyonoff
- using config file '/usr/local/etc/sphinx.conf'...
- indexing index 'site_search'...
- collected 759 docs, 0.0 MB
- sorted 0.0 Mhits, 100.0% done
- total 759 docs, 22171 bytes
- total 0.028 sec, 785871.25 bytes/sec, 26903.45 docs/sec
- muxx:~ muxx$ sudo searchd
- Sphinx 0.9.8.1-release (r1533)
- Copyright (c) 2001-2008, Andrew Aksyonoff
- using config file '/usr/local/etc/sphinx.conf'...
- creating server socket on 127.0.0.1:3312
- muxx:~ muxx$ search мой сложный запрос
- Sphinx 0.9.8.1-release (r1533)
- Copyright (c) 2001-2008, Andrew Aksyonoff
- using config file '/usr/local/etc/sphinx.conf'...
- index 'site_search': query 'мой сложный запрос ': returned 0 matches of 0 total in 0.000 sec
- words:
- 1. 'мо': 0 documents, 0 hits
- 2. 'сложн': 0 documents, 0 hits
- 3. 'запрос': 0 documents, 0 hits
- muxx:~ muxx$
* This source code was highlighted with Source Code Highlighter.
В листинге выше мы сначала останавливаем демон на случай, если он запущен. Затем выполняем индексацию. Можно увидеть, насколько высока скорость индексации у Sphinx.
// В комментариях подсказали, что можно производить индексирование, не останавливая демон командой sudo indexer --rotate --all.
Затем запускаем демон и выполняем пробный запрос. Sphinx показывает, как он разбивает запрос и нормализует слова в нем. В моем примере он отработал нормально, но ничего не нашел :)
После того, как удостоверились, что демон работает, можно работать со Sphinx из Symfony.
Устанавливаем плагин sfSphinxPlugin, подключаем его в конфигурациях:
- $this->enablePlugins(array('sfSphinxPlugin'));
* This source code was highlighted with Source Code Highlighter.
и пишем небольшой пример запроса к демону:
- $sphinx = new sfSphinxClient($options);
-
- //устанавливаем числовые фильтры, если они заданы
- if ($request->getParameter('category_id'))
- $sphinx->setFilter('category_id', array($request->getParameter('category_id')));
- if ($request->getParameter('row_type'))
- $sphinx->setFilter('row_type', array($request->getParameter('row_type')));
- $dateRange = $request->getParameter('date');
- if ($dateRange['from'] || $dateRange['to'])
- {
- $sphinx->setFilterRange('created_at',
- !empty($dateRange['from']) ? strtotime($dateRange['from']) : '',
- !empty($dateRange['to']) ? strtotime($dateRange['to']) : '');
- }
- $this->results = $sphinx->Query($request->getParameter('s'), 'site_search');
- if ($this->results === false)
- {
- $this->message = 'Запрос не выполнен: ' . $sphinx->GetLastError();
- }
- else
- //если все путём, то достаем информацию по id индекса
- //и выводим ее в template
- $this->items = $this->retrieveResultRows($this->results);
* This source code was highlighted with Source Code Highlighter.
Надеюсь, из моего описания можно оценить все прелести Sphinx, я рассказал далеко не обо всех его возможностях, остальное при желании вы уже сможете самостоятельно изучить.
PS: Просьба к тем у кого достаточно кармы — создайте блог Sphinx, я бы перенес туда статью.
PS2: Спасибо всем! Блог создан, топик перенесен туда.