Дизайн Новостной ленты соцсети типа Twitter или Facebook
Tags: systemdesign
Published: 2023-08-09 23:31:57

## Задача.
Задизайнить новостную ленту соцсети типа Facebook или Twitter, которая будет содержать посты с текстом, фото и видео, статус апдейтами от людей, на которых подписан пользователь.

## Решение.

### Уточняем требования.

#### Функциональные требования:

- Лента новостей будет создаваться на основе постов от людей, страниц и групп, на которые подписан пользователь.
- Пользователь может иметь много друзей и следить за большим количеством страниц/групп.
- Посты могут содержать изображения, видео или просто текст.
- Наш сервис должен поддерживать добавление новых постов в ленту новостей по мере их поступления для всех активных пользователей.

#### Нефункциональные требования:

- Наша система должна иметь возможность генерировать новостную ленту любого пользователя в режиме реального времени — максимальная задержка должна составлять порядка 2 секунд.

- Новый пост должен попадать в новостную ленту в течении 5 секунд при условии, что кто-то явно запросил рефреш новостной ленты.

### Оценка требуемых ресурсов

Предположим, что в среднем у пользователя 300 друзей и он подписан на 200 страниц.

**Трафик**: предположим, что ежедневно активны 300 миллионов пользователей, каждый из которых загружает свою новостную ленту в среднем пять раз в день. Это приведет к 1,5 млрд запросов новостной ленты в день или примерно 17 500 запросов в секунду.

**Хранилище**: предположим, что нам нужно хранить 500 постов в ленте каждого пользователя, которые мы хотим хранить в оперативной памяти для быстрого доступа. Давайте также предположим, что в среднем каждый пост будет иметь размер 1 КБ. Это будет означать, что нам нужно хранить примерно 500 КБ данных на каждого пользователя. Чтобы хранить все эти данные для всех активных пользователей, нам потребуется 150 ТБ памяти. Если сервер может хранить 100 ГБ, нам потребуется около 1500 машин, чтобы хранить в памяти 500 лучших сообщений для всех активных пользователей.

### High-Level Design

Нам необходимо решить две основные задачи: генерация (создание) новостной ленты и ее публикация, чтобы пользователь ее увидел.

**Создание новостной ленты**: Лента новостей для конкретного пользователя создается из постов пользователей и групп, на которые он подписан. 

Т.к. нам нужно обеспечить быстрое чтение ленты, будем ее хранить в распределенном in-memory кеше. Ключем будет UserId, а значением отсортированный список айдишников постов, которые нужно отобразить. Сам текст постов и медиа файлы в этом кеше хранить не будем. Посты будут храниться в отдельной базе, с кешем поверх этой базы для быстрого доступа. Посты могут содержать ссылки на медиафайлы. Сами медиафайлы будем хранить в отдельном blob хранилище по типу AWS S3.

Сервис, который занимается созданием новостной ленты, назовем Newsfeed Generation Service. Он работает следующим образом:
- Получаем UserId всех пользователей, на которых подписан наш пользователь. Граф связей всех пользователей будем хранить в Graph хранилище.
- По списку пользователей, получим список постов из базы Posts DB
- Полученный список постов отправим в Ranking Service, который отсортирует посты по релевантности.
- Отсортированный список постов сохраним в кеше Newsfeed Cache

Для взаимодействия с клиентом у нас будет backend (Web Servers) - это набор серверов, которые будут взаимодействовать с клиентами (mobile, web). Это может быть как REST (pull based), так и websockets, если мы хотим пушить изменения в ленте новостей сразу на клиента (push-based).


Таким образом мы создадим новостную ленту для пользователя один раз и сохраним ее к кеше.

**Каким образом мы будет апдейтить уже сгенерированную ленту и сохраненную к кеше новыми постами?**

Если наш пользователь в сети, у нас должен быть механизм ранжирования и добавления этих новых сообщений в его ленту. Мы можем периодически (скажем, каждые пять минут) выполнять вышеуказанные шаги, чтобы ранжировать и добавлять новые сообщения в его ленту. Затем мы можем нотифицировать пользователя о том, что у нас есть обновления в ленте.

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/19yb3ddbdddvn1afxbye.png)


**Публикация ленты новостей**. Когда пользователь открывает страницу с лентой новостей или делает рефреш, то запрос приходит сначала на наш бэкенд Web Servers и далее перенаправляется в Newsfeed Publishing Service. Этот сервис фетчит состояние ленты и отправляет клиенту первые 20 постов. Если он их все пролистал, то мы вернем следующие 20 и т.д. 
Т.к. наш кеш с лентой не хранит медиа файлы, то Newsfeed Publishing Service будет фетчить эту информацию для отправки клиентам из других хранилищ и баз данных.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9osc3ed0u7cvdcmxzfvb.png)

Все вместе:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93h3iy7s5q2z3nb50bsd.png)

### Стратегии публикации ленты новостей

#### Pull-base
Клиент периодически запрашивает обновления ленты новостей. Например раз с несколько минут. Минусы такого подхода: мы будем делать запросы даже тогда, когда никаких изменений в ленте нет, если изменения есть, то пользователь этого сразу не узнает. 
#### Push-base
Как только пользователь создает новый пост, мы сохраняем его в Posts DB, находим всех его друзей и подписчиков и обновляем их ленты новостей и пушим эти изменения клиенту через websocket. Тут есть минус, что если у него миллионы подписчиков (это какой-то блогер), то надо обновлять куча лент новостей сразу, что затратно. Даже если подписчики не онлайн.
#### Гибридный
Использовать оба подхода в зависимости от того, сколько подписчиков у того, кто делает пост. Для инфлюенсеров использовать pull-based или более хитрую систему на основе текущего статуса подписчика и паттерна его активности. Если подписчиков мало - то push-based.