Спроектируйте ленту новостей социальной сети.
- Fan-out on write или fan-out on read?
- Как построить ленту подписок для миллионов пользователей?
Главный выбор — когда собирать ленту. Fan-out on write: при публикации положить id поста в ленты всех подписчиков — чтение мгновенное. Fan-out on read: собирать при открытии из постов подписок — дорогое чтение. На практике гибрид: обычные авторы — на записи, знаменитости — на чтении.
- Лента в кэше — список id постов (Redis sorted set по времени), посты — отдельно
- Пагинация курсором, а не OFFSET
- Ранжирование — отдельный этап поверх кандидатов
А ещё Неактивным пользователям ленту не раскладывать — собрать при входе
Подробно
Требования: публикация постов, лента из постов подписок в обратном хронологическом порядке (или ранжированная), бесконечная прокрутка, лента открывается быстро (< 200 мс). Оценка: 200 млн пользователей в день, каждый открывает ленту 10 раз — 2 млрд чтений в день (~25 000/с, пик ×3); публикаций 50 млн в день (~600/с); в среднем 200 подписчиков, у знаменитостей — десятки миллионов.
Модель данных: посты (post_id, author, created, content) — шардированное хранилище по post_id или автору; граф подписок (follower → followees, followee → followers); лента — список id.
Fan-out on write (push). При публикации воркер читает подписчиков автора и добавляет post_id в их ленты (Redis sorted set с score = время, обрезанный до 500–1000 элементов). Чтение — взять верх списка и подгрузить посты пачкой (из кэша постов). Плюсы — быстрое чтение. Минусы — пост знаменитости с 50 млн подписчиков = 50 млн записей, задержка доставки и шторм нагрузки; место на ленты неактивных пользователей.
Fan-out on read (pull). При открытии ленты взять подписки, последние посты каждой и слить (k-way merge кучей). Плюсы — запись дешёвая. Минусы — чтение дорогое при сотнях подписок и частых открытиях.
Гибрид — так делают крупные сети: посты обычных авторов раскладываются при записи; посты авторов с огромной аудиторией не раскладываются, а подмешиваются при чтении (их немного у каждого пользователя, и их последние посты горячо кэшируются). Ленты раскладываются только активным пользователям; вернувшемуся после долгого перерыва ленту строят при входе.
Чтение и пагинация: курсор — score последнего показанного элемента (ZREVRANGEBYSCORE ... < cursor LIMIT 20), а не OFFSET: новые посты не сдвигают страницы и не дают дублей. Посты — пачкой из кэша по id (multi-get).
Ранжирование: из ленты берутся кандидаты (сотни), модель ранжирования оценивает и сортирует; результат кэшируется на короткое время.
Остальное: удаление и редактирование поста (ленты хранят id — удалённый пост отфильтровывается при подгрузке), приватность (проверка при показе), медиа — в объектном хранилище и CDN, счётчики лайков — отдельный сервис (агрегирование), уведомления — отдельный конвейер.
Пример
Публикация (автор с 300 подписчиками):
POST /posts → posts DB → событие в Kafka → fan-out воркер
→ для каждого активного подписчика: ZADD feed:{user} <ts> <post_id>; ZREMRANGEBYRANK feed:{user} 0 -1001
Публикация знаменитости (20 млн): только в posts + кэш «последние посты автора».
Чтение: ZREVRANGE feed:{me} + последние посты моих знаменитостей → слияние → MGET post:{id}... → ранжирование.Что хотят услышать
- Оценка нагрузки и асимметрия чтения и записи.
- Fan-out on write и on read, их плюсы и минусы.
- Гибрид для знаменитостей и неактивных пользователей.
- Хранение ленты как списка id, пагинация курсором, ранжирование.
Частые ошибки
- Делать JOIN подписок и постов в реляционной базе на каждое открытие ленты.
- Раскладывать посты знаменитостей всем подписчикам синхронно.
Проверьте себя
Один верный вариант. Подумайте, потом откройте ответ.
Показать варианты и ответ
- ✓Гибрид: fan-out on write, а для звёзд — сборка на чтении
- ✗Fan-out on write для всех, включая авторов с миллионами подписчиков
- ✗Лента всегда собирается на чтении из всех подписок
- ✗Лента хранит полные копии постов у каждого подписчика
Связанные вопросы
- Вас просят спроектировать ленту новостей. С чего вы начнёте оценку и какие вопросы зададите до выбора архитектуры? Middle
- Что делать с горячими ключами, на которые приходится непропорционально большая нагрузка? Senior
- Как оценить, насколько кэш разгружает базу, и что будет, если он упадёт? Middle
- Лента с бесконечной прокруткой использует LIMIT 20 OFFSET N. С ростом N страницы отдаются всё медленнее, а пользователи жалуются на дубли. В чём причина и что делать? Middle
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.