190 читателей, 7 постов
Администрация
Модераторы
Cвободно распространяемая система полнотекстового поиска.
В одном из текущих проектов возникла задача поиска по данным разного типа, которая была успешно решена с помощью зарекомендовавшей себя поисковой машины 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.
- 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 момента:
- sql_attr_uint = category_id
- sql_attr_uint = row_type
- sql_attr_timestamp = created_at
* This source code was highlighted with Source Code Highlighter.
- sql_query_info = SELECT title, descr \
- FROM geo_object WHERE id = ($id - 1) / 10
* This source code was highlighted with Source Code Highlighter.
- source = category
- source = geo_object
- source = article
* This source code was highlighted with Source Code Highlighter.
- path = /var/data/sphinx/site_search
- docinfo = extern
* This source code was highlighted with Source Code Highlighter.указываются параметры хранения индекса и полный путь к нему.
- morphology = stem_en, stem_ru
* This source code was highlighted with Source Code Highlighter.
- muxx:~ muxx$ sudo searchd --stop
- Sphinx 0.9.8.1-release (r1533)
- Copyright © 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 © 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 © 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 © 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.
- $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.
комментарии (39)
1. БД сможет использовать только полнотекстовый индекс, а если у вас еще будет фильтрация по другим параметрам, это будет производиться без индексов, т.е. медленно. В статье я поставил простую задачу, на самом деле мне нужно было фильтровать записи по десятку параметров;
2. В Sphinx существует множество режимов поиска, в каждом из них идут свои «бонусы»;
3. С помощью прстого указания между режимами SPH_MATCH_ALL, SPH_MATCH_ANY можно задать поиск по всех словам из фразы или по любому из слов и все это с учетом морфологии;
4. В режиме SPH_MATCH_EXTENDED, помимо стандартных операторов AND, OR, NOT можно задавать близость слов: «example program»~5 – такое условие говорит Sphinx, что между словами example и program должно быть не более 5-ти слов; а также порог на количество слов: «Петя Пупкин пошел гулять по лесу»/3 возвращает те записи, где встречается хотя бы 3 из 6 слов в заданной фразе.
5. В Sphinx введен режим SPH_MATCH_FULLSCAN, когда поисковая фраза пустая и заданы только фильтры и группирование. В документации пишут, что выборка записей по фильтрам идет в некоторых случаях даже быстрее, чем в MySQL. Я также перевел некоторые запросы с большими условиями на Sphinx, чтобы разгрузить БД.
6. В индекс может понадобиться включить что-то не хранящееся в БД, а Sphinx, как я писал, умеет искать и по xml, html, почте и др.
А кармы, я думаю, у вас будет скоро много(я принял в этом участие :) ) и сможете сами создать блог)
Спасибо за статью, для своих проектов на symfony использовали lucene. Теперь, думаю стоит посмотреть в сторону Sphinx.
– Linux 2.4.x, 2.6.x (various distributions)
– Windows 2000, XP
– FreeBSD 4.x, 5.x, 6.x
– NetBSD 1.6, 3.0
– Solaris 9, 11
– Mac OS X
те же самые 3 запроса, которые использовались при индексации, объединённые через UNION.
это предложение преподносится в контексте того, что «как круто, сфинкс умеет индексировать из N источников». Т.е. фичей преподносится возможность индексации кучи сорсов, а не производительность — с этой точки зрения мой комментарий вполне уместен: mysql через UNION может делать то же самое. м?
по поводу скорости — отлично представляю насколько сфинкс куче mysql.
И второе преимущество различных индексов — не приходится вводить лишнии манипуляции с id-шниками
source generic {конфиг коннекта}source foo : generic {конфиг для нашего конктретного source}
View — я бы не советовал использовать, так как при достаточно большом обьеме данных, даже используя частичную выборку из view вы положите базу данных ($start, $stop). Плюс если вы хотите действительно не напрягать mysql то не слудет при обращении в view делать какие либо условия на больших обьемах данных (например: where source = 'article', limit и тд, но это только пример 8) ).
Стоит делать отдельные индексы (article, category, geo).
Какие плюсы мы получим:
1. индексы могут содержать разное количество полей, часть полей могут быть в будущем использованы для специфического поиска к примеру по категориям.
2. можно гибко работать с весами для разных индесков
3. можно производить реиндексацию только изменившегося индекса (при полной реиндексации), а это снижение нагрузки. В данном случае чаще будет обновляться article, чем category и geo.
Ну и наспоследок, при использовании Sphinx сразу закладывайте в архитектуру работу с частичными индексами (индексы содержащие изменившиеся данные за определенный промежуток времени) и их обьединением с основным индексом.
PS. Эту статью меня попросили перевести на английский, надеюсь, это поспособствует популяризации Sphinx за рубежом :)
www.sphinxsearch.com/forum/view.html?id=2070
В индексе не было ни одного атрибута, поэтому возникла ошибка. Добавление атрибута решило проблему.
Одним словом вывод один — для работы со Sphinx нужно хорошо уметь работать напильником :).
С вашей версией обёртки ознакомиться где-нибудь можно?
К примеру:
21 * 10 + 1 = 211
22 * 10 + 1 = 221
Собственно хочется спросить — и чо? В чём разница между 21, 22 и 211, 221?
Если этого не хватает, меняем 10 на 100, и проблема решается на долгую перспективу.
Ведь если, грубо говоря, в одной таблице записей не больше 50, а во второй больше 500, то даже делая id * 10 + 1 as id, то идентификаторы всё равно будут пересекаться… т.е. это далеко не универсальное решение. Так что эта «перспектива» весьма туманна. Я как то читал на баше пост, где тип жаловался на инфляцию и говорил, что ему в его какой то биллинговой софтине пришлось все типы дынных int заменить на long из-за того, что цены быстро растут)))
P.S. Всё равно не ясно, что в этой комбинации решает еденица — id * 10 + 1? ))
translated.by/you/sphinx-0-9-9-reference-manual/trans/