Обзор модуляЧасть IV · ~9 ч · Сложность: (продвинутый) · Пререквизиты: Модуль 6, 9, 10
Каскад ранжирования (ranking cascade) — это инженерный ответ на фундаментальный конфликт поиска: самые точные модели релевантности слишком дороги, чтобы применять их ко всему индексу. Глубокая нейросетевая модель (Модуль 10) может оценить пару «запрос–документ» с великолепным качеством, но если в индексе сотни миллионов документов, прогнать её по каждому за отведённые на ответ десятки миллисекунд физически невозможно. Каскад разрешает конфликт, разбивая ранжирование на последовательность стадий нарастающей точности и стоимости: каждая следующая стадия дороже и умнее предыдущей, но получает на вход всё меньше документов, потому что предыдущая стадия уже отсеяла безнадёжных кандидатов.
В сквозном конвейере «обход → индекс → факторы → ранжирование → выдача → постобработка → измерение» этот модуль — сердце стадии «ранжирование» и начало стадии «выдача». На вход он принимает понятый и классифицированный запрос (Модуль 5), обращается к обратному индексу (Модуль 4), считает текстовые факторы (Модуль 6), факторы графа (Модуль 7) и поведенческие сигналы (Модуль 11), применяет обучаемые модели ранжирования (Модуль 9) и нейропоиск (Модуль 10), а на выходе отдаёт упорядоченный список, который дальше обрабатывает постранжирование (Модуль 16) и измеряют метрики (Модуль 19). Каскад — это архитектура исполнения, которая всё это связывает в единый поток, укладывающийся в бюджет латентности.
В этом модуле вы научитесь проектировать каскад L0→L1→L2→L3, понимать компромисс «точность против стоимости» на каждой стадии, строить дешёвый отбор кандидатов (matching) и быстрые признаки, выбирать главную модель и реранк, переключать формулу ранжирования под класс запроса (прямая связь с классификатором интента из Модуля 5), а также управлять бюджетом латентности, глубиной выдачи (пейджинг) и кэшированием результатов.
Как читать по трекамИнтуиция. Представьте отбор в большую компанию. Сначала автоматический фильтр резюме по формальным признакам отсеивает 99% (дёшево, грубо). Потом короткое телефонное интервью отсеивает ещё часть (дороже, точнее). Потом очное собеседование с командой (ещё дороже). И наконец — финал с руководителем для двух-трёх кандидатов (очень дорого, очень точно). Никто не зовёт сразу всех на финал — это и есть каскад: чем дороже стадия, тем меньше кандидатов до неё доходит.
- Студент CS — обязательно всё. Ядро — глава 12.1 (зачем многоступенчатость, математика воронки точность/стоимость) и 12.3 (модель L2 и реранк L3). Лабы по расчёту бюджета стоимости и по матрице правил выбора формулы — делать руками.
- Инженер поиска/ML — обязательно всё, особенно инженерные заметки про отсечение постинг-листов, ранний выход (early termination), бюджет латентности и инвалидацию кэша. Это ваша повседневная работа по эксплуатации поиска.
- SEO-специалист — обязательно глава 12.1 (почему попасть в кандидаты L0 — необходимое условие, без него ничего не поможет) и врезки про выбор формулы под интент (12.3) и про глубину выдачи/пейджинг (12.4). Математику воронки — обзорно.
- Смешанный/руководитель — Обзор модуля, интуиции, заблуждения, итоги; глава 12.4 целиком (латентность и кэш — это прямые деньги и UX). Формулы стоимости — по диагонали, но запомните идею «дорогая стадия видит мало документов».
- 12.1. Стадии L0→L1→L2→L3: зачем многоступенчатость, компромисс точность/стоимость (продвинутый)
- 12.2. Дешёвый отбор кандидатов (matching) и быстрые фичи на ранних стадиях (продвинутый)
- 12.3. Главная модель (L2) и реранк (L3); выбор формулы под класс запроса (продвинутый)
- 12.4. Бюджет латентности, пейджинг/глубина выдачи, кэширование результатов (средний)
Цели обучения
После главы студент сможет:
- Объяснить, почему одной модели ранжирования недостаточно и откуда берётся многоступенчатость.
- Сформулировать центральный компромисс каскада: точность стадии против её стоимости на документ.
- Описать роль каждой стадии L0, L1, L2, L3 и сужение воронки (сколько документов доходит до каждой).
- Вывести и оценить общую стоимость каскада как сумму «стоимость на документ × число документов» по стадиям.
- Объяснить, почему ошибки ранних стадий необратимы (потеря recall), а ошибки поздних — нет.
Зачем вообще многоступенчатость
Сформулируем задачу строго. Дан запрос q и индекс из N документов (N ∼ 10⁸–10¹¹). Нужно за бюджет времени T_budget (обычно десятки–сотни миллисекунд на серверной части) вернуть k лучших по релевантности документов (k ∼ 10–1000 в зависимости от глубины запрошенной выдачи). Пусть score(q, d) — «идеальная» функция релевантности, и пусть её вычисление стоит c единиц времени на один документ.
Наивное решение «посчитать score для всех N и отсортировать» стоит N · c. Подставим числа: даже если N = 10⁹, а c = 1 мкс (оптимистично для лёгкой формулы и нереалистично для нейромодели, где c ∼ миллисекунды), получаем 10⁹ · 10⁻⁶ с = 1000 с. Это в десятки тысяч раз больше бюджета. Вывод жёсткий: полный перебор индекса с дорогой моделью невозможен в принципе.
Ключевое наблюдение, на котором держится весь каскад: чтобы найти топ-k, не нужно точно ранжировать всё — достаточно точно ранжировать только тех, кто имеет шанс попасть в топ. Остальных можно грубо отбросить дешёвым фильтром. Если дешёвый фильтр не выбрасывает истинных финалистов (сохраняет recall@k), то итоговое качество почти не страдает, а стоимость падает на порядки.Интуиция. У нас есть дешёвая глупая оценка и дорогая умная. Если применять умную ко всему — не уложимся во время. Если применять дешёвую ко всему и на этом остановиться — потеряем качество. Каскад берёт лучшее: дешёвой оценкой быстро отсекаем заведомо плохих, а дорогую тратим только на горстку финалистов.
Стадии каскада: L0 → L1 → L2 → L3
Канонический каскад веб-поиска состоит из четырёх логических стадий. Конкретные границы и названия в разных системах плавают, но структура «воронки» одинакова.
Код: Выделить всё
Стадия | Что делает | Модель/механизм | Сколько на входе | Сколько на выходе | Стоимость на документ
--------------------------------------------+---------------------------------------------------------------------+--------------------------------------------------------------------+------------------------+-------------------------------------+----------------------------------------------
L0 — отбор кандидатов (matching/retrieval) | Найти все документы, что вообще «про запрос» | Пересечение/объединение постинг-листов, ANN-поиск по эмбеддингам | весь индекс N (∼10⁹) | ∼10³–10⁵ кандидатов | почти ноль (обход индекса)
L1 — предварительный отбор (pre-ranking) | Грубо отсортировать кандидатов, оставить перспективных | Лёгкая линейная формула / маленькое дерево на дешёвых фичах | ∼10³–10⁵ | ∼10²–10³ | очень мало
L2 — главное ранжирование (full ranking) | Точно упорядочить по богатой модели | Большой ансамбль GBDT / нейромодель (Модуль 9, 10) | ∼10²–10³ | ∼10²–10³ (то же, переупорядочены) | дорого
L3 — реранк / постобработка (re-ranking) | Доработать верх выдачи: контекст, листинг-эффекты, диверсификация | Listwise-модель, cross-encoder, бизнес-правила | топ ∼10–100 | финальные k | очень дорого на документ, но документов мало
Нарисуем воронку:Интуиция (главная мысль модуля). Стоимость стадии на документ растёт слева направо — от «почти ноль» до «очень дорого». Число документов на входе стадии падает слева направо — от миллиарда до десятков. Эти два движения компенсируют друг друга: суммарное время каждой стадии остаётся в рамках бюджета. Чем дороже стадия — тем меньше документов до неё доходит. Это и есть форма воронки.
Код: Выделить всё
весь индекс N ≈ 10^9
┌───────────────────────────────────────┐
L0 │ отбор кандидатов (matching) │ стоимость/док ≈ 0
└───────────────┬───────────────────────┘
│ ≈ 10^3 – 10^5 кандидатов
┌──────┴──────────────┐
L1 │ pre-ranking │ стоимость/док: низкая
└──────┬──────────────┘
│ ≈ 10^2 – 10^3
┌────┴────────┐
L2 │ full ranking │ стоимость/док: высокая
└────┬────────┘
│ ≈ 10^2 – 10^3 (переупорядочены)
┌──┴───┐
L3 │rerank│ стоимость/док: оч. высокая
└──┬───┘
│ топ k ≈ 10 – 100 → постобработка (Модуль 16) → выдача
Обозначим: стадия i принимает n_i документов, тратит c_i времени на документ. Суммарное время каскада:
Код: Выделить всё
T = Σ n_i · c_i = n_0·c_0 + n_1·c_1 + n_2·c_2 + n_3·c_3
i
Пример (порядки величин). Пусть бюджет на ранжирование T_budget ≈ 100 мс. Распределим:
- L0: обход индекса даёт n_1 = 50 000 кандидатов; стоимость отбора ≈ 20 мс (доминирует чтение постингов, не «на документ»).
- L1: n_1 = 50 000 документов, лёгкая формула c_1 = 0.5 мкс/док → 50 000 · 0.5 мкс = 25 мс. Оставляем топ n_2 = 1000.
- L2: n_2 = 1000 документов, GBDT-ансамбль c_2 = 30 мкс/док → 1000 · 30 мкс = 30 мс. Оставляем топ n_3 = 50.
- L3: n_3 = 50 документов, тяжёлый cross-encoder c_3 = 400 мкс/док → 50 · 400 мкс = 20 мс.
Итого ≈ 20 + 25 + 30 + 20 = 95 мс — уложились. А теперь представьте L2-модель на всех 50 000: 50 000 · 30 мкс = 1.5 с — провал. Или L3-cross-encoder на 1000: 1000 · 400 мкс = 400 мс — тоже провал. Воронка — единственный способ задействовать дорогие модели в бюджете.
Почему ошибки ранних стадий необратимыИнженерная заметка. Бюджет каждой стадии — это управляемая ручка: можно увеличить n_2 (точнее, но дороже L2) или взять более лёгкую L2-модель (быстрее, но грубее). Эти настройки балансируют по A/B-тестам (Модуль 19) против целевой метрики качества при ограничении на p99-латентность. Каскад — это пространство компромиссов, а не одна жёсткая конфигурация.
Это центральное свойство каскада, которое определяет всю инженерную дисциплину.
Отсюда асимметрия требований к стадиям:Интуиция. Поздняя стадия может только переупорядочить то, что ей дали, — она физически не видит документов, отброшенных раньше. Если истинно лучший документ выкинули на L0 или L1, никакая гениальная L3-модель его не вернёт. Кандидата нет в воронке — значит, его нет в выдаче. Точка.
- Ранние стадии (L0, L1) оптимизируют recall — «не потерять хорошего». Им разрешено пропускать много мусора вперёд (низкая precision), но нельзя выбрасывать истинных финалистов. Метрика ранних стадий — recall@(размер выхода): какая доля истинного топ-k дошла до следующей стадии.
- Поздние стадии (L2, L3) оптимизируют precision/порядок — точное ранжирование уже отобранного множества. Их метрика — NDCG/MAP по финальной выдаче (Модуль 19).
Код: Выделить всё
recall важен здесь precision/порядок важны здесь
▼ ▼
L0 ───────► L1 ───────► L2 ───────► L3
(не теряй) (не теряй) (упорядочь) (доработай верх)
Частые заблужденияИнженерная заметка. Поэтому пороги отсечения на ранних стадиях ставят с запасом: лучше пропустить лишнюю тысячу кандидатов (немного больше стоимость L1/L2), чем потерять одного настоящего финалиста (необратимая потеря качества). Запас отсечения — это плата за страховку recall.
Заблуждение. «Главное — самая умная модель в конце, остальное неважно.» Нет: качество финала ограничено сверху тем, что отобрали ранние стадии. Если recall L0/L1 низкий, даже идеальная L3 не вытащит — лучшего документа просто нет во входе. Ранние стадии не менее критичны, чем поздние; они задают потолок.
Заблуждение. «Каскад нужен только из-за нехватки железа; добавим серверов — и сделаем одну большую модель на всё.» Нет: проблема не только в железе, а в физике латентности. Пользователь ждёт ответ десятки–сотни миллисекунд; прогнать дорогую модель по миллиарду документов нельзя ни на каком разумном числе машин в этот бюджет. Каскад — структурное, а не временное решение.
Лаба / практикаЗаблуждение (SEO). «Если страница хорошая, она пробьётся в топ.» Сначала страница должна попасть в кандидаты L0 — то есть содержать термины запроса (или их близкие эмбеддинги) в индексируемых зонах. Не попала в matching — выбыла до всякого ранжирования, и качество контента уже неважно. Условие «быть найденным» предшествует условию «быть ранжированным высоко».
Расчёт бюджета каскада. Дано: бюджет ранжирования T_budget = 80 мс; четыре варианта L2-модели со стоимостями c_2 ∈ {10, 30, 80, 200} мкс/док и относительным качеством (условный NDCG-выигрыш) {0.0, +0.8, +1.2, +1.4}; L0 фиксированно даёт 40 000 кандидатов за 18 мс; L1 стоит 0.4 мкс/док и сужает до n_2; L3 стоит 350 мкс/док на топ-40 (фикс).
Шаги:
- Для каждого варианта L2 найдите максимальное n_2, при котором весь каскад укладывается в 80 мс.
- Постройте таблицу «вариант L2 → допустимое n_2 → ожидаемое качество». Учтите, что слишком маленькое n_2 режет recall перед L2 (примите штраф к качеству −0.5, если n_2 < 300).
- Выберите конфигурацию с лучшим итоговым качеством в бюджете. Обоснуйте.
Контрольные вопросы
- Почему наивное «посчитать дорогую модель для всех документов и отсортировать» неосуществимо? Оцените порядок времени.
- Сформулируйте центральный компромисс каскада одной фразой. Как n_i и c_i компенсируют друг друга?
- Какую метрику оптимизируют ранние стадии и какую — поздние? Почему по-разному?
- Почему ошибка ранней стадии необратима, а поздней — нет? Приведите пример.
- Вы хотите внедрить тяжёлую нейромодель. На какой стадии каскада это реалистично и почему именно там?
- Что произойдёт с качеством выдачи, если поднять порог отсечения L1 слишком высоко (оставлять мало кандидатов)? А если слишком низко?
- (SEO) Почему «попасть в кандидаты L0» — необходимое, но не достаточное условие высокой позиции?
- Как изменится распределение бюджета по стадиям, если T_budget урезали вдвое?
Цели обучения
После главы студент сможет:
- Реализовать отбор кандидатов (L0) через постинг-листы: пересечение, объединение, режимы AND/OR.
- Объяснить алгоритмы раннего выхода (WAND/Block-Max WAND, MaxScore) и зачем они сокращают обход индекса.
- Добавить векторный отбор (ANN-поиск по эмбеддингам) и гибридную выборку кандидатов (Модуль 10).
- Спроектировать быстрые признаки L1, вычислимые без обращения к телу документа.
- Обосновать, почему L1-формула должна быть дешёвой и монотонной по recall.
L0: отбор кандидатов как работа с постинг-листами
L0 не ранжирует тонко — он отвечает на вопрос «какие документы вообще стоит рассматривать?». Источник кандидатов — обратный индекс (Модуль 4). Для запроса из терминов {t1, t2, …} система берёт их постинг-листы (отсортированные списки docID) и комбинирует.
Два базовых режима:
- Конъюнктивный (AND): документ-кандидат должен содержать все обязательные термины. Это пересечение постинг-листов — мало кандидатов, высокая precision, риск нулевой выдачи (если совпадений нет).
- Дизъюнктивный (OR): достаточно любого термина. Это объединение — много кандидатов, высокий recall, дороже.
Пример. Запрос «купить смартфон x2 дёшево». Дерево запроса: купить (опционально, коммерческий маркер), смартфон x2 (обязательно + синсет {модель x2}), дёшево (опционально, синсет {недорого, цена}). L0 берёт пересечение по обязательному смартфон x2, добавляет документы из синсета, опциональные термины не сужают, а влияют на скор позже. Так из миллиарда документов остаётся, скажем, 30 000 кандидатов.
Векторный и гибридный отбор кандидатовИнженерная заметка. Чистый OR по частым терминам может дать миллионы кандидатов — это убивает бюджет. Поэтому применяют динамическое отсечение: обходят постинг-листы не до конца, а пока есть шанс улучшить топ-k. Алгоритмы: WAND (Weak AND) и Block-Max WAND, MaxScore. Идея — хранить верхнюю границу вклада каждого терма (max_score по листу), и если даже максимально возможный довесок не поднимет документ выше текущего k-го порога, документ/блок пропускают целиком. Это ранний выход (early termination): индекс не дочитывается, экономятся обращения к памяти/диску.
Лексический отбор по постингам не находит документ, в котором нет ни одного термина запроса, даже если он идеально про тему (проблема словарного разрыва, vocabulary mismatch). Здесь подключается плотный отбор (dense retrieval, Модуль 10): запрос и документы кодируются в эмбеддинги, кандидаты ищутся как ближайшие соседи в векторном пространстве через ANN-индекс (HNSW, IVF-PQ и т.п.).
Гибридный отбор объединяет два пула кандидатов (лексический + векторный) перед L1. Это повышает recall L0 за счёт покрытия и точных совпадений, и смысловых. Объединение — по docID, дубли сливаются, сигналы из обоих каналов сохраняются как фичи.Интуиция. Лексический отбор спрашивает «есть ли эти слова?», векторный — «про это ли по смыслу?». Запрос «как убрать жвачку с одежды» найдёт по векторам статью «удаление липких пятен с ткани», даже без общих слов.
Код: Выделить всё
запрос
├──► лексический отбор (постинги, WAND) ──┐
│ ├──► объединить → кандидаты L0
└──► векторный отбор (ANN по эмбеддингам) ─┘
После L0 у нас тысячи–десятки тысяч кандидатов — слишком много для дорогой L2. Задача L1 — дёшево отсортировать и сузить до сотен, не потеряв финалистов. Отсюда требования к признакам L1:
- Дёшево вычислимы — желательно из самого индекса/постингов, без чтения тела документа и без сетевых обращений.
- Предвычислимы — статические факторы документа (длина, авторитет, язык) считаются на этапе индексации и лежат рядом с постингом.
- Монотонны по recall — формула не должна систематически выбрасывать классы релевантных документов.
Код: Выделить всё
Группа | Примеры | Откуда | Стоимость
-----------------------+-------------------------------------------------------------+-----------------------------------------------+-----------------------------------------
Текстовые лёгкие | BM25 по индексу, число совпавших термов, покрытие запроса | постинги (Модуль 6) | низкая (часто уже посчитано при отборе)
Статические документа | авторитет/PageRank, длина, язык, спам-скор | предвычислено при индексации (Модуль 7, 16) | ≈ ноль (чтение поля)
Векторные | косинус запрос–документ | из ANN-отбора (Модуль 10) | ≈ ноль (побочный продукт отбора)
Запросные | класс интента, длина запроса | из понимания запроса (Модуль 5) | ≈ ноль (один на запрос)
Внимание. Соблазн «впихнуть в L1 побольше точности» опасен: каждая лишняя фича умножается на n_1 (десятки тысяч) и съедает бюджет. Дорогие фичи (proximity по позициям, тело документа, кросс-энкодер) — это L2/L3, где документов уже сотни. Правило: фича живёт на той стадии, где её суммарная стоимость n_i · c_feature терпима.
Инженерная заметка. Чтобы L1 не читал тело документа, его данные форвард-индексируют: рядом с документом хранят предвычисленный вектор статических фич. Тогда L1 — это «дешёвое скалярное произведение по уже лежащим в памяти числам». Архитектурно ранние стадии живут как можно ближе к индексу (часто на тех же шардах), чтобы не платить за сеть на десятках тысяч кандидатов.
Частые заблужденияSEO-врезка. Чтобы пройти L0, страница должна содержать термины запроса (или семантически близкий текст) в индексируемых, видимых зонах (Модуль 6: title, заголовки, тело). Чтобы пройти L1, помогают сильные статические сигналы — авторитет домена (Модуль 7) и отсутствие спам-меток (Модуль 16): они дёшевы и потому учитываются рано, фактически решая, доберётесь ли вы до главной модели вообще.
Заблуждение. «Отбор кандидатов — это и есть поиск, дальше просто сортировка.» Нет: L0 даёт множество без точного порядка; качество итоговой выдачи определяется в основном L2/L3. Но L0 задаёт потолок (что вообще можно показать).
Заблуждение. «Векторный отбор всегда лучше лексического, лексика устарела.» Нет: плотный отбор размывает точные совпадения (имена, артикулы, редкие термины, точные фразы). На навигационных и точных запросах лексика точнее. Промышленное решение — гибрид, а не замена.
Лаба / практикаЗаблуждение. «WAND/ранний выход портят качество, ведь индекс не дочитан.» При корректной верхней границе вклада терма ранний выход не меняет топ-k — он пропускает только тех, кто заведомо не пройдёт порог. Это точная оптимизация, а не приближение (для точных границ).
Отбор кандидатов и L1-скоринг. Дан мини-индекс: 12 документов с постинг-листами по 5 терминам, предвычисленные статические фичи (authority ∈ [0,1], length, spam ∈ {0,1}) и для трёх документов — векторные косинусы к запросу.
Шаги:
- Для запроса из 3 термов (один обязательный, два — синсет) постройте пул кандидатов в режимах AND и OR. Сравните размеры и состав.
- Добавьте к лексическому пулу 3 «векторных» кандидата (нет общих термов, но высокий косинус). Получите гибридный пул.
- Посчитайте L1-скор s = 1.0·BM25 + 0.7·authority + 0.4·cos − 2.0·spam и оставьте топ-5.
- Проверьте, попал ли в топ-5 заранее отмеченный «эталонный финалист». Если нет — какой признак его «утопил» и не нарушает ли это требование recall?
Контрольные вопросы
- Чем отличаются конъюнктивный и дизъюнктивный режимы отбора по precision/recall и стоимости?
- Что такое ранний выход (WAND/MaxScore) и почему он не портит топ-k при корректных границах вклада терма?
- Что такое словарный разрыв и как векторный отбор его закрывает? Где векторный отбор уступает лексическому?
- Перечислите три требования к признакам L1 и объясните каждое.
- Почему proximity по позициям — плохая фича для L1, но хорошая для L2?
- Как форвард-индексирование статических фич помогает удержать стоимость L1?
- (SEO) Какие два класса сигналов чаще всего решают, пройдёт ли страница ранние стадии, и почему именно дешёвые?
- Почему чистый OR по частым терминам опасен и чем его ограничивают?
Цели обучения
После главы студент сможет:
- Описать роль главной модели L2 (full ranking) и её типичную реализацию (GBDT-ансамбль, нейроранкер; Модуль 9, 10).
- Объяснить, чем реранк L3 отличается от L2: listwise-эффекты, контекст листинга, диверсификация, cross-encoder.
- Спроектировать механизм выбора формулы под класс запроса: флаги интента → набор правил → конкретная модель (связь с Модулем 5).
- Обосновать, почему одна универсальная формула проигрывает специализированным профилям.
- Понимать, как класс интента становится признаком модели и как обрабатывается неоднозначность.
L2: главная модель ранжирования
На L2 приходят сотни–тысячи кандидатов с уже посчитанными лёгкими фичами. Здесь система впервые позволяет себе дорогие признаки и богатую модель:
- Дорогие текстовые фичи: proximity, минимальное покрывающее окно, фразовость, зональные веса (Модуль 6) — требуют позиций и/или тела документа.
- Графовые и поведенческие: авторитет, клик-модели, dwell-time (Модуль 7, 11).
- Семантические: скоры нейромоделей, dense/late-interaction совпадение (Модуль 10).
L3: реранк и постранжированиеИнтуиция. L2 — это «очное собеседование»: на каждого кандидата тратим заметно больше, смотрим много сигналов, получаем точный балл. Но именно поэтому кандидатов уже не миллионы, а сотни — иначе не успеть.
L2 ранжирует каждый документ независимо (pointwise по входу: фичи пары (q,d), без оглядки на соседей по выдаче). Но качество выдачи — это свойство списка целиком, а не суммы независимых баллов. Этим занимается L3.
Что добавляет L3 поверх топа от L2:
- Listwise-эффекты: учёт взаимного влияния позиций; например, два почти одинаковых документа не должны занимать места 1 и 2 — нужна диверсификация (передаётся в постобработку; разнообразие — Модуль 15).
- Контекст листинга: как документ смотрится среди соседей, в каком блоке/вертикали (Модуль 14 — федерация, если есть).
- Тяжёлая семантика: cross-encoder — модель, которая совместно кодирует запрос и документ (а не по отдельности, как dense-отбор), давая высшую точность совпадения, но стоящая сотни микросекунд–миллисекунды на пару. Применима только к десяткам финалистов — ровно потому, что их мало.
- Бизнес-правила и фильтры: возрастные/правовые ограничения, family-фильтры, региональные требования.
Интуиция. L3 — это «финал с руководителем»: смотрим не на каждого отдельно, а на команду в сборе — кто кого дополняет, нет ли дублей, как набор выглядит как ответ целиком. Очень дорого на кандидата, но кандидатов единицы.
Выбор формулы под класс запроса — мост к Модулю 5Инженерная заметка. Граница L2/L3 подвижна: часть систем делают «L2 = GBDT по всем фичам, L3 = cross-encoder + диверсификация», другие сливают их в одну тяжёлую стадию. Важна не буква, а принцип: pointwise-точность → listwise/контекстная доработка верха, на всё меньшем числе документов.
Это центральный механизм главы и прямое продолжение классификатора интента (Модуль 5.3). Идея: не существует одной формулы, оптимальной для всех запросов. Навигационный, информационный, коммерческий и свежий запросы хотят разного порядка факторов.
Механизм выбора — трёхзвенная цепочка (флаги интента → правила → модель):Интуиция. Класс интента — это «режим работы» ранжирования. Навигационный запрос («сайт банка X») — «найди одну правильную дверь»: доминирует точное совпадение + авторитет домена, свежесть почти не важна. Свежий запрос («что случилось сегодня») — «принеси новое прямо сейчас»: вес даты огромен, авторитет вторичен. Одна формула не обслужит оба режима хорошо.
Код: Выделить всё
[Модуль 5: понимание запроса]
classify(q) → флаги интента:
intent ∈ {informational, navigational, commercial, fresh}
geo_flag ∈ {0,1}, freshness_flag ∈ {0,1}, ambiguous (распределение p(intent))
│
▼
[Звено 1: набор правил выбора профиля]
правила (декларативный конфиг), напр.:
IF navigational → profile = NAV
IF commercial AND geo_flag → profile = COMMERCE_LOCAL
IF fresh OR freshness_flag → profile = FRESH
ELSE → profile = GENERAL
│
▼
[Звено 2: конкретная модель/набор весов]
profile → (ranking_model_id, feature_weights, источники, фильтры)
NAV → модель с высоким весом exact-match и authority, низким — freshness
FRESH → модель с высоким весом date/recency, подмешать свежий индекс
COMMERCE_LOCAL → подмешать товарную вертикаль + геофильтр радиусом
│
▼
[применяется на L2/L3 к кандидатам]
Звено 2 — модель/веса. Профиль выбирает конкретный артефакт ранжирования: либо отдельную обученную L2-модель, либо набор весов/гиперпараметров над общей моделью, плюс настройку источников (какие индексы подмешать) и фильтров (Модуль 16).
Инженерная заметка. На практике это реализуют тремя способами, часто в комбинации:
1. Жёсткое переключение: предсказанный класс выбирает одну из специализированных моделей.
2. Мягкая смесь: итоговый скор — взвешенная по p(intent) смесь скоров профилей (плавно для неоднозначных запросов).
3. Класс как признак: флаги интента подаются внутрь единой модели как фичи, и она сама учится менять поведение. Чаще всего сочетают: грубое переключение профиля + интент-фичи внутри модели.
Внимание. Ошибка классификатора интента дорогая: она выбирает не ту формулу для всего запроса, а не для одного документа. Навигационный запрос, ошибочно принятый за информационный, выдаст «библиотеку статей» вместо одной нужной двери. Поэтому пары классов с дорогой путаницей (нав↔инфо) контролируют отдельно (Модуль 5, 19).
Пример. Запрос «лук». Классификатор даёт распределение: p(commercial)=0.4, p(informational)=0.4 (овощ vs оружие), сигналов мало → ambiguous. Правила: при неоднозначности не выбирать жёсткий профиль, а включить диверсификацию в постобработке (Модуль 15) — в топ по одному представителю каждой интерпретации. Если же запрос «лук репчатый цена» — commercial уверенно → профиль COMMERCE, товарная вертикаль, вес цены/наличия вверх.
Частые заблужденияSEO-врезка. Поскольку под класс запроса включается своя формула и свой набор источников, оптимизировать страницу надо под доминирующий интент конкретного запроса, а не «вообще». Навигационный запрос ранжирует по бренду и авторитету — туда не пробиться обзорной статьёй; коммерческий хочет карточку с ценой; информационный — разбор. Посмотрите, какой тип страниц система уже держит в топе — это её ответ на вопрос «какой здесь интент» — и приведите тип своей страницы в соответствие (мост к Модулю 5.3).
Заблуждение. «L2 уже даёт идеальный список, L3 — косметика.» Нет: L2 оценивает документы независимо и не видит листинг целиком. Дубли, отсутствие разнообразия, листинг-контекст и тяжёлая семантика (cross-encoder) — задача L3, и она ощутимо меняет верх выдачи.
Заблуждение. «Хорошая модель сама разберётся со всеми интентами, профили — лишнее.» Единая модель с интент-фичами помогает, но специализированные профили + правильные источники под класс дают выигрыш, который трудно выжать из одной формулы: разные интенты хотят разных источников и фильтров, а не только весов.
Лаба / практикаЗаблуждение. «Класс интента — это выход ранжирования (метка результата).» Нет: класс интента — это вход, управляющий сигнал, который выбирает формулу, источники и постобработку ещё до скоринга (Модуль 5.3).
Матрица выбора формулы под интент. Дано: 8 запросов с предсказанными флагами интента (класс + geo_flag, freshness_flag, признак неоднозначности) и четыре профиля ранжирования с описанными приоритетами факторов (NAV, GENERAL, FRESH, COMMERCE_LOCAL).
Шаги:
- Напишите набор правил (звено 1) в виде упорядоченного списка IF-условий над флагами, отображающий запрос → профиль. Учтите ортогональность гео/свежести (могут комбинироваться).
- Для каждого запроса примените правила и выпишите выбранный профиль; для неоднозначных — пометьте «диверсификация (Модуль 15)» вместо жёсткого профиля.
- Для двух запросов опишите набор весов профиля (какие факторы вверх/вниз) и какие источники подмешиваются.
- Подберите запрос, на котором ошибка классификатора (нав→инфо) даёт заметно плохую выдачу; объясните, почему именно эта пара классов дорогая.
Контрольные вопросы
- Какие классы признаков впервые появляются на L2 и почему их нельзя было считать на L1?
- Чем принципиально L3 отличается от L2 (pointwise vs listwise/контекст)? Назовите три задачи L3.
- Что такое cross-encoder и почему его место — на L3, а не на L0/L1?
- Опишите трёхзвенную цепочку выбора формулы под класс запроса. Что делает каждое звено?
- Назовите три способа реализовать зависимость ранжирования от интента; чем хороша «мягкая смесь»?
- Почему ошибка классификатора интента дороже ошибки в скоре одного документа?
- Как система поступает с неоднозначным запросом и в каком модуле это реализуется?
- (SEO) Почему оптимизировать страницу надо под доминирующий интент конкретного запроса, и как этот интент определить по текущей выдаче?
Цели обучения
После главы студент сможет:
- Разложить бюджет латентности ответа по компонентам и объяснить роль хвостовых перцентилей (p95/p99).
- Применять приёмы удержания бюджета: тайм-ауты, деградация качества, ранний выход (связь с 12.2).
- Спроектировать пейджинг и глубину выдачи: почему глубокие страницы дороги и как их ограничивают.
- Спроектировать кэширование результатов на разных уровнях и обосновать политику инвалидации.
- Оценить эффект кэша на латентность и нагрузку.
Бюджет латентности и хвостовые перцентили
Время ответа поиска — сумма по компонентам, и каждый отъедает от общего бюджета T_budget:
Код: Выделить всё
T_total ≈ T_сеть + T_понимание_запроса + T_отбор(L0) + T_ранжирование(L1..L3)
+ T_федерация + T_постобработка + T_сборка_выдачи
Интуиция. Поиск — распределённая система: запрос «разлетается» по сотням шардов индекса, ответ собирается из всех. Итоговая латентность определяется самым медленным шардом (эффект «отстающего», straggler). Поэтому даже если медиана 30 мс, p99 может быть 200 мс — и именно его чувствует пользователь на «плохих» запросах. Оптимизируют хвост, а не среднее.
Инженерная заметка. Приёмы удержания хвоста: тайм-ауты на стадии (если шард/стадия не успела — берём, что есть), hedged requests (дублировать запрос к реплике, взять первый ответ), деградация качества (под нагрузкой урезать n_2, отключить L3-cross-encoder, сузить расширение запроса). Деградация — это осознанный размен «чуть хуже качество ↔ уложиться в бюджет», лучше, чем тайм-аут в ноль результатов.
Пейджинг и глубина выдачиЗаблуждение. «Оптимизируй среднее время — и всё хорошо.» Нет: пользователи и SLA живут в хвосте. Система с отличным средним и ужасным p99 ощущается «тормозной», потому что заметные задержки бьют по самым сложным/ценным запросам.
Пользователь видит страницами по m результатов (например, 10). Запрос «страница 1» просит топ-10, «страница 5» — результаты 41–50. Наивно «страница p» = «посчитать топ-(p·m) и отдать хвост». Проблема:
Отсюда инженерные решения:Интуиция. Чтобы отдать результаты 41–50, надо корректно ранжировать как минимум топ-50 — а это значит протащить через дорогие стадии больше кандидатов (увеличить n_2, n_3). Глубина выдачи прямо увеличивает стоимость: чем глубже страница, тем больше документов надо точно отранжировать. При этом ценность глубоких страниц для пользователя мала (почти никто не ходит дальше первых страниц).
- Ограничение глубины: система не отдаёт результаты глубже некоторого предела (например, нескольких сотен) — дальше качество ранжирования всё равно низкое, а стоимость высокая.
- Курсорный пейджинг вместо offset: вместо «пропусти 1000, дай 10» передают «продолжи после вот этого результата» — позволяет дешевле и стабильнее листать, не пересчитывая всё с нуля.
- Стабильность между страницами: топ кэшируют на сессию запроса, чтобы при переходе на страницу 2 порядок не «прыгал» из-за свежей переиндексации.
Кэширование результатовSEO-врезка. Поскольку глубокие позиции дороги и почти не получают трафика, борьба идёт за первые позиции первой страницы. «Быть в индексе на странице 7» практически эквивалентно отсутствию: глубину часто и не ранжируют точно, и не показывают пользователю. Релевантность под доминирующий интент (12.3) — путь в видимый топ, а не объём страниц.
Кэш — главный рычаг и латентности, и нагрузки. Распределение запросов сильно неравномерно (закон Ципфа): небольшая доля популярных запросов даёт огромную долю трафика. Их результат можно посчитать один раз и переиспользовать.
Уровни кэширования:
Код: Выделить всё
Уровень | Что кэшируется | Ключ | Выигрыш
------------------------------------------------+---------------------------------------------------+-------------------------------------------------------------+---------------------------------------------
Кэш результатов (results cache) | готовая выдача (топ-k) | нормализованный запрос + контекст (регион, язык, фильтры) | максимальный: ответ без ранжирования вообще
Кэш постинг-листов / промежуточных пересечений | горячие постинги, пересечения частых пар термов | термы | ускоряет L0 для разных запросов
Кэш фич/скоров документов | предвычисленные статические фичи, скоры пар | docID / (q,d) | ускоряет L1/L2
Инвалидация — главная сложность кэша. Результат устаревает, когда:Внимание. Ключ кэша результатов должен включать весь контекст, влияющий на выдачу: нормализованный запрос (после понимания запроса, Модуль 5), регион/гео, язык, активные фильтры, иногда сегмент персонализации. Иначе пользователь получит чужой контекстный ответ. Слишком грубый ключ — неверные ответы; слишком детальный — низкий hit-rate.
- изменился индекс (новые/удалённые документы) — особенно критично для свежего интента (12.3): для него кэш должен быть короткоживущим или отключённым;
- изменилась модель ранжирования или правила выбора формулы (12.3) — после деплоя кэш надо сбрасывать или версионировать ключом модели.
Пример. Запрос «новости сегодня» — свежий интент: TTL кэша минуты или кэш выключен. Запрос «сайт центрального банка» — навигационный, ответ стабилен месяцами: TTL часы/сутки, высокий hit-rate. Один кэш, разные TTL по классу интента — снова видна связь с Модулем 5.
Частые заблужденияИнженерная заметка. Эффект кэша оценивают через hit-rate. Если доля попаданий h, средняя латентность ≈ h · T_кэш + (1−h) · T_полныйрасчёт, где Tкэш ≪ T_полныйрасчёт. При h = 0.5 и Tкэш ≈ 0 средняя латентность падает примерно вдвое, а нагрузка на ранжирующий кластер — на долю h. Поэтому кэш популярных запросов — самый дешёвый способ выиграть и скорость, и ёмкость.
Заблуждение. «Глубокий пейджинг бесплатен — просто отдать хвост уже посчитанного.» Нет: чтобы корректно отдать глубокую страницу, надо точно отранжировать больше кандидатов через дорогие стадии. Глубина прямо повышает стоимость, поэтому её ограничивают.
Заблуждение. «Кэш всегда ускоряет и ничего не портит.» Кэш с неверным ключом отдаёт чужой контекстный ответ; кэш без инвалидации отдаёт устаревшее (особенно больно для свежих запросов и после деплоя модели). Кэш требует дисциплины ключа и инвалидации.
Лаба / практикаЗаблуждение. «Оптимизировать надо среднюю латентность.» Нет: целевая метрика — хвост (p95/p99), потому что распределённый сбор по шардам упирается в самый медленный из них.
Бюджет, кэш и глубина. Дано: лог из 20 запросов с пометками класса интента и частоты; стоимость полного расчёта T_full = 90 мс, кэш-ответ T_cache = 2 мс; политика TTL «свежий = 1 мин, навигационный = 6 ч, прочее = 30 мин».
Шаги:
- Оцените hit-rate, если повторяются только частые запросы в пределах своего TTL; посчитайте ожидаемую среднюю латентность по формуле h·T_cache + (1−h)·T_full.
- Для свежего запроса покажите, как короткий TTL снижает hit-rate, но защищает от устаревания; обоснуйте компромисс.
- Для запроса «дайте страницу 6 по 10» оцените, во сколько раз вырастет n_2 относительно «страницы 1», и предложите ограничение глубины.
- Опишите, что должно произойти с кэшем после деплоя новой L2-модели, и как версионирование ключа решает проблему.
Контрольные вопросы
- Из каких компонентов складывается T_total ответа поиска?
- Почему бюджет задают по p95/p99, а не по среднему? Что такое эффект «отстающего» (straggler)?
- Назовите три приёма удержания хвоста латентности и поясните «деградацию качества».
- Почему глубокий пейджинг дороже мелкого? Какие два решения снижают его стоимость?
- Перечислите уровни кэширования и что выигрывает каждый.
- Что обязательно должно входить в ключ кэша результатов и почему слишком грубый/детальный ключ плохи?
- Когда и как инвалидируют кэш? Почему свежий интент требует особой политики TTL?
- Запишите формулу средней латентности через hit-rate и объясните эффект кэша на нагрузку.
- Каскад существует из-за конфликта «точность ↔ стоимость»: самые точные модели нельзя применить ко всему индексу в бюджете латентности, поэтому ранжирование разбивают на стадии.
- Главный принцип воронки: стоимость стадии на документ растёт (L0→L3), а число документов на входе падает на порядки — слагаемые n_i·c_i остаются в бюджете. Чем дороже стадия, тем меньше документов до неё доходит.
- Ранние стадии (L0/L1) оптимизируют recall, поздние (L2/L3) — точный порядок. Ошибка ранней стадии необратима: выброшенного кандидата поздние стадии не вернут — L0 задаёт потолок качества.
- L0 — отбор кандидатов (постинги + WAND/ранний выход + ANN/гибрид); L1 — дешёвый предотбор на быстрых, предвычисленных и монотонных по recall фичах.
- L2 — главная обучаемая модель на дорогих фичах (proximity, графовые, нейро); L3 — listwise-реранк: cross-encoder, диверсификация, контекст листинга, бизнес-правила.
- Формула выбирается под класс запроса по цепочке флаги интента (Модуль 5) → набор правил → конкретная модель/веса/источники. Реализации: жёсткое переключение, мягкая смесь по p(intent), интент как признак модели. Ошибка интента дорогая — она меняет формулу для всего запроса.
- Бюджет латентности оптимизируют по хвосту (p95/p99), а не по среднему; держат тайм-аутами, hedged-запросами и осознанной деградацией качества.
- Глубина выдачи стоит денег (ограничивают глубину, курсорный пейджинг), а кэш результатов — главный рычаг скорости/нагрузки при дисциплине ключа (весь контекст) и инвалидации (TTL по интенту, версия модели/индекса).
- Каскад ранжирования (ranking cascade) — последовательность стадий L0→L3 нарастающей точности и стоимости с сужением числа документов.
- L0 / отбор кандидатов (matching, retrieval) — стадия выбора множества потенциально релевантных документов из всего индекса.
- L1 / предотбор (pre-ranking) — дешёвое грубое ранжирование кандидатов для сужения перед главной моделью.
- L2 / главное ранжирование (full ranking) — точная обучаемая модель на богатом наборе фич.
- L3 / реранк (re-ranking) — listwise/контекстная доработка верха выдачи (cross-encoder, диверсификация, правила).
- Воронка точность/стоимость — структурный компромисс: дорогая стадия видит мало документов, дешёвая — много.
- Recall ранней стадии — доля истинного топ-k, дошедшая до следующей стадии; ключевая метрика L0/L1.
- WAND / Block-Max WAND / MaxScore — алгоритмы раннего выхода при обходе постинг-листов, отсекающие заведомо непроходные документы/блоки.
- Ранний выход (early termination) — прекращение обхода индекса, когда улучшить топ-k уже невозможно.
- Плотный / векторный отбор (dense retrieval, ANN) — поиск кандидатов по близости эмбеддингов; закрывает словарный разрыв.
- Гибридный отбор — объединение лексического и векторного пулов кандидатов перед L1.
- Cross-encoder — модель, совместно кодирующая пару (запрос, документ); высшая точность, высокая стоимость на пару, место — L3.
- Профиль ранжирования — набор модели/весов/источников/фильтров, выбираемый под класс запроса.
- Флаги интента → правила → модель — трёхзвенная цепочка выбора формулы под класс запроса (мост к Модулю 5).
- Бюджет латентности — целевое время ответа, задаваемое по хвостовым перцентилям (p95/p99).
- Эффект «отстающего» (straggler) — итоговая латентность распределённого сбора определяется самым медленным шардом.
- Деградация качества — осознанное упрощение ранжирования под нагрузкой ради удержания бюджета.
- Пейджинг / глубина выдачи — выдача результатов страницами; глубокие страницы дороже ранжировать.
- Кэш результатов (results cache) — хранение готовой выдачи по ключу «запрос + контекст».
- Инвалидация кэша — сброс/устаревание записей по TTL, событию (апдейт индекса) или версии модели.
- Hit-rate — доля запросов, обслуженных из кэша.
- Опирается на: Модуль 6 (текстовые фичи: BM25 дёшев для L1, proximity/phrase — дороги, место L2); Модуль 9 (learning-to-rank: GBDT/нейроранкеры — это и есть L2); Модуль 10 (нейропоиск: dense-отбор на L0, cross-encoder на L3).
- Использует результаты: Модуль 5 (флаги интента, дерево запроса — вход для отбора кандидатов и для выбора формулы под класс запроса); Модуль 4 (обратный/позиционный индекс, постинги — субстрат L0/L1); Модуль 7 (авторитет — дешёвая статическая фича L1); Модуль 11 (поведенческие сигналы — фичи L2).
- Питает дальше: Модули 15–16 (диверсификация, постранжирование, фильтры — продолжение L3); Модуль 14 (федерация вертикалей — переплетается с реранком и выбором источников под интент); Модуль 19 (A/B-тесты и метрики — как настраивают бюджеты стадий, пороги, профили и кэш).
- Тесно связан: Модуль 5.3 (классификация интента как переключатель конвейера — реализуется именно здесь, на стадиях L2/L3 и в выборе источников).
- Классические работы по каскадному ранжированию (cascade ranking models) и компромиссу эффективность/эффективность (efficiency–effectiveness trade-off) в IR.
- Обзоры по динамическому отсечению постинг-листов: WAND, Block-Max WAND, MaxScore и ранний выход в обходе индекса.
- Литература по плотному и гибридному отбору кандидатов (dense retrieval, ANN-индексы, late interaction) и его комбинации с лексическим поиском.
- Работы по listwise learning-to-rank и реранкингу с cross-encoder; место тяжёлых нейромоделей в каскаде.
- Материалы по инженерии латентности распределённого поиска: хвостовые перцентили, эффект отстающего, hedged requests, деградация качества.
- Обзоры по кэшированию в поисковых системах: уровни кэша, ключи, политики инвалидации, влияние закона Ципфа на hit-rate.