Real-Time Backend

Activity Feeds

Instagram открывается за 300 мс. За это время система должна показать персонализированную ленту из сотен событий от сотен подписок, отсортированных по релевантности, а не по времени. Как отобрать и отранжировать нужные события за миллисекунды?

  • **Instagram:** pre-computed inbox в Cassandra с партицированием по user_id. Глубина хранения - 1000 событий на пользователя с автоматическим TTL-удалением более старых. Гибридный fan-out с порогом ~10k followers.
  • **Facebook:** EdgeRank (затем ML-модель) ранжирует события по affinity, weight, time decay. Переход на ML поднял вовлечённость на 16% - алгоритм понял паттерны, которые люди не заложили явно.
  • **GitHub:** агрегирует события в ленте по 1-часовым окнам. Коммиты в один репозиторий за час сворачиваются в одну строку. Снижение числа строк в ленте в 10-20x для активных разработчиков.
  • **Pinterest:** динамический threshold для fan-out - граница между push/pull меняется в реальном времени в зависимости от текущей нагрузки системы, а не фиксирована на 10k.

Feed Timeline Architecture

Activity feed - это персонализированная лента событий: 'Иван лайкнул ваше фото', 'Мария начала подписываться', 'Команда закрыла задачу'. В отличие от notification fan-out (одно событие -> N уведомлений), feed работает в обратную сторону: один пользователь открывает ленту и видит агрегированные события от N источников. Это фундаментальное различие определяет всю архитектуру.

Instagram хранит activity feed в Cassandra с партицированием по user_id. Каждый пользователь имеет свой 'inbox' - список событий, предварительно записанных при action других пользователей. Глубина хранения - последние 1000 активностей на пользователя. Более старые автоматически удаляются через TTL.

Пользователь подписан на 5000 аккаунтов. При открытии ленты используется flat storage без pre-computation. Что произойдёт с производительностью?

Aggregation событий в ленте

Без агрегации лента превращается в спам: 'Иван лайкнул фото', 'Иван лайкнул фото', 'Иван лайкнул фото' - 50 строк подряд. Aggregation сворачивает однотипные события в одно: 'Иван и ещё 49 человек лайкнули ваше фото'. Это и UX-улучшение, и экономия трафика.

GitHub агрегирует события в ленте активности по окну 1 час. Коммиты в один репозиторий за час сворачиваются в 'X pushed Y commits to repo'. Это снижает количество строк в ленте в 10-20x для активных разработчиков. Алгоритм агрегации хранится на бэкенде, фронтенд получает уже свёрнутые данные.

Временное окно агрегации - критический параметр. Слишком узкое (5 мин) - пользователь видит дубли, если несколько людей лайкнули подряд. Слишком широкое (24 часа) - агрегат устаревает, актуальные события теряются в общей куче. Практика: 1-4 часа для социальных событий, 24 часа для сводных дайджестов.

50 пользователей лайкнули одно фото за 30 минут. Как правильно отобразить это в ленте владельца фото?

Ranking - хронология vs relevance

Простая хронология (reverse chronological) - честная, предсказуемая, но неэффективная. Если пользователь не заходил 3 дня, он видит кучу старых событий и пропускает важное. Ranking - ранжирование событий по relevance score - показывает сначала то, что реально важно пользователю, независимо от времени.

Facebook EdgeRank (сейчас ML-модель) использовал три фактора: affinity (близость), weight (тип события), time decay (время). После перехода на ML-ранжирование вовлечённость пользователей выросла на 16%. Instagram при переходе от хронологической к алгоритмической ленте в 2016 году: пользователи стали видеть на 50% больше постов от людей, с которыми реально взаимодействуют.

В алгоритмической ленте пользователь не видит посты от лучшего друга, зато видит вирусные посты от малознакомых людей. В чём проблема алгоритма?

Feed Fan-out стратегии

Feed fan-out решает ту же задачу, что и notification fan-out, но с другими компромиссами. При записи (push) - быстрое чтение ценой дорогой записи. При чтении (pull) - медленное чтение, но дешёвая запись. Выбор зависит от соотношения reads/writes и распределения размера аудитории.

Pinterest использует гибридный подход с интересным twist: fan-out на запись для пинов популярных досок, fan-out на чтение для нишевых авторов с малой аудиторией. Threshold определяется динамически на основе текущей нагрузки системы, а не фиксированным числом подписчиков.

Для activity feed достаточно одной стратегии fan-out - выбрать push или pull и применять для всех пользователей.

Гибридная стратегия обязательна при наличии пользователей с сильно различающимся числом подписчиков. Один threshold (обычно 10k followers) разделяет аккаунты на push и pull категории.

Обычный пользователь с 200 подписчиками и знаменитость с 10M создают принципиально разные нагрузки на систему. Единая стратегия либо убьёт запись (push для знаменитостей), либо сделает чтение медленным (pull для всех).

У пользователя 50k подписчиков. При публикации поста используется fan-out на запись. Сколько записей в БД создаётся?

Итоги

  • **Pre-computed inbox ускоряет чтение**: хранить готовый feed каждого пользователя вместо merge при запросе - классический трейдоф write amplification vs read speed, который выбирают Instagram и Facebook.
  • **Aggregation убирает шум**: 50 лайков на одно фото → одна строка 'Иван и ещё 49 лайкнули', временное окно 1-4 часа - стандарт индустрии для социальных событий.
  • **Гибридный fan-out обязателен**: push для обычных пользователей, pull для знаменитостей - единая стратегия ломается при неоднородном распределении аудитории.

Связанные темы

Activity feeds сочетают несколько паттернов из разных областей:

  • Notification Fan-out — Та же задача распределения событий по N получателям, но feed строит персонализированную ленту, а не отправляет push-уведомления
  • Cassandra / Wide-column stores — Cassandra с партицированием по user_id - стандартное хранилище для pre-computed feed благодаря O(1) чтению по partition key
  • Redis Sorted Sets — Sorted Set по score = timestamp используется для хранения feed в кеше с автоматической сортировкой и эффективным range-запросом

Вопросы для размышления

  • Пользователь удалил пост. Как эффективно удалить его из pre-computed feed всех подписчиков, у которых этот пост уже присутствует?
  • Как реализовать 'видел/не видел' для событий в ленте при гибридном fan-out, где часть событий берётся из pull-запроса в реальном времени?
  • LinkedIn показывает и хронологическую, и алгоритмическую ленту. Как хранить одни данные, но поддерживать оба режима отображения?

Связанные уроки

  • db-19-redis
Activity Feeds

0

1

Войти