Кто вы такие?

реклама
Загадки Sphinx-а
Анатомический атлас поискового движка
Кто вы такие?
• Sphinx – полнотекстовый поисковик
Кто вы такие?
• Sphinx – полнотекстовый поисковик
• Умеет копать
Кто вы такие?
• Sphinx – полнотекстовый поисковик
• Умеет копать
• Умеет не копать
Кто вы такие?
•
•
•
•
Sphinx – полнотекстовый поисковик
Умеет копать
Умеет не копать
Умеет передать лопату соседу
Кто вы такие?
•
•
•
•
•
Sphinx – полнотекстовый поисковик
Умеет копать
Умеет не копать
Умеет передать лопату соседу
Умеет много гитик
– «фасеточный» поиск, геопоиск, создание
сниппетов, мультизапросы, IO throttling, и еще
10-20 других интересных директив
Зачем вас позвали?
• Чего не будет в докладе?
– Вводного обзора «что это и зачем мне»
– Длинных цитат из документации
– Подробного разбора C++ архитектуры
Зачем вас позвали?
• Чего не будет в докладе?
– Вводного обзора «что это и зачем мне»
– Длинных цитат из документации
– Подробного разбора C++ архитектуры
• Что будет?
– Как оно в целом устроено внутри
– Как можно оптимизировать разное
– Как можно распараллеливать разное
Глава 1. Потроха движка
Вспомнить workflow
• Сначала индексируем
• Потом ищем
Вспомнить workflow
• Сначала индексируем
• Потом ищем
• Есть источники данных (откуда и что брать)
• Есть индексы
– Какие источники брать
– Как обрабатывать текст
– Куда складывать результат
Как работает индексация
• В двух актах, с антрактом
• Фаза 1 – сбор документов
– Получаем документы (цикл по источникам)
– Разбиваем каждый документ на слова
– Обрабатываем слова (морфология, *фиксы)
– Заменяем слова на wordid (CRC32/64)
– Пишем ряд временных файлов
Как работает индексация
• Фаза 2 – сортировка хитов
–
–
–
–
Хит (hit, вхождение) – запись (docid,wordid,wordpos)
На входе частично сортированные (по wordid) данные
Сортируем слиянием списки хитов
На выходе полностью сортированные данные
• Интермеццо
– Собираем и сортируем значения MVA
– Сортируем ordinals
– Сортируем extern атрибуты
Тупой и еще тупее
• Формат индекса на выходе… несложен
• Несколько сортированных массивов
– Словарь (список wordid)
– Атрибуты (только если docinfo=extern)
– Список документов (для каждого слова)
– Список позиций (для каждого слова)
• Всё уложено линейно, хорошо для IO
Как работает поиск
• Для каждого локального индекса
– Строим список кандидатов (документов,
удовлетворяющих запросу)
– Фильтруем (аналог – WHERE)
– Ранжируем (считаем веса документов)
– Сортируем (аналог – ORDER BY)
– Группируем (аналог – GROUP BY)
• Склеиваем результаты по всем индексам
1. Цена поиска
• Построение списка кандидатов
– 1 ключевое слово = 1+ IO (список документов)
– Булевы операции над списками документов
– Стоимость пропорциональна (~) длине списков
– То есть, сумме частот всех ключевых слов
– При поиске фраз итп, еще и операции над
списками позиций слов – примерно 2x IO/CPU
1. Цена поиска
• Построение списка кандидатов
– 1 ключевое слово = 1+ IO (список документов)
– Булевы операции над списками документов
– Стоимость пропорциональна (~) длине списков
– То есть, сумме частот всех ключевых слов
– При поиске фраз итп, еще и операции над
списками позиций слов – примерно 2x IO/CPU
• Мораль – “The Who” – очень плохая музыка
2. Цена фильтрации
• docinfo=inline
– Атрибуты хранятся в списке документов
– ВСЕ значения дублируются МНОГО раз!
– Доступны сразу после чтения с диска
• docinfo=extern
– Атрибуты хранятся в отдельном списке (файле)
– Полностью кэшируются в RAM
– Хэш по docid + бинарный поиск
• Перебор фильтров
• Cтоимость ~ числу кандидатов и фильтров
3. Цена ранжирования
• Прямая – зависит от ranker-а
– Учитывать позиции слов –
• Полезно для релевантности
• Но стоит ресурсов – двойной удар!
• Стоимость ~ числу результатов
• Самый дорогой – phrase proximity + BM25
• Самый дешевый – none (weight=1)
• Косвенная – может наводиться в сортировке
4. Цена сортировки
• Стоимость ~ числу результатов
• Еще зависит от критерия сортировки
(документы придут в порядке @id asc)
• Еще зависит от max_matches
• Чем больше max, тем хуже серверу
• 1-10K приемлемо, 100K перебор
• 10-20 недобор
5. Цена группировки
•
•
•
•
Группировка внутри – подвид сортировки
Тоже число результатов
Тоже max_matches
Вдобавок, от max_matches зависит
точность @count и @distinct
Глава 2. Оптимизируем разное
Как оптимизировать запросы
• Разбиение данных (partitioning)
•
•
•
•
Режимы ранжирования и сортировки
Фильтры против ключевых слов
Фильтры и MTF вручную
Мультизапросы (multi queries)
Как оптимизировать запросы
• Разбиение данных (partitioning)
•
•
•
•
Режимы ранжирования и сортировки
Фильтры против ключевых слов
Фильтры и MTF вручную
Мультизапросы (multi queries)
• Последняя линия защиты – Три Большие Кнопки
1. Разбиение данных
• Чисто швейцарский нож, для разных задач
• Уперлось в переиндексацию?
– Разбиваем, переиндексируем только изменения
• Уперлось в фильтрацию?
– Разбиваем, ищем только по нужным индексам
• Уперлось в CPU/HDD?
– Разбиваем, разносим по разным
cores/HDDs/boxes
1a. Разбиение под индексацию
•
•
•
•
•
•
Необходимо держать баланс
Не добьешь – будет тормозить индексация
Перебьешь – будет тормозить поиск
1-10 индексов – работают разумно
Некоторых устраивает и 50+ (30+24...)
Некоторых устраивает и 2000+ (!!!)
1b. Разбиение под фильтрацию
• Полностью, на 100% зависит от статистики
боевых запросов
– Анализируйте свои личные боевые логи
– Добавляйте комментарии (3й параметр Query())
• Оправдано только при существенном
уменьшении обрабатываемых данных
– Для документов за последнюю неделю – да
– Для англоязычных запросов – нет (!)
1c. Разбиение под CPU/HDD
• Распределенный индекс, куски явно
дробим по физическим устройствам
• Прицеливаем searchd “сам на себя” –
index dist1
{
type = distributed
local = chunk01
agent = localhost:3312:chunk02
agent = localhost:3312:chunk03
agent = localhost:3312:chunk04
}
1c. Как искать CPU/HDD заторы
• Три стандартных средства
– vmstat – чем и насколько занят CPU?
– oprofile – кем конкретно занят CPU?
– iostat – насколько занят HDD?
•
•
•
•
Плюс логи, плюс опция searchd --iostats
Обычно все наглядно (us/sy/bi/bo…), но!
Ловушка – HDD может упираться в iops
Ловушка – CPU может прятаться в sy
2. Ранжирование
• Теперь бывает разное (т.н. ранкеры в
режиме поиска extended2)
• Ранкер по умолчанию – phrase+BM25,
анализирует позиции слов – не бесплатно
• Иногда достаточно более простого
• Иногда @weight игнорируется совсем
(ищем ipod, сортируем по цене)
• Иногда можно сэкономить
3. Фильтры против спецслов
• Известный трюк
– При индексации, добавляем специальное
ключевое слово в документ (_authorid123)
– При поиске, добавляем его в запрос
• Понятный вопрос
– Что быстрее, как лучше?
• Нехитрый ответ
– Считайте ценник, не отходя от кассы
3. Фильтры против спецслов
• Цена булева поиска ~ частотам слов
• Цена фильтрации ~ числу кандидатов
• Поиск – CPU+IO, фильтр – только CPU
• Частота спец-слова
= селективности значения фильтра
• Частое значение + мало кандидатов → плохо!
• Редкое значение + много кандидатов → хорошо!
4. Фильтры и MTF вручную
• Фильтры перебираются последовательно
• В порядке, указанном приложением!
• Самый жесткий – лучше ставить в начале
• Самый нестрогий – лучше ставить в конце
• При замене спец-словами – не важно
• Домашняя работа – а почему?
5. Мультизапросы
• Любые запросы можно передать пачкой
• Всегда экономит network roundtrip
• Иногда может сработать оптимизатор
• Особо важный и нужный случай –
разные режимы сортировки-группировки
• 2x+ оптимизация “фасеточного” поиска
5. Мультизапросы
$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 ();
6. Три Большие Кнопки
• Если ничто другое не помогает…
• Cutoff (см. SetLimits())
– Останов поиска после N первых совпадений
– В каждом индексе, не суммарно
• MaxQueryTime (см. SetMaxQueryTime())
– Останов поиска после M миллисекунд
– В каждом индексе, не суммарно
6. Три Большие Кнопки
• Если ничто другое не помогает…
• Consulting 
– Можем заметить незамеченное
– Можем дописать недописанное
Глава 3. Пример распараллеливания
Боевая задача
• Есть ~160M кросс-ссылок
• Нужен ряд отчетов (по доменам→groupby)
*************************** 1. row ***************************
domain_id: 440682
link_id: 15
url_from: http://www.insidegamer.nl/forum/viewtopic.php?t=40750
url_to: http://xbox360achievements.org/content/view/101/114/
anchor: NULL
from_site_id: 9835
from_forum_id: 1818
from_author_id: 282
from_message_id: 2586
message_published: 2006-09-30 00:00:00
...
Решаем – раз
• Дробим данные
• 8 машин, 4x CPU, ~5M ссылок на CPU
• Используем Sphinx
• Теоретически можно MySQL
• Практически сложно
– Выйдет 15-20M+ rows/CPU
– Агрегацию придется делать вручную
Решаем – два
• Выделяем “интересные части” URL при
индексации UDF-кой
• Выборку подменяем текстовым запросом
*************************** 1. row ***************************
url_from: http://www.insidegamer.nl/forum/viewtopic.php?t=40750
urlize(url_from,0): www$insidegamer$nl
insidegamer$nl
insidegamer$nl$forum
insidegamer$nl$forum$viewtopic.php
insidegamer$nl$forum$viewtopic.php$t=40750
urlize(url_from,1): www$insidegamer$nl
insidegamer$nl
insidegamer$nl$forum
insidegamer$nl$forum$viewtopic.php
Решаем – три
• 64 индекса
– 4 копии searchd на сервер, по числу CPU/HDD
– 2 индекса (main+delta) на CPU
• Обыскиваются параллельно
– Web box спрашивает главную копию searchd
– Главная спрашивает себя и остальные 3 копии
– Используем 4 копии, потому что startup/update
– Используем plain HDD, потому что IO stepping
Результаты
• Точность приемлема
• “Редкие” домены – без ошибок
• “Частые” домены – в пределах 0.5%
•
•
•
•
Среднее время запроса – 0.125 sec
90% запросов – менее 0.227 sec
95% запросов – менее 0.352 sec
99% запросов – менее 2.888 sec
Конец
Скачать