Sphinx Technologies Inc
Компания
23,66
рейтинг
3 июня 2009 в 12:34

Разработка → Как готовить SphinxQL

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

Обе фичи добавлены в версии 0.9.9-rc2, опубликованной в начале апреля 2009го. Версия традиционно (слишком) стабильная, известных серьезных багов нету, тесты проходятся, итп. Отважные люди, а также коммерческие клиенты с контрактами про поддержку, уже успешно используют в продакшне, несмотря на отличный от «release» тег.

1я мега-фича. Теперь Sphinx поддерживает сетевой протокол MySQL (внутренней версии номер 10, которую поддерживают все версии сервера и клиента, начиная с MySQL 4.1 и по MySQL 5.x включительно).



Что это означает в переводе обратно на руский? По существу, появился третий метод доступа к searchd, при этом особенно простой и доступный. Ранее делать поисковые запросы можно было либо через нативные PHP/Perl/Java/Ruby/… API, либо через SphinxSE. Теперь их вдобавок к этому можно делать через любой клиент для MySQL, включая всем привычный клиент в командной строке, PHP-шные вызовы mysql_connect() и mysql_query(), Perl DBI, и т.д. При этом он совместим со всеми клиентами, начиная от MySQL 4.1 и по настоящий момент.

Те. API и SphinxSE становятся необязательными, многое (в перспективе вообще все) можно делать просто «как бы» MySQL запросами. Кроме того, автоматически появляется поддержка персистентных соединений. Это важно в случае, когда обслуживаются очень быстрые в среднем запросы, но таких запросов МНОГО. Оверхед на сетевое соединение и fork() типично укладывается в интервал от 0.001 до 0.01 секунды, в зависимости от размера индексов, операционной системы, и т.д. В случае 1M запросов в сутки и 0.001 секунды оверхеда на запрос это лишняя 1000 секунд CPU, что плюс-минус неважно. В случае 50M запросов и 0.01 сек/запрос имеем таки уже примерно 5 суток процессорного времени… есть, за что побороться.

Еще пара важных моментов про сетевые соединения. Во-1х, все соединения, независимо от протокола, ограничиваются сверху лимитом max_children. Во-2х, в текущей версии соединения по протоколу MySQL считаются интерактивными и таймаут для них автоматически повышается до 900 секунд (вместо 1 секунды по нативному протоколу Sphinx). Соотв-но будьте аккуратны с лимитами, иначе можно случайно задушить searchd простаивающими соединениями.

Хватит теории, даешь практику. Как уже сконфигурировать и попробовать? Нужно добавить буквально одну строчку с указанием адреса интерфейса, порта и собственно имени протокола в sphinx.conf:

listen = localhost:3307:mysql41


После этого перезапускаем searchd, и ура, можно цепляться к нему известным клиентом. Обратите внимание на ключик -h 127.0.0.1, под юниксами без него не взлетит: libmysqlclient по умолчанию коннектится на UNIX-сокет, а не TCP-порт, поэтому просто -P 3307 недостаточно. Проверять проще всего по полю Server version.

$ searchd --stop
...
$ searchd
...
$ mysql -h 127.0.0.1 -P 3307
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 0.9.9-rc2 (r1785)

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql>


MySQL клиент отсылает строки на сервер без проверок и изменений, поэтому технически возможно придумать какой-то свой новый и абсолютно уникальный язык запросов. Однако человечество уже давно придумало SQL, и многие его немного знают. Поэтому вместо очередного велосипеда с квадратными колесами была приделана…

2я мега-фича. При работе через MySQL протокол, Sphinx поддерживает обычный SQL-синтаксис. Разумеется, с кучей ограничений; о полной поддержке SQL'92 речь пока не идет. Впрочем, уже поддерживаемое подмножество SQL позволяет делать самое главное: писать практически любые поисковые запросы. Поддержка для остальной функциональности searchd (обновления атрибутов, создание сниппетов, итп) тоже будет постепенно добавляться, по мере развития проекта.

Весь поиск делается посредством оператора SELECT, при этом все привычные клаузы SELECT поддерживаются практически полностью. Можно вычислять произвольные выражения; поддерживаются WHERE, GROUP BY, ORDER BY, LIMIT, и так далее. Собственно, Sphinx это все и так умел последние года два или три; просто теперь еще и запрос можно написать в привычном виде. Кроме того, поддерживается ряд новых, специфичных для Sphinx расширений синтаксиса. Вот пример:

SELECT *, @weight+userkarma*1000 AS myweight FROM mainindex, deltaindex
   WHERE MATCH('@title hello @content world')
      AND userid NOT IN (123,456,98,76,54)
      AND hidden!=0
      AND postkarma>=5
   GROUP BY userid
   WITHIN GROUP ORDER BY myweight DESC
   ORDER BY myweight DESC, postdate ASC
   LIMIT 100,20


Синтаксис пока несколько более жесткий, чем обычный SQL, и не без своих шероховатостей.
  • Выражения можно перечислять только сразу после SELECT.
  • У каждого сложного выражения обязан быть явный алиас, указанный через AS.
  • При этом COUNT(*) и COUNT(DISTINCT col), наоборот, алиасить нельзя. Ссылаться на них в выражениях придется по магическим именам count и distinct соответственно, равно как на id и на weight.
  • В клаузах WHERE/GROUP BY/ORDER BY выражения задавать нельзя, только ссылаться на существующие колонки либо выражения.
  • Для ORDER BY обязательно указывать явный порядок (ASC либо DESC).
  • Есть ряд ограничений на условия WHERE, тк. они транслируется непосредственно в фильтры; самое заметное заключется в том, что не поддерживается OR, только AND.

Однако пользоваться уже таки можно, невзирая. Все перечисленное собираемся потихоньку поправлять, приводя синтаксис ближе и ближе к SQL.

Есть и специально задуманные отличия синтаксиса.
  • Перечисление индексов через запятую означает Sphinx-style выборку из нескольких индексов, а не SQL-style JOIN.
  • Спец-функция MATCH() передает полнотекстовый запрос и может встречаться не более одного раза. В случае, если ее нету, включается т.н. full scan режим, который перебирает все существующие в индексе записи, применяя WHERE/GROUP BY/ORDER BY.
  • Всегда есть неявный LIMIT, по умолчанию LIMIT 0,20.
  • Добавлено расширение WITHIN GROUP ORDER BY, которое позволяет контролировать, какой «лучший» элемент выбрать внутри группы при использовании GROUP BY.


Кроме полноценного SELECT, поддерживается еще ряд SQL операторов попроще.

Есть оператор SHOW WARNINGS, который показывает предупреждения, сгенерированные предыдущим запросом. Сообщение об ошибке сразу возвращается «и так» и доступно через вызов mysql_error(); а вот предупреждения придется вынимать отдельным запросом. Впрочем, запускать этот отдельный запрос можно и нужно опционально, предварительно проверив результат mysql_warning_count() либо mysql_info().

mysql> select * from dist1;
+------+--------+----------+------------+
| id   | weight | group_id | date_added |
+------+--------+----------+------------+
|    1 |      1 |        1 | 1231721236 |
|    2 |      1 |        1 | 1231721236 |
|    3 |      1 |        2 | 1231721236 |
|    4 |      1 |        2 | 1231721236 |
+------+--------+----------+------------+
4 rows in set, 1 warning (1.13 sec)

mysql> show warnings;
+---------+------+--------------------------------------------------------+
| Level   | Code | Message                                                |
+---------+------+--------------------------------------------------------+
| warning | 1000 | index dist1: agent localhost:3313: connect() timed out |
+---------+------+--------------------------------------------------------+
1 row in set (0.00 sec)


А вот так, кстати, выглядит ошибка.

mysql> select * from test;
ERROR 1064 (42000): unknown local index 'test' in search request


Есть оператор SHOW STATUS, который показывает всякую разную статистику. Счетчики, понятно, отличные от MySQL; но формат выдачи тот же. LIKE пока не поддерживается.

mysql> show status;
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| uptime             | 1018  |
| connections        | 6     |
| maxed_out          | 0     |
| command_search     | 0     |
| command_excerpt    | 0     |
| command_update     | 0     |
| command_keywords   | 0     |
| command_persist    | 0     |
| command_status     | 0     |
| agent_connect      | 1     |
| agent_retry        | 0     |
| queries            | 1     |
| dist_queries       | 1     |
| query_wall         | 1.123 |
| query_cpu          | OFF   |
| dist_wall          | 1.123 |
| dist_local         | 0.100 |
| dist_wait          | 1.006 |
| query_reads        | OFF   |
| query_readkb       | OFF   |
| query_readtime     | OFF   |
| avg_query_wall     | 1.123 |
| avg_query_cpu      | OFF   |
| avg_dist_wall      | 1.123 |
| avg_dist_local     | 0.100 |
| avg_dist_wait      | 1.006 |
| avg_query_reads    | OFF   |
| avg_query_readkb   | OFF   |
| avg_query_readtime | OFF   |
+--------------------+-------+
29 rows in set (0.00 sec)


Некоторые счетчики в примере выше возвращают OFF. Это потому, что searchd был запущен без спец-ключей --iostats --cpustats. По умолчанию они отключены, тк. теоретически добавляют оверхедов (точно добавляют лишних вызовов gettimeofday() и прочих clock_gettime() соответственно, например). Сколько тех оверхедов получается практически, никто пока не мерил. Считаю, отличная возможность стать первопроходцем!

Уже есть некоторые известные проблемы. Вроде (вроде) серьезных пока ни одной. Самая на мой взгляд забавная: говорят, некоторые фреймворки обязательно отсылают всякие ненужные запросы типа SET NAMES при коннекте, и перехотеть им никак не удается. Запросы, понятно, падают; вслед за ними и фреймворки. Ну, дойдут руки, добавим заглушку.

Зато особенно сильно упростился перенос некоторых видов SQL запросов из MySQL в Sphinx. На запросах, которые лопатят все имеющиеся данные, Sphinx получается до 1.5-3 раз быстрее (раз бенчмарк, два бенчмарк). При этом раньше надо было их переписывать на API вызовы, а теперь не надо.

Причем это мы только по одному ядру, а можно и по два.
Автор: @shodan
Sphinx Technologies Inc
рейтинг 23,66
Компания прекратила активность на сайте

Комментарии (80)

  • +1
    А я как раз сейчас настраиваю работу SphinxQL, вовремя вы
  • –1
    Шикарно :)
  • –2
    замечательно, зажигательно, познавательно. побольше бы таких статей. спасибо)
  • –2
    очень изящно, и со вкусом приготовлено, спасибо!
  • +3
    Когда-же MySQL возьмет все лучшее от Sphinx…
    • +2
      Продвинем идею в MariaDB?
  • 0
    Да это просто праздник какой-то!!! Как это всё здорово! И особенно развяжет руки то, что теперь OR тоже поддерживается.
    • 0
      Вынужден огорчить, но в статье написано обратное:

      «Есть ряд ограничений на условия WHERE, тк. они транслируется непосредственно в фильтры; самое заметное заключется в том, что не поддерживается OR, только AND.»
      • 0
        Все так.
        Но можно засунуть в выражение, а затем засунуть выражение в WHERE.
      • 0
        Ой, да… Видимо, я увидел то, что очень хотел увидеть.
  • 0
    Кстати, у меня вопрос есть. С версии 0.99-rc1 появилась фича SetSelect, она, как я понимаю, позволяет использовать OR при фильстрации нужных результатов поиска. Почему же OR не добавили в запросы? Это, наверное, амая востребованная фича сфинкса.
    • +1
      Почему не добавили, все есть.
      Тут некоторое непонимание, плюс я плохо объясняю.

      OR() вполне можно использовать в выражениях (!) — это которые сразу после SELECT.
      В строчке WHERE напрямую нельзя — но можно посчитать такое выражение и сунуть его в WHERE.
  • +1
    Подскажите, поддерживается ли SUM() и AVG() c GROUP BY?
    • +1
      Да.
      MIN() MAX() еще тоже.
  • +1
    SET NAMES — очень важная команда, лучше бы не заглушку, а полноценную реализацию )
    • +1
      Готовы проспонсировать разработку? :)
      • 0
        В обмен на что-нибудь — не вопрос )

        Сам я не использую Сфинкс.
        • 0
          «Спонсирование в обмен» это как-то сложно для меня! :)
          • 0
            Странно, как же это не сложно для других людей, для многих проектов?)

            Например, возьмите любую выставку, у каждой из них много спонсоров. И причём спонсоры тоже имеют некий профит…
            • 0
              Сфинкс это такой софт (бесплатный, открытый, итп).
              Причем тут выставки?..
              • 0
                Ближайшие аналоги из мира ПО — donation-ware.

                Вообще форм взаимодействия спонсоров и разработчиков много, просто лень перечислять.
                • 0
                  Donations не работают, если чо.
                  Примерно совсем.

                  В общем, я какую мысль-то хотел донести.
                  Если кому вдруг нужно приделать какие-то фичи, пишите, можно обсуждать.
  • +1
    Читаю мануал по Сфинксу, не могу остановиться)

    С чего авторы вдруг такую вещь решили бесплатно раздавать? Или коммерческие поисковики еще мощнее что ли?
    • +3
      мм… понимаете… я думаю что это just for fun… ну и опыт огромный…
    • +3
      Продать задорого пока не получается, не умеем :(
      Приходится раздавать бесплатно поэтому.
    • 0
      Например существует схема заработка на продажах продукта, а другая — заработков на поддержке. — Чем не вариант?
      • 0
        Тем что можно бы зарабатывать и на том и на том :) А то понимаешь все хотят бесплаьно поюзать. а денежкой делиться не хотят.
        • 0
          Начни с себя, погладь кота купи саппорта!!!
  • +1
    А раскажите еще об поддержке Drizzle и про участие сфинксового движка в новом проекте веб-поисковика опенсорсного на базе Drizzle/Gearman/Sphinx
    • 0
      Они сами!
  • +1
    как можно hightlight найденых слов настроить в результатах поиска?
    • +1
      Через MYSQL протокол никак пока.
      Через нативные API есть BuildExcerpts()
      • 0
        А есть какие-нибудь планы, в которых это «пока» превращается в «уже»? Очень уж AutoSuggest хочется делать.
        • 0
          Эээ.
          А какая связь между генерацией сниппетов (highlight) и тем autosuggest?
          • 0
            Возможно, стреляю не из того ружья не по тем воробьям:

            Хочу сделать поиск с AutoSuggest – ну в точности как здесь на Хабре.
            Для этого приходится делать поиск фразы по мере ввода с добавлением звезды: ПАРО* найдет нам все страницы, на которых есть ПАРОХОДЫ, ПАРОВОЗЫ и ПАРОМЫ, после чего к тексту каждой найденной странице применяем BuildExcerpts и полученные в результате «огрызки» показываем в выпадающем списке.

            Другого придумать не смог, а этот способ меня не устраивает по двум причинам:

            а) BuildExcerpts все же вытаскивает не найденные слова в исходной форме (именительный падеж единственное число), а буквально куски текста, то есть у меня получаются: «белый ПАРОход пришвартовался», «ожидаемые ПАРОходы приплыли» и «как оказалось ПАРОходами заинтересовался» – это выглядит неприятно, хочется получать фрагменты начиная со слова «ПАРОход».

            б) База данных у меня такая, что мне приходится писать довольно большой и сложный запрос в sql_query для индексатора. Для того, чтобы скормить текст в BuildExcerpts, мне приходится писать этот запрос заново, чтобы получить то, что получал индексатор. Было бы здорово, если бы индексатор умел где-нибудь сохранить тот текст, который он индексировал, в своем первозданном виде.

            Или я совсем не так это делаю?
            • 0
              Совсем не так.
              indexer --buildstops + засосать каждую строчку в базу + искать в ней (необязательно Сфинксом).

              Подробнее распишу в следующем посте, пожалуй.
              Будет хорошее дополнение к нему.
              • 0
                Спасибо, было бы очень здорово потому что, кажется, не вполне очевидное решение.
                Или если есть в документации – ткните носом, пожалуйста.
                • 0
                  В документации не.
                  И вряд ли будет.

                  Там описаны все кубики.
                  Но как их складывать в пирамидки, это отдельное :)
                  • 0
                    Впрочем, да – вот вам и способ монетизации :)
                    Спасибо, что помогаете забесплатно.

                    То есть indexer --buildstops 100000000 примерно так, да? Остальное ясно.
                    • 0
                      Плохо работающий способ монетизации, да.
                      Примерно так.
      • 0
        портировали функцию с апи для майскл. все работает отлично уже неделю, очень даже стабильно. мы очень довольны. исправили сами баги в проекте и ура. сфинкс рулит
        • 0
          Слишком стабильно, я всегда это говорю!
        • 0
          Не поделитесь?
  • 0
    а что слышно по поводу инкрементального индекса?
    • 0
      Live index updates не то?
      • 0
        Возможно и то, а есть опыт реального использования? + ссылки?
        • 0
          Стандартная фича, в доках: www.sphinxsearch.com/docs/current.html#live-updates
          Опыт реального использования… Будет через часок )
          • 0
            Вау, очень жду, можно и отдельный топик забацать
            • 0
              Эх, не судьба. По здравому размышлению оно мне оказалось не нужно :(
              Это работает, когда все имеющиеся уже в базе документы не изменяются – например, форум в котором нельзя отредактировать свое сообщение после того, как оно опубликовано.

              А у меня просто сайт, в котором каждая страница может в любой момент быть изменена, стало быть, после изменения в инкрементный индекс не попадет. Можно извернуться, но у меня порядка 2-3 тыс документов в базе – легче каждый час всю базу перестраивать.

              Прошу прощения )
              • 0
                Все стоит подумать и попробовать сделать флаг для изменившихся страниц, если он есть переиндексировать, для форума тоже актуально, редко закрытые топики редактируют, а если редактировался ставим флаг что топик редактировался, при росте базы, это актуально. Сейчас пока у меня база на 9 тыс. документов, другой индексатор пыхтит минут 20-ть, но это не sphinx а могло бы намного быстрее работать
                • 0
                  Коммерческий проект со сжатыми сроками и без перспективы роста хотя бы до 6000 документов. А так конечно можно, хотя бы просто по полю даты изменения документа. Ну может дойдут руки еще.
    • 0
      А что есть инкрементальный индекс?
      • 0
        дополнение индекса на лету без обновление всего индекса, например появились 50 новых записей в базе, раз в 5-ть минут, индекс ими дополнился, понятно описал?
    • 0
      Слышно вот это
      sphinxsearch.com/news/35.html
  • 0
    Спасибо! Было бы интересно посмотреть на указанный SQL-пример в виде API вызовов, чтобы было понятнее.
    • 0
      Ну я даже…

      $client->SetSelect ( "*, @weight+userkarma*1000 AS myweight" );
      $client->SetFilter ( «userid», array(123,456,98,76,54), true );
      $client->SetFilter ( «hidden», array(0), true );
      $client->SetFilterRange ( «postkarma», 5, 1000000000 ); // UINT_MAX, но как его там в PHP…
      $client->SetGroupBy ( SPH_GROUPBY_ATTR, «userid», «myweight DESC, postdate ASC» );
      $client->SetSortMode ( «myweight DESC» );
      $client->SetLimit ( 100, 20 );
      $client->Query ( "@title hello @content world", «mainindex, deltaindex» );

      Примерно (примерно) так
  • НЛО прилетело и опубликовало эту надпись здесь
    • +1
      Не хотим.
      В существование людей, которые НЕ могут прислать патч только потому, что у нас не git, отчего-то не верю.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Задача помогать третьим лицам вести свои ветки приоритетной не является.
          • НЛО прилетело и опубликовало эту надпись здесь
            • 0
              Совет опоздал на год или даже два.
              Уже пробовал разное, SVN для моих целей до сих пор удобнее всего.
              • НЛО прилетело и опубликовало эту надпись здесь
                • 0
                  Нет, но рулю-то я.
                  • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Ну лично тебе вспоминается.
                      А лично у меня никаких бонусов от внедрения DVCS что-то не видать, кроме геморроя.

                      Вопрос решаемых задач, вкусов, итд итп.
                      Линусу без гита очевидно никуда!!!

                      Родной API это в смысле поддержку Postgres-овского протокола тоже сделать?
                      Нет, не занимался никто, и в планах нет.
  • 0
    подключайтесь к переводу мануала:
    translated.by/you/sphinx-0-9-9-reference-manual/trans/
  • 0
    Вопросец: при использовании API — нам возвращается вместе с результатами и число — общее количество результатов. А в случае использования sphinxQL — как лучше всего его получить?
    • 0
      Вы случаем не решили этот вопрос?
      • 0
        Шодан ответил ниже — запрос SHOW META
        • 0
          Ага, я сразу же заметил как написал вам вопрос и забыл написать об этом.
          Спасибо.
  • 0
    SHOW META
  • 0
    Простите за нескромный вопрос… Но, например, я привязал демон searchd к какому-то порту, открыл этот порт TCP для публичного доступа и разрешаю некоторому удаленному клиенту соединяться с демоном для запроса информации. А потом еще другому клиенту.

    А как недопустить несанкционированный доступ, можно ли провести какую-либо авторизацию? Может это и не относиться к Sphinx а относиться к администрированию серверов, но я уже извнился за нескромный вопрос.
    • 0
      Да, аутентификация в протоколе бы не помешала. Но и без нее жить можно. Самый простой способ ограничения доступа — на файрволе. Если нужно ограничить доступ со своих серверов, то делается все фильтрами по IP на ура. Если все-таки по какой-то причине нужна авторизация, то можно организовать ее через тоннели: вариант 1, по-проще — SSH, вариант 2, по-сложнее — VPN.
  • 0
    Добрый день! Когда происходит группировка, с помощью WITHIN GROUP ORDER BY можно контролировать, какой элемент в группе будет лучшим. А как выбрать не один лучший элемент, а N лучших элементов из каждой группы?
  • 0
    1. Правильно ли я понимаю, что правильным современным способом работы со Sphinx является SphinxQL? Т.е. при реализации нового проекта смотреть на API не смысла. Да и указание в мануале на то, что SphinxQL умеет все, что и API, но наоборот — неверно.

    2. А где в мануале рассказывается про экранирование спецсинтаксиса в расширенном режиме запроса? Очевидно, что при вставке в запрос строк от пользователя, необходимо обеспечить некий уровень экранирования (в самом жестком режиме запретить все).

    3. Какой клиент лучше всего использовать для работы со SphinxQL из PHP? mysqli?
    • 0
      SphinxQL и с нашей стороны быстрее обновляется и с клиентской проще использовать. В целом со всех сторон лучше и удобнее, я считаю.

      Экранирование это один очень простой (реализация в одну строчку) метод EscapeString() в том API.

      Про клиент ничего сказать не могу. «Какой лучше» в случае mysql, такой и в нашем, видимо.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка