Игорь Чакрыгин Игорь Чакрыгин

У любой задачи существует по крайней мере одно очевидное и невероятно простое для понимания неправильное решение

Sphinx - Полезные полезности (Часть 2)

Мне кажется, что про Sphinx я уже рассказал всё что только можно, по крайней мере из того, о чём я знаю сам. В качестве завершения этого цикла статей я решил собрать ещё одну небольшую подборку особенностей, о которых не стоит забывать в работе.

Фильтрация посредством полнотекстового поиска

Предположим, что у нас есть индекс с товарами, каждый из которых может находиться в одной или нескольких категориях, и нам нужно получить товары из определённой категории. Это вполне можно сделать при помощи фильтрации по атрибуту.

select * from product where category_id = 42;

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

Вместо этого атрибут category_id можно заменить на одноимённое поле и выполнять фильтрацию по нему, проиндексировав идентификаторы каталогов как обычный текст.

select * from product where match('@category_id 42');

Такой запрос зачастую будет выполняться намного быстрее, а Sphinx будет использовать меньше памяти. Если же атрибут всё-таки необходим, например, для группировки по нему или для того, чтобы возвращать его в качестве одной из колонок, то можно создать атрибут и поле с одинаковыми именами. Поле будет использоваться для фильтрации, а атрибут - для всего остального.

Кстати, вот небольшая статья с примерами на эту тему: Sphinx search performance optimization: attribute-based filters.

Использование rt-индексов

Помимо обычных дисковых индексов, обновление которых обычно производится при помощи схемы main+delta, Sphinx также поддерживает rt-индексы, которые позволяют добавлять, обновлять и удалять документы как обычная таблица в базе данных, не теряя при этом возможность полнотекстового поиска. Так может стоит уже забыть про дисковые индексы?

На самом деле нет. В качестве подтверждения хочу привести статью, в которой сравнивается скорость выполнения запросов дисковых и rt-индексов: Тестирование производительности Обычных, Real Time и Смешанных индексов Sphinx Search.

Суть её в том, что на малых объёмах данных rt-индексы по скорости не уступают дисковым, однако на больших начинают сильно проигрывать. Таким образом, если вы хотите использовать всю гибкость rt-индексов, то вам, возможно, стоит задуматься о схеме main+delta, где большой main-индекс является дисковым, а маленький delta-индекс - rt-индексом.

Обновление атрибутов в дисковых индексах

Для обновления атрибутов индекса при помощи команды update вовсе не обязательно использовать rt-индексы. Дисковые индексы тоже её поддерживают, правда с большими ограничениями, позволяя обновлять любые атрибуты кроме строковых (в том числе и multi-valued атрибуты).

Таким образом, если у нас, например, есть индекс с товарами и мы хотим в течении дня обновлять только их цену и доступность, то мы можем делать это при помощи команды update.

update product set price = 94.90, avalability = 1 where id = 42;

Пакетные запросы

Что если помимо найденных в нашем индексе товаров мы захотим показать пользователю информацию о том, сколько товаров каждого цвета и размера было найдено? Для этого нам потребуется выполнить три запроса.

select * from product where match('@catalog_id 42');
select color, count(*) from product where match('@catalog_id 42') group by color;
select size, count(*) from product where match('@catalog_id 42') group by size;

Такие запросы крайне рекомендуется отправлять в Sphinx одним пакетом (т.е. одной строкой), поскольку в этом случае они могут быть оптимизированы и выполнены вместе. Чтобы узнать, производилась ли оптимизация, достаточно посмотреть файл с логами поисковых запросов.

[...] 0.042 sec x3 [...] ...
[...] 0.042 sec x3 [...] ...
[...] 0.042 sec x3 [...] ...

Запись x3 означает, что данный запрос был оптимизирован и обработан в пакете из трёх запросов.

SENTENCE

Ну и на последок хочу рассказать о не очень серьёзном баге, который тем не менее встречается на очень многих сайтах (даже на Хабрахабре), которые используют Sphinx для поиска.

Что будет, если пользователь на сайте введёт в поисовую строку запрос «SENTENCE» (заглавными буквами)? Если этот запрос без изменений передаётся в Sphinx и используется в операторе match, то Sphinx при этом выдаст ошибку. В лучшем случае, если ошибка будет перехвачена, пользователь увидит, что ничего не было найдено. В худшем же случае (и такие сайты тоже бывают), пользователь увидит страницу с ошибкой.

Происходит эта ошибка, потому что слово «SENTENCE», написанное заглавными буквами, считается специальным оператором, который использутся для поиска слов в одном предложении, и не может быть использовано отдельно. Если вы не хотите, чтобы пользователь имел возможность использовать расширенный синтаксис запросов, не забывайте тщательно экранировать все специальные символы.

Заключение

Ну вот, собственно, и всё, что я хотел рассказать про Sphinx в рамках этого цикла статей. Надеюсь, что эта информация обязательно кому-нибудь пригодится.

10 комментариев

  1. PARAGRAPH - тоже ключевое слово.

    ОтветитьУдалить
  2. Ренат Ганиев20 февраля 2014 г., 11:10

    Добрый день! Подскажите, пожалуйста, как настроить Sphinx, чтобы он искал точное совпадение по определенному полю. Спасибо!

    ОтветитьУдалить
  3. Включить индексацию ненормализованых слов (если нужно)
    http://sphinxsearch.com/docs/2.2.2/conf-index-exact-words.html



    в match перед словом ставить равно
    match('@somefield =красный')

    ОтветитьУдалить
  4. Доброго времени суток!

    А вы как SENTENCE экранируете? Просто str_replace? В сфинксклиенте есть EscapeString, но он почему-то не экранирует спец операторы https://code.google.com/p/sphinxsearch/source/browse/trunk/api/sphinxapi.php?r=4522#1627

    ОтветитьУдалить
  5. Обычно я пользовательский запрос просто перевожу в lowercase.
    А за EscapeString спасибо, надо будет иметь ввиду, т.к. там есть все правила для экранирования спец-символов.

    ОтветитьУдалить
  6. Игорь, благодарю за статью. Не мог бы подробнее описать как делать пакетный запрос? Мне как раз надо выгружать данные о то каких категорий найдено в каком количестве, но я добавил запрос в индекс и индекс не пересчитывается, по-моему я не туда добавил. Может быть надо сделать отдельный ресурс и к нему подвязать индекс?

    ОтветитьУдалить
  7. Я ничего не понял =)
    Пакетный запрос - это просто несколько запросов SphinxQL (разделённые точкой с запятой), которые выполняются как один. Sphinx их автоматически оптимизирует, если это возможно. Если это происходит, то запрос может выполняться значительно быстрее.
    Такая оптимизация обычно делается, если все запросы в пакете идут к одному индексу, имеют одинаковый match, но имеют группировку по разным атрибутам.

    ОтветитьУдалить
  8. Как на примере обращения к API Sphinx реализовать пакетный запрос?

    ОтветитьУдалить
  9. Я никогда не пользовался API Sphinx, поэтому не могу сказать.
    Даже сами разработчики его не рекомендуют использовать.

    ОтветитьУдалить
  10. Спасибо, сегодня сам сделал то что не получалоcь через АПИ, на SphinxQL.

    ОтветитьУдалить

© Игорь Чакрыгин. Все права защищены при помощи чёрной магии. Технологии Blogger.