Рекомендации за 50 мс

Recommendations under 50 ms

Классическая задача, на которой видно, умеет ли инженер считать бюджет, а не только перебирать модели: качество ранжирования здесь ограничено не архитектурой сети, а миллисекундами и памятью.

Senior 5 шагов разбора · 3 развилки · 5 поломок из прода

Вводная

Маркетплейс: главная страница показывает блок «вам может понравиться». Сейчас там правило «популярное в категории», продукт хочет персонализацию.

Блок рендерится вместе со страницей, поэтому у рекомендаций свой бюджет — 50 мс на p99, иначе фронтенд рисует заглушку и персонализация не доезжает до пользователя.

Требования и ограничения
ЧтоЗначениеОткуда
Задержкаp99 ≤ 50 мсСчитается от входа в сервис до ответа, вместе с походом за признаками.
Нагрузка12 000 RPS в пикеВечерний пик втрое выше дневного плато.
Каталог30 млн товаровВ наличии одновременно около 8 млн: остальные скрыты фильтром доступности.
Аудитория5 млн DAUТреть — незалогиненные, истории нет.
СвежестьНовый товар виден за 15 минТребование каталога: акции живут часами.
Доступность99.9 %Отказ рекомендаций не должен ронять страницу.

Прежде чем читать разбор, попробуйте спроектировать систему сами — по этим требованиям. Дальше будет видно, что вы учли, а что нет.

Разбор по шагам

Разложить 50 мс по статьям

Первое действие — не выбор модели, а бюджет задержки: он определяет всё остальное.

Пока бюджет не разложен, любое обсуждение архитектуры беспредметно. Раскладка сразу показывает, что на само ранжирование остаётся меньше половины времени.

СтатьяБюджет p99Что в ней происходит
Сеть и сериализация5 мсЗапрос от фронтенда, ответ обратно, JSON.
пользователя8 мсОдин батч-запрос в онлайн-хранилище.
Отбор кандидатов10 мсANN-поиск плюс фильтры доступности.
Ранжирование15 мсСкоринг 300–500 кандидатов.
Бизнес-правила и сборка7 мсДедупликация, разнообразие, пины.
Запас5 мсGC, всплески, повторные попытки.
На практике

Бюджет назначается на p99, а не на среднее. Средняя в 12 мс отлично уживается с хвостом в 200 мс — а видит пользователь именно хвост.

Отсюда сразу следует главное архитектурное ограничение: скорить 30 млн товаров нельзя. При 15 мс на ранжирование и разумном железе бюджет — сотни кандидатов, а не миллионы.

Двухэтапная схема: отбор и ранжирование

Дешёвая модель сокращает 30 млн до сотен, дорогая расставляет их по местам.

  1. Отбор кандидатов (retrieval): несколько источников по 100–200 позиций — ANN-поиск по , «похожие на просмотренные», популярное в категории, свежие поступления.
  2. Слияние: объединение с дедупликацией даёт 300–500 кандидатов.
  3. Ранжирование (ranking): одна модель на всех кандидатов, пользователя, товара и пары.
  4. Пост-обработка: разнообразие по категориям, бизнес-пины, фильтр «уже куплено».

Стоимость этапов различается на порядки: ANN-поиск по 8 млн размерности 128 в HNSW укладывается в 1–3 мс при @100 около 0.95, а полный проход модели по каталогу — секунды. Именно поэтому каскад, а не одна модель.

Что означает каждый компонент
  • бюджет этапа ранжирования — 15 мс из общих 50
  • сколько объектов пришло с этапа отбора: 300–500
  • стоимость скоринга одного объекта: около 20 мкс у бустинга на одном ядре
500 кандидатов × 20 мкс ≈ 10 мс — в бюджет укладывается, 30 млн объектов — нет
На практике

Фильтр доступности вешается на этап отбора, а не на этап показа. Иначе ранжирование честно потратит бюджет на товары, которых нет на складе, и блок приедет полупустым.

Признаки: где они лежат и когда считаются

Главная причина промахов по бюджету — не модель, а поход за признаками.

Группа Где считаетсяГде лежит
Профиль пользователя (, агрегаты за 30 дней)Офлайн, раз в суткиОнлайн-хранилище 1–3 мс
Сессия (последние 20 действий)Онлайн, при событииТо же хранилище, TTL 30 мин1–3 мс
Товар (, статистика продаж)Офлайн, каждые 15 минПамять процесса, обновление фоном0 мс
Пара «пользователь — товар»На летуНе хранится≈1 мс

товара держат прямо в памяти сервиса: 8 млн товаров × 128 float16 — это около 2 ГБ, что реально для одной реплики и убирает целый сетевой поход из бюджета.

Все ключи забираются одним батч-запросом. Пятьдесят последовательных обращений по 1 мс — это 50 мс и мгновенный выход за бюджет; те же пятьдесят ключей в одном MGET — 2–3 мс.

На практике

Тот же код, что считает в обучении, должен считать их в проде — иначе появляется training/serving skew: офлайн-метрика растёт, онлайн-метрика стоит на месте, и найти причину почти невозможно.

Деградация: что показать, когда не успели

Система с жёстким SLA обязана иметь ответ на случай «не уложились».

  • Таймаут на каждый этап, а не на запрос целиком: не пришли за 8 мс — ранжируем на том, что есть, и помечаем ответ как деградированный.
  • Фолбэк-цепочка: персональные → популярное в категории → популярное вообще. Последний уровень считается заранее и лежит в памяти, поэтому не может отказать.
  • Circuit breaker на онлайн-хранилище: если оно легло, сервис не должен ждать таймаута на каждом запросе.
  • Кеш готовых блоков на 5–10 минут для незалогиненных: треть аудитории без истории получает один и тот же набор, и держать под неё полный незачем.
На практике

Доля деградированных ответов — обязательная метрика на дашборде. Без неё система «работает нормально» ровно до разбора, почему упала конверсия: кеш и фолбэки маскируют отказ, отдавая пользователю правдоподобный мусор.

Выкатка и проверка

Офлайн-метрика ранжирования не доказывает ничего, пока модель не проверена на живом трафике.

  1. Shadow-режим: новая модель считает ответ на реальном трафике, ответ не показывается. Проверяются и стабильность, а не качество.
  2. A/B на 5 % трафика с фиксацией guardrail-метрик: p99, доля фолбэков, ошибки, выручка на сессию.
  3. Раскатка до 50/50 при неухудшении guardrail; автооткат срабатывает без человека.

Стабильные группы обязательны: пользователь распределяется по хешу идентификатора и не должен прыгать между вариантами между запросами, иначе эксперимент меряет шум.

Развилки и выбор

На собеседовании ценится не сам выбор, а объяснение, почему отклонены остальные варианты.

Чем ранжировать: градиентным бустингом на CPU или нейросетью на GPU?

Бустинг на CPU для первой версии

500 кандидатов × 200 признаков укладываются в 10 мс на CPU, горизонтальное масштабирование тривиально, а стоимость реплики в разы ниже. Нейросеть окупается позже — когда упрёмся в потолок качества, а не в старте.

Что отклонено и почему
  • DLRM на GPU сразуДаёт прирост качества, но добавляет GPU в критический путь, батчинг ради утилизации карты и +10–20 мс задержки на очередь.
  • Скоринг всего каталога30 млн объектов на запрос — это секунды на любом железе. Каскад существует именно поэтому.

Где хранить эмбеддинги товаров?

В памяти каждой реплики, обновление фоном каждые 15 минут

Убирает сетевой поход из бюджета, а 2 ГБ на реплику — приемлемая цена. Требование свежести в 15 минут при этом выполняется.

Что отклонено и почему
  • Внешнее векторное хранилищеЕщё один сетевой хоп и ещё одна зависимость, которая может лечь; оправдано, когда индекс не влезает в память.
  • Пересчёт эмбеддингов на летуПрямой выход за бюджет: инференс энкодера на каждый запрос дороже всего остального пайплайна.

Кешировать ли персональные рекомендации?

Кешировать только для незалогиненных и только на 5–10 минут

Незалогиненные дают треть трафика при почти одинаковом ответе — это самый дешёвый способ снять нагрузку. Персональный кеш быстро протухает: после каждого действия пользователя ответ должен меняться.

Что отклонено и почему
  • Кешировать всё на часРекомендации перестают реагировать на действия пользователя — ровно то, ради чего строилась персонализация.
  • Не кешировать ничегоТреть трафика обслуживается полным пайплайном без единого шанса на другой ответ.

Что ломается в проде

Хвост задержки живёт своей жизнью

Среднее в 12 мс и p99 в 180 мс — обычная картина: сборка мусора, промахи кеша, ретраи. Мерить и алертить нужно по p99, а бюджет закладывать с запасом на всплески.

Расхождение признаков между обучением и продом

Офлайн агрегат «покупок за 30 дней» считается по полной таблице, а онлайн — по данным, доехавшим до хранилища. Разница в пару часов даёт систематический сдвиг, который не видно ни в одной офлайн-метрике.

Утечка через будущее

При сборке обучающей выборки легко взять признак, которого в момент показа ещё не существовало (например, итоговый рейтинг товара). Метрика на валидации взлетает, в A/B прироста нет.

Кеш маскирует деградацию

Когда часть трафика обслуживается кешем и фолбэками, отказ модели виден не в ошибках, а только в продуктовых метриках — через сутки. Долю фолбэков надо считать явно.

Обратная связь замыкается на себя

Модель учится на кликах, которые сама же и собрала: показанное получает клики, непоказанное не имеет шанса. Без доли случайного трафика для сбора непредвзятых данных выборка вырождается.

По чему видно, что система здорова

МетрикаЗначениеЗачем
Задержкаp50, p95, p99 по этапамОтдельно по каждому этапу — иначе непонятно, кто съел бюджет.
Доля деградаций< 1 %Ответы, отданные фолбэком или без части признаков.
Покрытие каталогадоля товаров, показанных хотя бы разПадение — признак схлопывания рекомендаций в популярное.
Продуктовая метрикавыручка и конверсия на сессиюРешение о раскатке принимается по ней, а не по NDCG.
Офлайн-качествоNDCG@10, Recall@100 по этапамRecall отбора ограничивает всё, что может сделать ранжирование.

Что спрашивают по этому кейсу

  1. Разложите бюджет в 50 мс по статьям и скажите, какая из них рискованнее всего.
  2. Почему нельзя обойтись одной моделью вместо каскада? На каком размере каталога граница?
  3. Что показывать пользователю, если хранилище признаков не ответило за 8 мс?
  4. Как поймать расхождение признаков между обучением и продом до выката?
  5. Модель выиграла офлайн, но проиграла в A/B. Ваши гипотезы по порядку проверки.

Проверить себя: тест «NLP», тест «Генеративный ИИ и LLM», тест «Временные ряды». Открытые задачи там же — под тестом.

Темы атласа, из которых собран кейс

Requirements and SLA

Требования и SLAиз «Системный дизайн ML-сервисов»

Как формулируют требования и бюджет задержки.

Serving Architecture

Архитектура инференсаиз «Системный дизайн ML-сервисов»

Каскад «отбор → ранжирование» как типовая схема.

Online and Offline Features

Онлайн- и офлайн-признакииз «Системный дизайн ML-сервисов»

Где считаются признаки и почему расходятся с обучением.

Feature Stores

Хранилища признаковиз «MLOps»

Онлайн- и офлайн-хранилище признаков.

Caching

Кешированиеиз «Системный дизайн ML-сервисов»

Что кешировать и на сколько.

Reliability

Отказоустойчивостьиз «Системный дизайн ML-сервисов»

Таймауты, фолбэки и circuit breaker.

Experiment Infrastructure

Инфраструктура экспериментовиз «Системный дизайн ML-сервисов»

Shadow-режим, стабильные группы и автооткат.

Semantic Search

Семантический поискиз «Обработка естественного языка»

ANN-поиск по эмбеддингам — механика этапа отбора.

NDCG

NDCGиз «Метрики»

Метрика ранжирования и её пределы.

Data leakage

Утечка данныхиз «Практика ML»

Признаки из будущего в обучающей выборке.