Sphinx в примерах и задачах Андрей Аксенов Sphinx Technologies Что за… Sphinx? • Sphinx – это полнотекстовый поисковик Что за… Sphinx? • Sphinx – это полнотекстовый поисковик • Может копать – Активный, ответственный работник • Может не копать – Внимателен к инициативам начальства • Может лопату спрятать – Отличный командный игрок Что за… Sphinx? • Может то, чего по слухам не может! – «Полуживые» обновления индекса – Фасеточный поиск – Особенные, уличные SQL запросы – Геопоиск – Создание сниппетов – Мультизапросы – И еще 10-20-30 других интересных фичей Где выход и когда обед? • О чем вы НЕ узнаете из доклада? – Зачем всё это – Что написано в документации – Что написано в исходниках • О чем поговорим – Как Sphinx устроен внутри – Как оптимизировать разное – Как куют highload Общее устройство • Нет… никакого… Сфинкса. Общее устройство • Есть две программы – Indexer – строит индексы – Searchd – отвечает на запросы • Еще есть API – Клиент, который умеет говорить с searchd по сети – PHP, PECL, Python, Perl, Java, Ruby, C99, C++, Haskell, C#, MySQL SE… Как работает indexer • Есть источники данных – Откуда брать (MySQL, PgSQL, xmlpipe) – Что брать (sql_query, sql_attr_xxx) – Как брать (sql_query_pre, sql_query_post) • Есть физические индексы – Как индексировать (токенизация, стемминг, словоформы, HTML stripper) – Куда класть файлы Что хранится в индексе • Индекс для полнотекстовых запросов – Словарь – Списки документов по ключевым словам – Списки позиций по документам • Привязанные атрибуты документов – Integer (от 1 до 32 или 64 бит) – Float – MVA (сортированный список 32-битных целых) • НЕ хранятся исходные текстовые данные Как работает searchd • Получает запрос, вычисляет ответ • Умеет агрегировать ответы – По нескольким физически индексам – С ответами от удаленных searchd • Еще умеет строить сниппеты • Еще умеет обновлять атрибуты (иногда) Как все перестает работать Нагрузка ударяет в спину • Виды проблем – Bandwidth – слишком много запросов – Latency – слишком долгий отклик – Availability – слишком мало (работающих) серверов • Как бороться? – Локально – как оптимизировать запросы – Глобально – как обустраивать кластера Оптимизируем запросы • Как работает поиск Как работает поиск • Для каждого локального индекса – Строим список кандидатов (документов, удовлетворяющих запросу) – Фильтруем (аналог – WHERE) – Ранжируем (считаем веса документов) – Сортируем (аналог – ORDER BY) – Группируем (аналог – GROUP BY) • Склеиваем результаты по всем индексам Цена булева поиска • Построение списка кандидатов – – – – – 1 ключевое слово = 1+ IO (список документов) Булевы операции над списками документов Стоимость пропорциональна (~) длине списков То есть, сумме частот всех ключевых слов При поиске фраз итп, еще и операции над списками позиций слов – примерно 2x IO/CPU • Мораль – “The Who” – очень плохая музыка – Гамлет – очень плохая литература Цена фильтрации • docinfo=inline – Атрибуты хранятся в списке документов – ВСЕ значения дублируются МНОГО раз! – Доступны сразу после чтения с диска • docinfo=extern – Атрибуты хранятся в отдельном списке (файле) – Полностью кэшируются в RAM – Хэш по docid + бинарный поиск • Перебор фильтров • Cтоимость ~ числу кандидатов и фильтров Цена ранжирования • Прямая - зависит от ranker-а – Учитывать позиции слов - • Полезно для релевантности • Но стоит ресурсов - двойной удар! • Стоимость ~ числу результатов • Самый дорогой - phrase proximity + BM25 • Самый дешевый - none (weight=1) • Косвенная - может наводиться в сортировке Цена сортировки • Стоимость ~ числу результатов • Еще зависит от критерия сортировки (документы придут в порядке @id asc) • Еще зависит от max_matches • Чем больше max, тем хуже серверу • 1-10K приемлемо, 100K перебор • 10-20 недобор Цена группировки • Группировка внутри – особый подвид сортировки • Тоже число результатов • Тоже max_matches • Вдобавок, от max_matches зависит точность @count и @distinct (Некоторые) оптимизации (Некоторые) оптимизации • Режимы ранжирования и сортировки • Фильтры против ключевых слов • Мультизапросы (multi queries) • Разбиение данных (partitioning) • Последняя линия защиты – • Три Большие Кнопки Ранжирование… • Бывает разное, см. SetRankingMode() • По умолчанию – phrase+BM25 – Анализирует позиции слов – Что не бесплатно! • Иногда достаточно более простого • Иногда достаточно тривиального – ищем ipod, сортируем по цене… …и сортировка • Можно упростить ранжирование – Когда сортируем по цене, вес не интересен • Можно вкомпилировать – См. src/sphinxcustomsort.inl + @custom • Можно оптимизировать – Документы приходят в порядке @id asc – @id asc => date asc – оптимально – @id asc => date desc – можно поменять id Фильтры против спецслов • Известный трюк – При индексации, добавляем специальное ключевое слово в документ (_authorid123) – При поиске, добавляем его в запрос • Понятный вопрос – Что быстрее, как лучше? • Нехитрый ответ – Считайте ценник, не отходя от кассы Фильтры против спецслов • Цена булева поиска ~ частотам слов • Цена фильтрации ~ числу кандидатов • Поиск – CPU+IO, фильтр – только CPU • Частота спецслова = селективности значения фильтра • Частое знач-е + мало кандидатов → плохо! • Редкое знач-е + много кандидатов → хорошо! Мультизапросы • Любые запросы можно передать пачкой • Всегда экономит network roundtrip • Иногда может сработать оптимизатор • Особо важный и нужный случай – разные режимы сортировки группировки • 2x+ оптимизация “фасеточного” поиска Мультизапросы $client = new SphinxClient (); $q = “laptop”; // coming from website user $client->SetSortMode ( SPH_SORT_EXTENDED, “@weight desc”); $client->AddQuery ( $q, “products” ); $client->SetGroupBy ( SPH_GROUPBY_ATTR, “vendor_id” ); $client->AddQuery ( $q, “products” ); $client->ResetGroupBy (); $client->SetSortMode ( SPH_SORT_EXTENDED, “price asc” ); $client->SetLimit ( 0, 10 ); $result = $client->RunQueries (); (Некоторые) оптимизации • Режимы ранжирования и сортировки • Фильтры против ключевых слов • Мультизапросы (multi queries) • Разбиение данных (partitioning) • Последняя линия защиты – • Три Большие Кнопки Partitioning Partitioning • Алгоритм решения боевых задач им. тов. Цезаря • Уперлось в переиндексацию? – Разбиваем, переиндексируем только изменения • Уперлось в фильтрацию? – Разбиваем, ищем только по нужным индексам • Уперлось в CPU/HDD? – Разбиваем, разносим по разным cores/HDDs/boxes Разбиение под индексацию • Необходимо держать баланс • Не добьешь – будет тормозить индексация • Перебьешь – будет тормозить поиск • 1-10 индексов – работают разумно • Некоторых устраивает и 50+ (30+24...) • Некоторых устраивает и 2000+ (!!!) Разбиение под фильтрацию • Полностью, на 100% зависит от статистики боевых запросов – Анализируйте свои личные боевые логи – Добавляйте комментарии (Query(), 3rd arg) • Оправдано только при существенном уменьшении обрабатываемых данных – Для документов за последнюю неделю – да – Для англоязычных запросов – нет (!) Разбиение под CPU/HDD • Распределенный индекс, куски явно дробим по физическим устройствам • Прицеливаем searchd “сам на себя” – index dist1 { type = distributed local = chunk01 agent = localhost:3312:chunk02 agent = localhost:3312:chunk03 agent = localhost:3312:chunk04 } Куем highload Куем highload • • • • Или «как нам обустроить кластер» Всегда будет рост bandwidth Всегда будет no-SPoF (те. «какой-то» HA) Варианты строго зависят от требований – – – – Допустимая задержка индексации? Допустимая скорость запроса? Время восстановления после hard crash? Приемлема ли деградация результатов? Методы борьбы • Борем задержку индексации – Дельта-индекс – Каскады дельта-индексов – Merge – Partitioning на несколько машин • Борем скорость запроса – Оптимизируем локальные запросы – Partitioning на несколько машин Методы partitioning • Линейная репликация – Клонируем весь индекс много раз – Опционально выносим индексацию – Pro – легко поддерживать/наращивать – Pro – автоматом HA (сплошные hotspare!) – Pro – деградация невозможна – Contra – растет только bandwidth Методы partitioning • Линейный или “деревянный” partitioning – Разбиваем индекс на независимые куски – Линейный – star topology – Деревянный – star-of-stars topology – Pro – легко поддерживать – Pro – почти линейно падает latency – Contra – переделки при наращивании – Contra – нету HA, деградация при сбоях Методы partitioning • Комбинированные схемы – Partitioning + репликация кусков – Балансировка между конечными кусками – внешним LB (пока?), на уровне TCP port – Pro – решает “все” задачи – Contra – сложнее всего разворачивать и поддерживать Военные хитрости • Несколько копий searchd на сервер – Улучшает время запуска – Нужно, когда МНОГО атрибутов • Raw HDD, а не RAID – Явное разделение – лучше неявного – Иначе – IO stepping для одного (!) запроса • Вечный indexer – Бесконечный цикл вместо crontab Выбираем железо • Большие коллекции упираются в IO – Добивать HDD, причем числом! – Добивать RAM • Маленькие коллекции в CPU • Три стандартных средства – vmstat – чем и насколько занят CPU? – oprofile – кем конкретно занят CPU? – iostat – насколько занят HDD? • Плюс логи, плюс опция searchd --iostats Выбираем железо • Анализируем результаты – Обычно все наглядно (us/sy/bi/bo…), но! – Ловушка – HDD может упираться в iops – Ловушка – CPU может прятаться в sy – Ловушка – «незаметные» проблемы в us (Некоторые) оптимизации • Режимы ранжирования и сортировки • Фильтры против ключевых слов • Мультизапросы (multi queries) • Разбиение данных (partitioning) • Последняя линия защиты – • Три Большие Кнопки Три Большие Кнопки • Если ничто другое не помогает… • Cutoff (см. SetLimits()) – Останов поиска после N первых совпадений – В каждом индексе, не суммарно • MaxQueryTime (см. SetMaxQueryTime()) – Останов поиска после M миллисекунд – В каждом индексе, не суммарно Три Большие Кнопки • Если ничто другое не помогает… • Consulting – Можем заметить незамеченное – Можем дописать недописанное Вопросы?