Pull to refresh

Ищем втрое быстрее: мульти-запросы и фасеточный поиск

Reading time 5 min
Views 13K
В сегодняшней статье расскажу про фичу Sphinx под названием мульти-запросы: встроенные в нее оптимизации, реализацию тн. фасеточного поиска, и вообще как иногда можно с ее помощью сделать поиск втрое быстрее.

Но сначала 15 секунд политинформации (сам себя не похвалишь, никто не похвалит). В этом году Sphinx прошел во второй тур конкурса Sourceforge Awards 2009 в номинациях SysAdmins и Enterprise (говорят, в номинации Developers не добрали совсем чуть-чуть). Голосование продлится еще неделю (до 20го числа). Кроме рабочего email адреса, ничего не нужно. Заранее спасибо всем, кто не даст нам пропасть!

И обратно к разработке. Что вообще такое мульти-запросы, и откуда берется обещанное втрое быстрее?

Мульти-запросы (multi-queries) — это механизм, который позволяет отослать несколько поисковых запросов одним пакетом.

Методы API, реализующие механизм мульти-запросов, называются AddQuery() и RunQueries(). (Кстати, «обычный» метод Query() внутри использует их же: один раз вызывает AddQuery(), и затем сразу RunQueries()). Метод AddQuery() сохраняет текущее состояние всех настроек запроса, выставленных предыдущими API вызовами, и запоминает запрос. Настройки уже запомненного запроса более меняться не будут, любый вызовы API их не тронут, соотв-но для последующих запросов можно использовать любые другие настройки (другой режим сортировки, другие фильтры, итп). Метод RunQueries() фактически отсылает все запомненные запросы одним пакетом и возвращает несколько результатов. На участвующие в пакете запросы никаких ограничений не накладывается. Количество запросов на всякий случай ограничено директивой max_batch_queries (добавлена в 0.9.10, ранее фиксированным числом 32), но это в общем-то только проверка против битых пакетов.

Зачем использовать мульти-запросы? Вообще говоря, все сводится к производительности. Во-1х, отсылая запросы к searchd одним пакетом, всегда экономим немножко ресурсов и времени на том, что отсылаем туда-сюда меньше сетевых пакетов. Во-2х, что значительно более важно, searchd получает возможность провести некоторые оптимизации над всем пакетом запросов. Со временем постепенно добавляются новые оптимизации, поэтому имеет смысл всегда, когда можно, отсылать запросы пакетами — тогда при обновлении Sphinx новые пакетные оптимизации включатся полностью автоматически. В случае, когда никаких пакетных оптимизаций применить нельзя, запросы просто будут обработаны по одному, без каких-либо видимых отличий для приложения.

Зачем (точнее когда) мульти-запросы НЕ использовать? Все запросы в пакете должны быть независимы, но иногда это не так, и запрос Б может зависеть от результатов запроса А. Например, можем хотеть показывать результаты поиска из дополнительного индекса только тогда, когда в главном индексе ничего не нашлось. Или просто выбирать разное смещение во 2й набор результатов в зависимости от количества совпадений в 1м наборе. В таких случаях придется использовать отдельные запросы (или отдельные пакеты).

Есть две важные пакетные оптимизации, про которые стоит знать: оптимизация общих запросов (доступна начиная с версии 0.9.8), и оптимизация общих поддеревьев (доступн начиная с находящейся в разработке версии 0.9.10).

Оптимизация общих запросов работает так. searchd выбирает из пакета все запросы, у которых отличаются только настройки сортировки и группровки, а полнотекстовая часть, фильтры итп совпадают — и проводит поиск только один раз. Например, если в пакете 3 запроса, текстовая часть у всех «ipod nano», но 1й запрос выбирает 10 самых дешевых результатов, 2й группирует результаты по ID магазина и сортирует магазины по рейтингу, а 3й запрос просто выбирает максимальную цену, поиск «ipod nano» отработает только один раз, но из его результатов будут построены 3 по-разному отсортированных и сгруппированных отклика.

Так называемый фасеточный поиск является частным случаем, для которого применима данная оптимизация. В самом деле, его можно реализовать, запустив несколько поисковых запросов с разными настройками: один для основных результатов поиска, еще несколько с таким же поисковым запросом, но разными настройками группировки (top-3 авторов, top-5 магазинов, итп). Когда все, кроме сортировки и группировки одинаковое, оптимизация включается и скорость неплохо растет (пример ниже).

Оптимизация общих поддеревьев еще более интересная штука. Она позволяет searchd использовать сходства между разными запросами внутри пакета. Внутри всех пришедших отдельных — разных! — полнотекстовых запросов выявляются общие части, и если такие есть, промежуточные результаты расчета кешируются и разделяются между запросами. Например, вот в таком пакете из 3 запросов

barack obama president
barack obama john mccain
barack obama speech


есть общая часть из 2х слов («barack obama»), которую можно для всех трех запросов вычислить ровно один раз и закешировать. Именно этим оптимизация общих поддеревьев и занимается. Максимальный размер кеша на каждую пачку жестко ограничивается директиваи subtree_docs_cache и subtree_hits_cache, так что если общая часть «i am» найдется в ста миллионах документов, память у сервера внезапно таки не кончится.

Вернемся обратно к оптимизации про общие запросы. Вот пример кода, который запускает один и тот же запрос, но с тремя разными режимами сортировки:
sorting modes:

require ( "sphinxapi.php" );
$cl = new SphinxClient ();
$cl->SetMatchMode ( SPH_MATCH_EXTENDED2 );

$cl->SetSortMode ( SPH_SORT_RELEVANCE );
$cl->AddQuery ( "the", "lj" );
$cl->SetSortMode ( SPH_SORT_EXTENDED, "published desc" );
$cl->AddQuery ( "the", "lj" );
$cl->SetSortMode ( SPH_SORT_EXTENDED, "published asc" );
$cl->AddQuery ( "the", "lj" );
$res = $cl->RunQueries();


Как узнать, сработала ли оптимизация? Если сработала, в соответствующих строчках лога будет поле с «мультипликатором», который показывает, сколько запросов было обработано вместе:

[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext2/0/rel 747541 (0,20)] [lj] the
[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext2/0/ext 747541 (0,20)] [lj] the
[Sun Jul 12 15:18:17.000 2009] 0.040 sec x3 [ext2/0/ext 747541 (0,20)] [lj] the


Обратите внимание на «x3», это именно оно — означает, что запрос был оптимизирован и обработан в числе пакета из 3 запросов (включая этот). Для сравнения, вот так выглядит лог, в котором те же самые запросы были отправлены по одному:

[Sun Jul 12 15:18:17.062 2009] 0.059 sec [ext2/0/rel 747541 (0,20)] [lj] the
[Sun Jul 12 15:18:17.156 2009] 0.091 sec [ext2/0/ext 747541 (0,20)] [lj] the
[Sun Jul 12 15:18:17.250 2009] 0.092 sec [ext2/0/ext 747541 (0,20)] [lj] the


Видно, что время поиск на каждый запрос в случае с мульти-запроса улучшилось от 1.5 до 2.3 раз, в зависимости от режима сортировки. На самом деле, это не предел. Для обоих оптимизаций известны случаи, когда скорость улучшалась в 3 и более раз — причем не на синтетических тестах, а вполне себе в продакшне. Оптимизация общих запросов довольно хорошо ложится на вертикальные поиски по товарам и онлайн магазины, кеш общих поддеревьев соовт-но на data mining запросов; но, разумеется, строго этими областями применимость не ограничивается. Например, можно делать поиск вообще без полнотекстовой части и считать несколько разных отчетов (с разной сортировкой, группировкой итп) по одинаковым данных за один запрос.

Каких еще оптимизаций можно ожидать в будущем? Зависит от вас. Пока что в долгосрочном плане записана понятная оптимизация про одинаковые запросы с разными наборами фильтров. Знаете другой частый паттерн, котороый можно ловко соптимизировать? Присылайте!
Tags:
Hubs:
+44
Comments 20
Comments Comments 20

Articles

Information

Website
sphinxsearch.com
Registered
Founded
Employees
Unknown
Location
Россия