SphinxQL. Теперь Sphinx поддерживает SQL запросы к индексам!
В этой статье будетрассказано о двух новых мега-фичах в 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:
1 |
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 расширений синтаксиса. Вот пример:
1 2 3 4 5 6 7 8 9 |
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().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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) |
А вот так, кстати, выглядит ошибка.
1 2 |
mysql> select * from test; ERROR 1064 (42000): unknown local index 'test' in search request |
Есть оператор SHOW STATUS, который показывает всякую разную статистику. Счетчики, понятно, отличные от MySQL; но формат выдачи тот же. LIKE пока не поддерживается.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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 вызовы, а теперь не надо.
Причем это мы только по одному ядру, а можно и по два.
Привет!
Кто у кого статью стащил? http://habrahabr.ru/blogs/sphinx/61222/
Эм, ну там вообще-то внизу стоит ссылка на оригинал статьи 🙂