Задача 5 из 10
Лента новостей
Спроектируйте ленту постов от подписок: пользователь открывает приложение и видит свежие записи тех, на кого подписан.
Функциональные требования
- Опубликовать пост
- Лента подписок в обратном хронологическом порядке
- Подписаться и отписаться
- Подгрузка следующей страницы ленты
Нефункциональные требования
- 10 млн активных пользователей в сутки
- Чтений на два порядка больше, чем записей
- Лента открывается за 200 мс на p99
- Средний пользователь подписан на 200 аккаунтов, у популярных — миллионы подписчиков
- Задержка появления поста в ленте до нескольких секунд допустима
Нарисуйте архитектуру
Не гонитесь за красотой: на собеседовании от схемы нужно, чтобы по ней было видно путь запроса и где лежат данные. Сначала нарисуйте сами — эталон и разбор ниже.
Блок из палитры — добавить. Тащите мышкой или пальцем. Двойной клик по названию — переименовать. «Связь» — щёлкнуть по одному блоку, потом по другому: получится стрелка.
Схема сохраняется в этом браузере — вкладку можно закрыть и вернуться позже.
1 С чего начать, если ступор
- Два подхода: разложить пост по лентам подписчиков при публикации (fanout on write) или собирать ленту при чтении (fanout on read).
- Fanout on write ломается на аккаунтах с миллионами подписчиков: одна публикация — миллион записей.
- Fanout on read ломается на пользователе с тысячей подписок: тысяча запросов на каждое открытие ленты.
- Гибрид: обычных авторов раскладываем заранее, «звёзд» подмешиваем при чтении.
- Прикиньте объём кэша: 10 млн лент × 200 записей — сколько это памяти.
2 Эталонная схема
Ленты обычных авторов раскладывает воркер, и они лежат готовыми в Redis — чтение становится одним запросом. Посты «звёзд» не разносим по миллионам лент: их подмешивает сервис ленты при чтении.
Это один из рабочих вариантов, а не единственно верный. Если у вас иначе, но вы можете объяснить почему — на собеседовании это ровно то, что нужно.
3 Разбор: что должно прозвучать
Стратегия fanout
- Fanout on write: чтение дешёвое, запись дорогая, кэш лент занимает много памяти
- Fanout on read: запись дешёвая, чтение дорогое и медленное
- Гибрид с порогом по числу подписчиков — тот ответ, которого ждут; важно назвать сам порог как настраиваемый
Оценка объёма
- 10 млн лент × 200 записей × ~50 байт ≈ 100 ГБ — это уже кластер Redis, а не один инстанс
- Держать ленту целиком не нужно: хватает первых страниц, остальное собирается по требованию
- Ленты неактивных пользователей можно не поддерживать вовсе и строить при заходе
Пагинация
- Offset ломается: пока пользователь читает, лента сдвигается и записи дублируются или теряются
- Курсор по (время, id) или по идентификатору последней увиденной записи
- Курсор должен переживать вставку новых постов сверху
Консистентность и отставание
- Лента eventually consistent — это записано в требованиях, и на это можно опираться
- Свой пост пользователь должен видеть сразу: обычно его подмешивают локально
- Что делать, если воркер fanout отстал на 10 минут: деградация в fanout on read
Горячие точки
- Публикация звезды создаёт всплеск на миллион операций — его надо размазывать
- Подписки на звёзд читаются постоянно: их посты кэшируются отдельно
- Отписка не должна требовать перестроения всей ленты
Куда копать дальше, если спросят
- Ранжирование вместо хронологии и как оно меняет всю схему
- Удаление поста: чистить миллион лент или фильтровать при чтении
- Восстановление ленты после потери кэша
- Шардирование лент и постов, ключ шардирования
Следующая задача: Бронирование мест
Задача не про масштаб, а про конкурентность: 500 человек одновременно жмут на одно место, и продать его можно ровно один раз.