Recommendation system case study (Netflix/YouTube/Spotify class). Two-stage funnel: candidate generation via two-tower ANN over millions of items, then ranking via DLRM on top 1000, then re-rank for diversity and business rules. Includes cold start (popular + demo cohort + bandit explore), real-time signal updates via Kafka+Flink streaming, A/B testing with experiment assigner, drift detection and retrain pipeline. 5 scenarios and 2 ADRs (two-stage vs single model, real-time vs batch features).
Recommendation system отличается от классического CRUD тем, что “правильный ответ” не хранится в одной таблице. Система строит персональный ranked list из сигналов поведения, признаков пользователя, признаков item-ов, embeddings, бизнес-правил и экспериментов. Ошибка может выглядеть не как 500, а как падение CTR, filter bubble, stale model, плохой cold start или деградация diversity.
Целевой масштаб похож на YouTube/TikTok/Netflix class: 2B MAU, 600M DAU, 5 sessions/user/day, 20 recommendation calls per session. Это примерно 60B rec calls/day, средний RPS около 700K и peak до 2.1M. Каталог может содержать 5B videos/items, item embedding 256 floats занимает около 1 KB, а vector index с overhead может вырасти до десятков TB. Latency budget жесткий: candidate generation меньше 30ms, ranking top-1000 меньше 50ms, общий p95 меньше 200ms.
Кейс тренирует ::concept{slug="embeddings-basics"}, ::concept{slug="vector-similarity-ann"}, ::concept{slug="feature-store"} и ::concept{slug="model-serving"}. Он полезен тем, что соединяет online serving и offline training в один контур: логирование сегодняшних показов и кликов становится обучающими данными для завтрашней модели.
Думайте о recommendation system как о многоступенчатой воронке. Candidate generation быстро находит сотни или тысячи потенциально релевантных item-ов из миллиардов. Ranking model дорого, но точнее оценивает top candidates. Re-ranker накладывает diversity, freshness, safety, business constraints и exploration. Финальный список возвращается пользователю и обязательно логируется вместе с request_id, model_version, experiment_id и impression positions.
Второй mental model: features имеют разные скорости жизни. Статические признаки item-а могут обновляться часами. Популярность и trending меняются минутами. Recent clicks пользователя должны повлиять на следующий feed request за секунды. Поэтому есть offline feature store в lake/warehouse и online feature store в Redis/Aerospike/Cassandra-like KV.
Третий mental model: ML система деградирует молча. Если распределение контента изменилось из-за вирусного события, старая модель может продолжать отвечать с хорошей latency, но хуже ранжировать. Поэтому monitoring должен смотреть не только CPU и errors, но и CTR, dwell time, calibration, drift, diversity, long-term retention и guardrails.
Диаграмма делит систему на online serving и offline/training loop. Client идет через Edge Gateway в Rec Service. Rec Service orchestrates fan-out: Candidate Gen обращается к Vector DB с ANN search, Feature Store отдает user/item/context features, Ranking Model считает scores для top-1000, Re-ranker делает final slate из 20 items.
Vector DB может быть Vespa, FAISS, ScaNN, Milvus или custom ANN service. Важно, что это не обычный SQL query: для billions items exact nearest neighbor слишком дорогой, поэтому используется approximate search: HNSW, IVF, PQ или hybrid retrieval. Candidate Gen может смешивать источники: similar-to-user embedding, similar-to-recent-item, trending, subscriptions/follow graph, editorial pools и explore candidates.
Offline часть начинается с Logging -> Kafka -> Data Lake. Impression, click, watch, skip, like, share, hide, dwell time и request context пишутся как immutable events. Flink/streaming обновляет online features почти в реальном времени. Batch Training на Spark/GPU строит новые embeddings и ranker weights. Embedding Builder перезагружает vector index. Experiment/AB service решает, какая модель обслуживает пользователя.
Happy path показывает запрос GET /recs. Rec Service параллельно получает candidates и features, ANN возвращает top-1000 за десятки миллисекунд, DLRM или sequence-aware model ранжирует, re-ranker добавляет diversity and freshness, затем response возвращает 20 items. Урок: одна “большая модель на все” часто не помещается в latency, поэтому используется two-stage или multi-stage funnel.
Cold start показывает нового пользователя без истории. Online feature store возвращает miss по user embedding. Система смешивает popularity, demographic cohort, contextual signals и bandit exploration. Нельзя просто показывать глобальный топ навсегда: так пользователь не даст сигналов, а система не научится. Хороший cold start балансирует safe popular content и controlled exploration.
Feedback loop показывает click/watch event, который идет в Kafka, затем Flink обновляет recent features and user embedding delta в online store. Через 5-10 секунд следующий feed уже отражает новый интерес. Это отличает modern recommendations от nightly batch-only systems. Но real-time signal опасен: один случайный click не должен полностью сломать профиль, поэтому нужны decay, caps и smoothing.
A/B test scenario показывает experiment assigner. 5% пользователей получают новый ranker variant, все impression/click events логируются с model_version и experiment_id. Metrics pipeline считает CTR, dwell, completion, retention, latency и diversity guardrails. Ramp идет 5% -> 25% -> 50% -> 100% только если guardrails не нарушены. Урок: offline AUC недостаточно; production recs оцениваются через controlled experiments.
Drift/retrain scenario показывает CTR drop на 3% за 4 часа. Drift monitor запускает retrain на последних 7 днях, trainer обновляет model, embedding builder обновляет changed item vectors, shadow deploy сравнивает predictions, затем A/B ramp возвращает traffic. Урок: model lifecycle это часть system design, а не “потом data scientists разберутся”.
Первый trade-off: two-stage funnel против single heavy ranker. Single model на весь каталог теоретически точнее, но невозможно прогнать 5B items за 200ms. Candidate generation жертвует частью recall ради скорости, ranker улучшает precision на малом наборе. Цена: если candidate gen не достал хороший item, ranker его уже не спасет.
Второй trade-off: real-time features против batch stability. Real-time делает систему отзывчивой, но увеличивает сложность, cost и риск feedback loops. Batch-only проще и стабильнее, но плохо реагирует на свежие интересы и тренды. Часто используют гибрид: базовые embeddings обновляются batch, session/user velocity features обновляются streaming.
Третий trade-off: personalization против diversity/exploration. Чистая оптимизация CTR быстро ведет к filter bubble и однообразию. Re-ranker должен добавлять category caps, creator diversity, freshness, safety filters и exploration budget. Это может снизить immediate CTR, но улучшить long-term retention.
Четвертый trade-off: explainability против model complexity. Deep ranker с embeddings и sequence features может быть точнее, но сложнее объяснить пользователю “почему это рекомендовано”. Для некоторых доменов, например кредитов, медицины или hiring, нужны более объяснимые и регулируемые модели.
Пятый trade-off: vector index freshness против serving stability. Frequent reindexing дает свежие items, но может ломать latency и consistency между embeddings/model versions. Нужны versioned indexes, shadow build, canary и rollback.
YouTube recommendations historically используют candidate generation plus ranking, включая deep neural networks и множество источников кандидатов. TikTok известен быстрым feedback loop и сильным использованием session signals. Netflix сочетает personalization, artwork/title ranking и A/B experimentation. Spotify использует collaborative filtering, content embeddings, playlists и editorial/business constraints.
E-commerce рекомендации похожи, но добавляют stock availability, price, margin, sponsored products, seller quality и purchase intent. Social recommendations добавляют follow graph, privacy, blocks, safety и abuse constraints. News recommendations добавляют freshness, locality, source diversity и misinformation risk.
В production часто используются специализированные платформы: Feast/Hopsworks для feature store, Kafka/Flink для streaming, Spark/Ray для training data prep, Triton/TF Serving/TorchServe для model serving, Vespa/FAISS/ScaNN/Milvus для ANN, Optimizely/internal experimentation для A/B tests.
Главная ошибка: не логировать impressions. Клики без показов бесполезны для обучения, потому что неизвестно, что пользователь мог выбрать. Нужен request_id, position, candidate source, model_version, experiment_id и context. Вторая ошибка: training-serving skew, когда offline features считаются одним способом, а online другим. Это дает красивый offline metric и плохой production result.
Третья ошибка: считать CTR единственной метрикой. Можно увеличить CTR clickbait-ом и ухудшить retention, satisfaction или safety. Четвертая: не иметь cold-start strategy. Новые пользователи и новые item-ы будут получать мусор или никогда не наберут сигналов.
Пятая ошибка: синхронно вызывать слишком много сервисов в request path. Каждый network hop съедает latency budget и увеличивает tail. Шестая: обновлять модель сразу на 100% traffic без shadow/canary. Седьмая: не версионировать embeddings, features and ranker. Несовпадение user embedding v4 и item embedding v5 может резко ухудшить ranking.
Восьмая ошибка: не контролировать feedback loops. Если система продвигает только то, что уже популярно, новые creators/items не получают exposure, а популярность становится self-fulfilling prophecy.
Не нужна ML recommendation platform для маленького каталога с сотнями item-ов. Там часто достаточно rules, popularity, recency, manual curation или SQL queries. Vector DB, feature store и GPU serving окупаются только при большом каталоге, частых запросах и ценности personalization.
Не стоит использовать персональные рекомендации там, где регуляторика требует прозрачных правил и explainability, если команда не готова к audit and fairness controls. Для банковских, медицинских и образовательных high-stakes решений нужен отдельный governance layer.
Если продукт новый и данных мало, сначала лучше построить event logging, taxonomy, search, editorial playlists and popularity baselines. Сложная модель без данных будет имитировать интеллект, но не даст стабильного качества.
::concept{slug="embeddings-basics"} для понимания dense vectors и semantic similarity.::concept{slug="vector-similarity-ann"} для HNSW, IVF и approximate retrieval.::concept{slug="feature-store"} для offline/online features и борьбы с skew.::concept{slug="model-serving"} для latency, batching, canary и rollback моделей.