Бэк-офис в Avito:Бэк-офис в Avito:Бэк-офис в Avito:Бэк-офис в Avito:Бэк-офис в Avito:
миллиард объявлениймиллиард объявлениймиллиард объявлениймиллиард объявлениймиллиард объявлений
на 10 серверахна 10 серверахна 10 серверахна 10 серверахна 10 серверах
Вячеслав КрюковВячеслав КрюковВячеслав КрюковВячеслав КрюковВячеслав Крюков
SphinxSearch Meetup #2 @ AvitoSphinxSearch Meetup #2 @ AvitoSphinxSearch Meetup #2 @ AvitoSphinxSearch Meetup #2 @ AvitoSphinxSearch Meetup #2 @ Avito
Зачем нужен Backoffice?
• Модерация объявлений
• Управление
• пользователями
• магазинами
• сообщениями
• контекстными объявлениями
• И много еще зачем
• И для всего нужен поиск
SphinxSearch Meetup #2 @ Avito
Основное хранилище объявлений
• PostgreSQL
• Данные нормализованы
SphinxSearch Meetup #2 @ Avito
Что мы хотим от поиска в Backoffice?
• Обрабатывать много объявленй
• Высокую актуальность
• Приемлемую скорость выполнения запросов
• Унификацию
SphinxSearch Meetup #2 @ Avito
Sql источник и дельта индексация - классика
жанра
• Делим индекс на две части Small и Main
• Запоминаем метку времени последней индексации Main
• Периодически переиндексируем Small от этой метки
• Пока его размер приемлем
SphinxSearch Meetup #2 @ Avito
Дельта индексация
Переиндексируем Small
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексируем Small
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексируем Small, Small уже совсем и не Small
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексировали Main, Small стал маленьким
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексируем Small
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексируем Small
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация
Переиндексировали Main, Small стал маленьким
SphinxSearch Meetup #2 @ Avito
Small
Main
Дельта индексация - известные проблемы
• Small быстро пухнет
• И переиндексируется с каждым разом медленнее
• Мы вытаскиваем повторно из базы все больше и больше данных
• Падает актуальность
• И надо переиндексировать Main
• Возможно, слишком часто, чем хотелось бы
SphinxSearch Meetup #2 @ Avito
Дельта индексация: почему часто
переиндексировать Main плохо?
• Мы вытаскиваем все данные
• Мы не запрещаем JOIN в запросах
• А даже любим их
• Переиндексировать Main это долго
• Либо очень сложно
SphinxSearch Meetup #2 @ Avito
Дельта индексация - пытаемся
оптимизировать
• Делим индекс на три части Small, Medium, Main
• Храним две метки времени индексации для Medium, Main
• И периодически переиндексируем Small от первой метки
• А Medium от второй
• Пока их размер приемлем
SphinxSearch Meetup #2 @ Avito
Дельта индексация
Переиндексируем
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Small
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Small...Small...
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Small...Small...Medium
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Small...Small...Medium...Small
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Medium...Small...Small
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Medium...Small...Small...Medium
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Medium...Small...Small
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация
Переиндексируем Medium...Small...Small...Main...Medium...Small
SphinxSearch Meetup #2 @ Avito
Small
Medium
Main
Дельта индексация: после оптимизации
• Переиндексация Main происходит реже
• Но она все равно нужна
• Если не раз в сутки
• Так раз в неделю
• И даже если раз в месяц - это плохо
• Хотим это делать в крайнем случае
SphinxSearch Meetup #2 @ Avito
Всякая экзотика
• Апдейтим атрибуты, а в дельте только изменения текста
• RT
• может упасть
• Мерж индексов
• быстро работает
• но фейлится в самый неподходящий момент
• Предложите свой вариант
SphinxSearch Meetup #2 @ Avito
Pipe источник индексации
• Необходима собственная реализация извлечения данных
• Широкие возможности: можно объеденять данные с различных серверов
БД, источников с разными форматами
SphinxSearch Meetup #2 @ Avito
TSV pipe
• TSV - Tab Separated Values
• Работает быстрее XML Pipe
• Проще обрабатывается
SphinxSearch Meetup #2 @ Avito
TSV pipe + дельта индексация
Как это устроено
• Выделяем несколько частей-чанков
• Small, Medium, Main и т.п.
• Как для обычной дельта схемы
• Отдельный TSV файл
• соответствующий какому-либо чанку
• отсортирован по убыванию id объявления
SphinxSearch Meetup #2 @ Avito
TSV pipe + дельта индексация
Как это устроено
• Выделяем самый маленький TSV чанк - Pre
• Ставим его перед остальными TSV чанками
• В него генерируем данные
• по времени от сохраненной метки
• маленькими порциями
• Сортируем по убыванию id объявления
• После генерации сохраняем новую метку
SphinxSearch Meetup #2 @ Avito
TSV pipe + дельта индексация
Как это устроено
• Осуществляем последовательный мерж TSV чанков
• Мерж TSV чанков по лэтенси
• indexer забирает заданные TSV файлы и строит индексы
• Применяем TSV KL для удаленных объявлений
• Применяем Sphinx KL для приоритета актуальности младших чанков над
старшими
• Большие TSV файлы жмем
• Гибкие и простые блокировки
SphinxSearch Meetup #2 @ Avito
TSV pipe + дельта индексация
Профит
• Вычитываем данные из БД только один раз
• Одна метка
• Манипулировать отсортированными TSV файлами просто и быстро
• Можем вытаскивать данные не только из БД
• А из любого др. хранилища и любом др. формате
• indexer быстро обрабатывает TSV файлы
• Очень полезно при смене версии Sphinx
• И отсутствии обратной совместимости формата индексов
SphinxSearch Meetup #2 @ Avito
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
После генерации Pre сохранили новую мектку
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small, мерж занял слишком много времени
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Small в Main
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Генерим данные в Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Сгенерили
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Pre в Small, мерж занял слишком много времени
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Мерж TSV чанков по лэтенси
Смержили Small в Main
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже Pre-Small
Генерим Pre и лочим Small, что бы потом с ним смержиться
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже Pre-Small
Сгенерли Pre, другая генерация Pre пока невозможна
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже Pre-Small
Смержили Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже Pre-Small
Разлочили Pre и Small, возможна следующая генерация Pre
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже промежуточных TSV
чанков
Перед мержем Small в Main
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Блокировки при мерже промежуточных TSV
чанков
Лочим Main, перемещаем Small в Small.tmp
Залочить Main можно, если в этот момент не был залочен Small
SphinxSearch Meetup #2 @ Avito
Pre
Small.tmp
Main
Блокировки при мерже промежуточных TSV
чанков
Мержим Small.tmp в Main, можем мержить Pre в Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Small.tmp
Main
Блокировки при мерже промежуточных TSV
чанков
Все еще мержим Small.tmp в Main, Small растет
SphinxSearch Meetup #2 @ Avito
Pre
Small
Small.tmp
Main
Блокировки при мерже промежуточных TSV
чанков
Получили новый Main, Small растет
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Перегенерация Main
Надо согнать все данные по цепочке TSV чанков в конец перед Main
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Перегенерация Main
Согнали
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Перегенерация Main
Переместили Medium в Medium.tmp, генерируем Main.new
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, растет Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, растет Small и готов к мержу в Medium
SphinxSearch Meetup #2 @ Avito
Pre
Small
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, смержили Small в Medium
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, опять растет Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, опять смержили Small в Medium
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Medium.tmp
Main.new
Перегенерация Main
Генерируем Main.new, растет Small
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Medium.tmp
Main.new
Перегенерация Main
Main.new сгенерился, опять смержили Small в Medium
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Medium.tmp
Main.new
Перегенерация Main
Main.new переместили в Main, Medium.tmp удалили
После перегенерации Main сохранили новую мектку
SphinxSearch Meetup #2 @ Avito
Pre
Small
Medium
Main
Перегенерация Main
Особый, тяжелый случай
• Останавливаем реплику
• Вычитываем через Seq Scan все данные для Main
• Нарезаем TSV файлы по нодам
• Сохраняем новую метку
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
• Около 3000 строк PHP
• Генератор данных - TSVGeneratorBase
• AdmItems
• ProItems
• AdmAds
• ...
• Всего 9 генераторов и 70% кода
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
• Управление вводом/выводом - TSVResource
• Обычные TSV файлы
• .gz
• создание TSV файла c одной строкой
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
• Мержер файлов - PHPMerger
• Мерж предсортированных TSV файлов
• Мерж с TSV KL
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
Выделение TSV чанка в отдельную сущность
SphinxSearch Meetup #2 @ Avito
TSVChunkFirst
TSVChunkMedium
TSVChunkMedium
TSVChunkLast
Реализация схемы TSV pipe + дельта
индексация
Выделение TSV чанка в отдельную сущность
• Первый чанк - TSVChunkFirst
• process
• processRange
• processFull
• generate
• reset
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
Выделение TSV чанка в отдельную сущность
• Промежуточный чанк - TSVChunkMedium, последний чанк - TSVChunkLast
• process
• processFull
• merge
• move
• reset
• archive
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
Выделение TSV чанка в отдельную сущность
• TSVManager - cборщик цепочки чанков из экземпляров
• TSVChunkFirst
• TSVChunkMedium
• TSVChunkLast
• Запускает обработку цепочки
SphinxSearch Meetup #2 @ Avito
Реализация схемы TSV pipe + дельта
индексация
Конфигурация
'adm_items' => [
'nodes' => 100,
'path' => '/path/to/tsv',
'chunks' => [
'pre' => [],
'small' => [],
'medium' => ['latency' => 1000, 'throttle' => 15 * 60],
'main' => ['gzip' => 1, 'latency' => 40000, 'throttle' => 61 * 60],
'archive' => ['gzip' => 1, 'throttle' => 61 * 60, 'schedule' => ['wH' => '002']], // schedule - воскресенье 2 ночи
],
]
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Распределение данных
• Нужно больше для обеспечения скорости выполнения запроса
• Чем для обработки большого числа запросов
• Утилизирует многоядреность
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Распределение данных
• Равномерно распределяем наш миллиард объявлений по 100 нодам
• 10 нод на каждом из 10 контейнерах
• Можем переиграть число нод, обработав TSV файлы
• Намного быстрее, чем вычитать всю базу
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Управление конфигурацией
• Контейнерная архитектура, LXC
• Динамическая конфигурация для indexer и searchd
• Конфигурация контейнеров в манфетах puppet
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Раздача TSV файлов по контейнерам при индексации
SphinxSearch Meetup #2 @ Avito
Cron-server, генерация TSV файлов
Search-container01 ... Search-container10
Node1
Small1 Medium1 Main1 Archive1
... Node10 Node91 ... Node100
Small100 Medium100 Main100 Archive100
Архитектура поискового кластера
Запуск indexer на контейнерах Search-container01-10
• Каждую минуту по крону
• Только для тех индексов, для которых обновились TSV файлы
• touch <TSV файл> - индекс будет перестроен заново
• indexer срабатывает
• Small - мили сек
• Medium - сек
• Main - десятки сек
• Archive - сотни сек
SphinxSearch Meetup #2 @ Avito
Примерные объемы данных
• Индексы - десятки гб на ноду
• TSV файлы - десятки гб на ноду
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Объединение контейнеров в кластере
SphinxSearch Meetup #2 @ Avito
HAproxy on app's
Search-pool01...10
Search-container01 ... Search-container10
Node1 ... Node10 Node91 ... Node100
Архитектура поискового кластера
Конфигурация индексов, поля и атрибуты, порядок соотв. колонкам в TSV
файле
#!/usr/bin/env bash
INDEX='items'
INDEX_CHUNKS="archive.gz main.gz medium.tsv small.tsv"
MAX_NODE_ID=100
HAS_KL=1
SOURCE_CONF="
field = title
field = description
attr_bigint = price
attr_uint = user_id
attr_uint = category_id
attr_uint = location_id
...
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Конфигурация индексов, привязка tsv файлам ноды
source s_archive_1:s_template
{
tsvpipe_command = ssh ... -q cron-server cat /path/to/tsv/archive_1.*gz | zcat
}
index archive_1:template
{
type = plain
source = s_archive_1
path = /home/sphinx/src/archive_1
}
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Конфигурация индексов, Sphinx KL
source s_kl_main_1:s_kl_template
{
xmlpipe_command = ssh ... -q cron-server /path/to/kl.sh /path/to/tsv/main_1.*gz
}
source s_main_1:s_template
{
tsvpipe_command = ssh ... -q cron-server cat /path/to/tsv/main_1.*gz | zcat
}
index main_1:template
{
type = plain
source = s_main_1
source = s_kl_main_1
path = /path/to/indexes/main_1
}
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Конфигурация индексов, сборка индекса ноды
index items_1
{
type = distributed
local = archive_1
local = main_1
local = medium_1
local = small_1
}
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Конфигурация индексов, сборка индекса контейнера
index items
{
type = distributed
agent = localhost:9314:items_1
agent = localhost:9314:items_11
agent = localhost:9314:items_21
agent = localhost:9314:items_31
agent = localhost:9314:items_41
agent = localhost:9314:items_51
agent = localhost:9314:items_61
agent = localhost:9314:items_71
agent = localhost:9314:items_81
agent = localhost:9314:items_91
}
SphinxSearch Meetup #2 @ Avito
Архитектура поискового кластера
Конфигурация индексов, сборка индекса кластера
index items
{
type = distributed
agent = search-container01:9314:items
agent = search-container02:9314:items
agent = search-container03:9314:items
agent = search-container04:9314:items
agent = search-container05:9314:items
agent = search-container06:9314:items
agent = search-container07:9314:items
agent = search-container08:9314:items
agent = search-container09:9314:items
agent = search-container10:9314:items
}
SphinxSearch Meetup #2 @ Avito
Оптимизации и лайфхаки
• Обновите версию Sphinx
• threads, thread_pool
• local, dist_threads, agent, max_children
• Ограничение выдачи
• Обратный поиск
• Универсальные поля
• JSON атрибуты
• Ranker может многое
SphinxSearch Meetup #2 @ Avito
Время генерации/мержа, кол-во items
Отставание Small
Отставание medium
Спасибо, вопросы ?Спасибо, вопросы ?Спасибо, вопросы ?Спасибо, вопросы ?Спасибо, вопросы ?

"Бэк-офис в Avito: миллиард объявлений на 10 серверах" Вячеслав Крюков (Avito)