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

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

Sphinx - Полезные полезности

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

Кодировки

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

Результат может выглядеть так:

Или так:

Решается эта проблема достаточно просто. Во-первых, в файле конфигурации необходимо при помощи опции charset_type указать туже кодировку, которая используется в источнике данных, а во-вторых, эту же кодировку необходимо указать при подключении к Sphinx. При использовании в качестве источника базы данных MS SQL Server, в которой данные хранятся в кодировке unicode, также необходимо использовать опцию mssql_unicode.

Например, если мы индексируем базу данных MS SQL Server, где данные хранятся в столбцах с типами данных char, varchar или text то в опции charset_type необходимо указывать значение sbcs (или вообще ничего не указывать, поскольку это значение используется по умолчанию), а при подключении к Sphinx указывать кодировку windows-1251:

c:\mysql\bin\mysql -h 127.0.0.1 -P 9306 --default-character-set=cp1251

Если же мы индексируем xml-файл в кодировке utf-8 (индексировать xml-файлы можно только в этой кодировке) или базу данных MS SQL Server, где данные хранятся в столбцах с типами данных nchar, nvarchar или ntext то в опции charset_type необходимо указывать значение utf-8, в опции mssql_unicode - значение 1 (только для MS SQL Server), а при подключении к Sphinx указывать кодировку utf-8:

c:\mysql\bin\mysql -h 127.0.0.1 -P 9306 --default-character-set=utf8

Указывание кодировки при подключении к Sphinx относится не только к консольному клиенту для MySQL, но и к любому коннектору, который вы захотите использовать. Например, при использовании коннектора для .NET, кодировка в строке подключения указывается так:

Server=127.0.0.1;Port=9306;Character Set=utf8

После того, как мы правильно укажем все настройки, результат будет выглядеть следующим образом:

Опция «max_matches»

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

Задаётся это ограничение в файле конфигурации в блоке searchd при помощи опции max_matches:

searchd
{
    ...
    max_matches = 10000
}

Если в файле конфигурации эта опция не указана, то она считается равной 1000.

Помимо файла конфигурации опция max_matches может быть задана для каждого запроса в отдельности при помощи выражения option:

select * from product order by price desc limit 100, 20 option max_matches = 10000;

При этом значение опции max_matches, заданное для конкретного запроса, не может превышать то значение, которое задано в файле конфигурации (или значение по умолчанию, если в файле конфигурации оно не указано).

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

Стоит также отметить, что данная опция может очень сильно влиять на производительнось, поэтому следует с осторожностью использовать значения, которые превышают значение по умолчанию.

Команда «show meta»

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

select * from product where match('men sports shorts');
show meta;

Выполненная сразу после другой команды, команда show meta возвращает таблицу с несколькими значениями.

Значения total_found и total содержат количество документов, найденных в индексе. Разница между ними заключается в том, что total учитывает только те документы, которые остались после применения огранияения max_matches, поэтому это значение не может больше значения max_matches.

Значение time содержит время выполнения запроса в секундах.

Значения keyword, docs и hits с индексами содержат слова, по которым производился поиск, количество документов, в которых это слово упоминается и полное количество упоминаний слова во всех документах (одно и тоже слово может упоминаться в документе несколько раз). Обратите внимание, что поиск может выполняться не по тому слову, которое указано в операторе match, а по его нормализованной форме. Например, в запросе выше мы искали по словам «sports» и «shorts», но команда show meta вернула нам слова «sport» и «short». Процесс нормализации слов называется стеммингом и нужен, чтобы Sphinx мог находить слова в разных словоформах.

Команда «call keywords»

В некоторых случаях может потребоваться узнать, как Sphinx «видит» поисковую фразу: как разбивает её на слова, как эти слова нормализуются и сколько раз каждое слово встречается в индексе. Получить эту статистику можно и с помощью команды show meta, однако, если результаты поиска при этом не нужны, проще воспользоваться командой call keywords.

call keywords('men sports shorts', 'product', 1);

Эта команда получает три параметра: поисковую фразу, название индекса и флаг, показывающий, нужно ли возвращать колонки docs и hits.

Команды «show tables» и «describe»

Ещё две полезные команды, которые могут потребоватьбся, чтобы получить информацию о существующих индексах: show tables и describe.

Команда show tables возвращает список существующих индексов вместе с их типами: локальные (local), индексы реального времени (rt), распределённые (distributed).

show tables;

Команда describe возвращает список полей и атрибутов в указанном индексе.

describe product;

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

  1. Про кодировки - не столь важно, как они выглядят на экране (в конце концов, никто не мешает в клиенте выполнить set names=...). Гораздо важнее указать при индексации правильный charset_type!

    Исторически всё было в однобайтных кодировках, и потому по дефолту charset_type в сфинксе - sbcs (single-byte character set). При этом автоматически работает встроенный charset_table, который переваривает латинницу и русский язык в кодировке cp1251 (виндовое прошлое!). Остальные символы при токенизации считаются разделителями (пробелами). Это всё работает прозрачно, так что обычному пользователю вроде как вообще незачем трогать charset_type и charset_table (они кажутся непонятными и большими. Вроде как бестолку загромождают конфиг). Но годы летят, прогресс не стоит на месте - и в настоящий момент работа сфинкса с другими языками (кроме только русского и английского) стала вполне обычной. На линуксе сейчас гораздо чаще встретишь данные в utf-8, чем в однобайтной кодировке!

    И вот тут появляются грабли: если задать в конфиге источник, который индексирует данные в utf-8, но при этом забыть явно указать charset_type=utf-8 - да, индекс создастся. И даже будет что-то искать. Но ооо-чень медленно! Грабли настолько часты, что даже породили миф - дескать, "сфинкс тормозит!" А механизм этих "тормозов" очень прост: сфинкс пытается токенизировать поток utf-8 с помощью кодировки 1251. Байты, случайно совпавшие с русскими буквами (у вас, кстати, можно разглядеть их в скриншоте дампа) индексируются. А не являющиеся - считаются пробелами и разделяют слова. В итоге по факту получается индекс из нескольких однобуквенных "слов" русского алфавита, которые можно разглядеть в потоке utf-8. А поиск в сфинксе заточен именно на разные слова в словаре. Они образуют большой словарь с хэшем, который позволяет найти любое слово практически мгновенно. А вот дальше - по найденным словам извлекаются цепочки документов и хитов в документах, которые уже как-то взаимодействуют между собой и образуют финальный резултсет. При этом поток документов/хитов по сути обрабатывается линейно, штука за штукой, а это гораздо дОльше. Представьте, что если вместо поиска документов, где есть слово "мир" (операция "в лоб" - извлекаем из словаря цепочку документов и мгновенно выдаём результат) вы делаете три поиска - на "м", "и" и "р", и потом пересекаете результаты? Такой поиск мало того, что очень медленный, но ещё и не всегда точен (у некоторых букв и составленных из них слов в дампе utf-8 может вообще не оказаться ни одного русского символа. И тогда они вообще пропадают из поиска).
    В общем - всего одна строчка, зато какой эффект!

    ОтветитьУдалить
  2. Интересно пишите, Игорь. Есть вопрос по поводу SphinxQL.

    Я поставил себе sphinx на локальный компьютер чтобы попрактиковаться. Не сразу, но все заработало (в том числе благодаря вашим статьям). Но меня смущает одна вещь. У меня одновременно на локальном сайте работает либо запросы к MySQL (searchd.exe отключен), либо к SphinxQL (searchd.exe включен). Но сайт-то пользуется и тем и другим. И если на локальной машине я могу вкл/выкл. searchd.exe когда нужно, то на для внешнего сервера это однозначно абсурд.

    Это так и должно быть и я должен выбрать какой-то один способ работы с БД или я сделал что-то не так?

    п/с. Я так понимаю все из-за listen = 127.0.0.1:3306:mysql41.

    ОтветитьУдалить
  3. Порт 3306 по умолчанию используется для бд mysql, поэтому она со сфинксом не могут этот порт использовать одновременно.

    Нужно сфинксу назначить любой другой порт, обычно везде указывают 9603:
    listen = 127.0.0.1:9603:mysql41



    Думаю, дело в этом.

    ОтветитьУдалить
  4. А лучше взять 9306 для sphinxql и 9312 для api. Это официальные порты сфинкса в IANA (см. http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.txt)

    ОтветитьУдалить
  5. Большое спасибо за комментарий, при неправильном указании charset_type действительно ищет всякую чушь, я этого раньше не замечал, поскольку всегда и везде использовал utf-8. Поправил статью.

    set names относится только к mysql, в mssql нельзя при подключении указывать кодировку, т.к. там разные столбцы сами по себе могут иметь разные кодировки, поэтому там используется опция mssql_unicode.

    Тем не менее, как я понял, если в источнике данных используется определённая кодировка, то её надо использовать везде: и в charset_type и при подключении к sphinx. Нельзя, например, проиндексировать источник данных в windows-1251, а индекс сохранить в utf-8.

    ОтветитьУдалить
  6. Даже не кодировка, а именно _тип_ кодировки. Их, в общем-то, как раз ровно два.
    На самом деле можно и utf-8 проиндексировать как однобайтовую - но важно тогда в charset-table указать все его видимые символы. Правда, при этом не будут работать стеммеры/лемматизаторы, поскольку они такой финт просто не поймут.
    Ну и правильность кодировки можно всегда проверить, выполнив сразу после запроса команду show meta. Там будет видно либо реальные слова, либо одно-двухбуквенные ошмётки

    ОтветитьУдалить
  7. Здравствуйте! Скажите, как поступить в случае с MS SQL базой, в которой есть как поля таблиц как nvarchar, так и varchar. при коннекшн --default-character-set=utf8 отображается правильно только часть таблиц. А при --default-character-set=cp1251 - не ищет кириллицу

    ОтветитьУдалить
  8. При индексации в sql запросах делать приведение к nvarchar.
    select ID, cast(Name as nvarchar) as Name from ...
    С учётом того, что последние вевсии сфинкса поддерживают только utf-8, это будет правильнее всего.

    ОтветитьУдалить
  9. Спасибо огромное, действительно помогло, очень выручили

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

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