Проектирование LinkedIn
Глава 1. Проектирование LinkedIn
1.1. Что такое LinkedIn?
LinkedIn — это крупнейшая профессиональная социальная сеть в мире. По состоянию на 2025 год платформа насчитывает более 1,2 миллиарда зарегистрированных пользователей, из которых 310 миллионов активны ежемесячно и примерно 134,5 миллиона заходят на платформу каждый день. Ежедневно через LinkedIn отправляется более 100 миллионов сообщений.
Проектирование такой системы — классическая задача на собеседованиях по System Design, потому что LinkedIn объединяет в себе множество сложных компонентов: социальный граф (сеть связей между людьми), ленту новостей, систему обмена сообщениями, поиск, рекомендации и уведомления. Разбирая эту задачу, вы научитесь проектировать системы, которые работают под высокой нагрузкой и масштабируются на сотни миллионов пользователей.
Основные функции LinkedIn
Прежде чем проектировать систему, нужно определить, какие функции она должна поддерживать:
- Профиль пользователя — создание и редактирование профессионального профиля (опыт работы, образование, навыки).
- Граф связей (Connections) — добавление контактов, степени связи (1-я, 2-я, 3-я степень).
- Лента новостей (News Feed) — персонализированная лента с постами, статьями и активностью контактов.
- Обмен сообщениями (Messaging) — переписка в реальном времени между пользователями.
- Поиск — поиск людей, компаний, вакансий, контента.
- Уведомления — push-уведомления, email, in-app уведомления о событиях.
- Рекомендации — «Люди, которых вы можете знать», рекомендации вакансий и контента.
1.2. Оценка нагрузки (Capacity Estimation)
Перед тем как приступить к проектированию, нужно понять масштаб системы. Этот этап называется capacity estimation (оценка ёмкости) — мы прикидываем, сколько данных система будет хранить и сколько запросов обрабатывать.
Исходные данные
| Метрика | Значение |
|---|---|
| Зарегистрированных пользователей | 1,2 млрд |
| Ежемесячно активных (MAU) | 310 млн |
| Ежедневно активных (DAU) | 134,5 млн |
| Сообщений в день | 100 млн |
| Среднее время сессии | ~14 минут |
MAU (Monthly Active Users) — количество уникальных пользователей, которые заходили на платформу хотя бы раз в месяц. DAU (Daily Active Users) — количество уникальных пользователей, которые заходили на платформу хотя бы раз в день.
Расчёт нагрузки на чтение и запись
Профили:
- Средний размер профиля: ~10 КБ (текстовые данные: имя, опыт, навыки).
- Общий объём профилей: 1,2 млрд × 10 КБ = ~12 ТБ только текстовых данных.
- Фотографии профилей: если у 50% пользователей есть фото (~200 КБ каждое), это 600 млн × 200 КБ = ~120 ТБ.
Лента новостей:
- Если каждый активный пользователь открывает ленту 3-5 раз в день, это ~134,5 млн × 4 = ~538 млн запросов ленты в день, или примерно 6 200 запросов в секунду (QPS).
- В пиковые часы нагрузка может быть в 3-5 раз выше: ~20 000-30 000 QPS.
QPS (Queries Per Second) — количество запросов в секунду, которое система должна обрабатывать. Это ключевая метрика при проектировании высоконагруженных систем.
Сообщения:
- 100 млн сообщений в день = ~1 150 сообщений в секунду в среднем.
- Среднее сообщение: ~500 байт → 100 млн × 500 байт = ~50 ГБ новых данных в день.
Поиск:
- Предположим, 30% DAU выполняют хотя бы один поиск: 134,5 млн × 0,3 = ~40 млн поисковых запросов в день, или ~460 QPS (в пике — до 2 000 QPS).
Пропускная способность (Throughput)
Внутренняя инфраструктура LinkedIn обрабатывает колоссальные объёмы данных. Для обработки потоков событий LinkedIn использует Apache Kafka (систему распределённой обработки сообщений, которую LinkedIn сам и создал). Через Kafka проходит более 1 триллиона сообщений в день с пиковой скоростью 4,5 миллиона сообщений в секунду. Важно: здесь «сообщения» — это не переписка пользователей, а внутренние события системы (обновления профилей, клики, просмотры и т. д.).
1.3. Высокоуровневая архитектура (High-Level Design)
От монолита к микросервисам
LinkedIn начинал в 2003 году как монолитное приложение — единая программа, которая отвечала за всё: профили, связи, поиск, сообщения. Рядом стояла одна база данных.
Монолит — это архитектурный подход, при котором всё приложение представляет собой единый, неделимый блок кода. Все функции (авторизация, профили, лента, сообщения) живут в одном проекте, компилируются и деплоятся вместе. Это удобно на старте, но с ростом проекта монолит становится тяжёлым: любое изменение затрагивает всю систему, а масштабировать отдельные части невозможно.
По мере роста (от 2 700 пользователей до сотен миллионов) LinkedIn прошёл через несколько этапов эволюции:
2003: Монолит + одна БД
↓
2004-2008: Выделение сервисов (граф, поиск, профили)
↓
2008-2012: Переход на микросервисы + собственные технологии (Kafka, Voldemort, Espresso)
↓
2015+: Более 750 микросервисов + Super Blocks
↓
2020+: GraphQL, LIquid, AI-driven рекомендации
Микросервисная архитектура
Сейчас LinkedIn построен на микросервисной архитектуре — система разбита на сотни независимых сервисов, каждый из которых отвечает за свою функцию.
Микросервис — это небольшое, автономное приложение, которое выполняет одну конкретную задачу (например, «сервис профилей» или «сервис сообщений»). Микросервисы общаются друг с другом через сеть (обычно по HTTP/REST или RPC), их можно разрабатывать, деплоить и масштабировать независимо друг от друга.
┌─────────────────────┐
│ Load Balancer │
│ (Распределитель │
│ нагрузки) │
└─────────┬───────────┘
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ API │ │ API │ │ API │
│ Gateway 1 │ │ Gateway 2 │ │ Gateway N │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└───────┬───────┴───────┬───────┘
│ │
┌────────────┼───────────────┼────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Profile │ │ Graph │ │ Feed │ │Messaging │
│ Service │ │ Service │ │ Service │ │ Service │
└────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │ │
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Espresso │ │ LIquid │ │ Feed │ │ Message │
│ (DB) │ │ (GraphDB)│ │ DB │ │ DB │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
Разберём каждый компонент:
-
Load Balancer (балансировщик нагрузки) — принимает все входящие запросы от пользователей и равномерно распределяет их между серверами. Если один сервер перегружен или вышел из строя, балансировщик направит трафик на другие.
-
API Gateway — «входные ворота» системы. Он принимает запрос от клиента (мобильного приложения или веб-сайта), проверяет авторизацию, определяет, какому микросервису этот запрос адресован, и перенаправляет его. LinkedIn использует Rest.li — собственный фреймворк для построения RESTful API.
-
Микросервисы (Profile Service, Graph Service и т. д.) — каждый сервис отвечает за свою область. Они общаются друг с другом по сети.
-
Базы данных — у каждого сервиса может быть своя база данных, оптимизированная под его задачи. Это называется database per service — паттерн, при котором каждый микросервис владеет своими данными и не даёт другим сервисам напрямую обращаться к его базе.
Концепция Super Blocks
Когда у вас сотни микросервисов, один пользовательский запрос может вызвать цепочку из десятков вызовов между сервисами. Это называется fanout (веерный вызов). Например, для отображения главной страницы нужно получить данные профиля, ленту новостей, уведомления, рекомендации — и каждый из этих сервисов может вызывать ещё несколько.
LinkedIn решил эту проблему, введя концепцию Super Blocks — группировок backend-сервисов, доступных через единую точку входа (API). Super Block объединяет связанные сервисы и выставляет наружу один интерфейс, скрывая внутреннюю сложность.
Без Super Blocks: С Super Blocks:
Client Client
│ │
├──→ Service A ├──→ Super Block 1 ──→ A, B, C
├──→ Service B │
├──→ Service C └──→ Super Block 2 ──→ D, E
├──→ Service D
└──→ Service E
1.4. Хранилище данных (Data Storage)
LinkedIn использует несколько типов баз данных, каждая из которых оптимизирована для конкретных задач. Такой подход называется polyglot persistence — использование разных хранилищ для разных типов данных.
Espresso — основное хранилище
Espresso — это распределённая документо-ориентированная NoSQL база данных, разработанная в LinkedIn. Она хранит данные в виде JSON-документов и поддерживает иерархическую модель: database → table → collection → document.
NoSQL (Not Only SQL) — класс баз данных, которые не используют классические реляционные таблицы. Они проектируются для гибкости и масштабируемости. Документо-ориентированные БД (как Espresso или MongoDB) хранят данные в виде документов (обычно JSON), а не строк в таблице.
Espresso используется для хранения:
- Профилей пользователей
- Сообщений (InMail)
- Данных главной страницы
- Данных мобильных приложений
На пике Espresso обслуживает миллионы записей в секунду и является source of truth (единственным источником правды) для сотен терабайт данных.
Пример документа профиля в Espresso:
{ "memberId": "123456789", "firstName": "Анна", "lastName": "Иванова", "headline": "Senior Software Engineer at Google", "location": "Москва, Россия", "experience": [ { "company": "Google", "title": "Senior Software Engineer", "startDate": "2021-03", "endDate": null, "description": "Разработка распределённых систем" }, { "company": "Яндекс", "title": "Software Engineer", "startDate": "2018-06", "endDate": "2021-02" } ], "skills": ["Java", "Distributed Systems", "Kafka", "System Design"], "connections": 1247 }
LIquid — графовая база данных
Социальная сеть по своей природе — это граф, где узлы (nodes) — это пользователи, компании, навыки, а рёбра (edges) — связи между ними. LinkedIn называет это Economic Graph — цифровое представление мировой экономики, объединяющее:
- Более 1,2 млрд пользователей
- Более 36 000 навыков
- 50+ миллионов компаний
- Миллионы вакансий
- 90 000 учебных заведений
Для работы с этим графом LinkedIn разработал LIquid — собственную графовую базу данных, которая:
- Хранит весь граф в оперативной памяти (in-memory) для быстрого доступа
- Обеспечивает 99,99% доступности
- Поддерживает обход рёбер графа за константное время O(1)
- Использует декларативный язык запросов на основе Datalog
In-memory база данных хранит данные в оперативной памяти (RAM), а не на диске. Это на порядки быстрее, потому что доступ к RAM измеряется в наносекундах, а к диску — в миллисекундах. Минус: RAM дороже и теряет данные при отключении питания, поэтому нужна репликация и персистентность на диск.
Apache Kafka — потоковая платформа
Kafka — это распределённая платформа потоковой обработки данных, изначально созданная в LinkedIn. Kafka работает по модели publish-subscribe (издатель-подписчик):
- Producer (издатель) публикует события в topic (тему).
- Consumer (подписчик) читает события из темы.
- Сообщения хранятся на диске и реплицируются, поэтому ничего не теряется.
В LinkedIn Kafka используется как «центральная нервная система» — через неё проходят все события: обновления профилей, новые связи, просмотры, клики, лайки. Эти данные затем попадают в аналитику (Hadoop), поисковый индекс, ленту новостей и другие системы.
┌──────────┐ ┌─────────────────────────┐ ┌──────────────┐
│ Profile │ │ │ │ Search Index │
│ Service ├───►│ ├───►│ Updater │
└──────────┘ │ │ └──────────────┘
│ Apache Kafka │
┌──────────┐ │ │ ┌──────────────┐
│ Feed │ │ (Брокер сообщений, │ │ Hadoop │
│ Service ├───►│ распределённая ├───►│ Analytics │
└──────────┘ │ очередь событий) │ └──────────────┘
│ │
┌──────────┐ │ │ ┌──────────────┐
│ Connect │ │ │ │ Notification │
│ Service ├───►│ ├───►│ Service │
└──────────┘ └─────────────────────────┘ └──────────────┘
Сводная таблица хранилищ
| Хранилище | Тип | Для чего используется |
|---|---|---|
| Espresso | Документо-ориентированная NoSQL | Профили, сообщения, основные данные |
| LIquid | Графовая БД (in-memory) | Граф связей, степени соединения |
| Kafka | Распределённая очередь сообщений | Потоковая обработка всех событий |
| Venice | Производные данные (read-only) | Предвычисленные результаты, ML-фичи |
| MySQL/Oracle | Реляционные БД | Некоторые legacy-данные |
| Couchbase/Memcached | Кэш | Кэширование горячих данных |
1.5. Граф связей (Graph Service)
Граф связей — это сердце LinkedIn. Он отвечает на вопросы: «Связаны ли эти два человека?», «Какова степень их связи?», «Кого я могу знать?»
Степени связи
LinkedIn использует три степени связи:
- 1-я степень — ваш прямой контакт. Вы отправили запрос, и человек его принял (или наоборот).
- 2-я степень — контакт вашего контакта. Вы не связаны напрямую, но у вас есть общий знакомый.
- 3-я степень — контакт контакта вашего контакта. Через одного «посредника» от второй степени.
Вы ←──1-я──→ Алексей ←──1-я──→ Мария ←──1-я──→ Олег
Вы ←──────2-я──────→ Мария
Вы ←────────────3-я────────────→ Олег
Как определяется степень связи?
Наивный подход — использовать BFS (Breadth-First Search), поиск в ширину по графу. Начинаем с текущего пользователя, обходим всех его друзей (1-я степень), потом друзей друзей (2-я степень) и т. д.
BFS (Breadth-First Search, поиск в ширину) — алгоритм обхода графа, который сначала посещает все вершины на расстоянии 1 от стартовой, потом на расстоянии 2, и так далее. Он гарантированно находит кратчайший путь в невзвешенном графе.
Проблема: у среднего пользователя LinkedIn ~500 контактов. BFS до 2-й степени означает проверку 500 × 500 = 250 000 узлов. До 3-й степени: 500³ = 125 миллионов. При 134,5 миллионах активных пользователей в день это невозможно считать в реальном времени «в лоб».
Как LinkedIn это решает: Network Cache Service (NCS)
Распределённый граф LinkedIn состоит из двух компонентов:
- GraphDB — партиционированная (разделённая на части) и реплицированная (скопированная для надёжности) графовая база данных.
- Network Cache Service (NCS) — распределённый кэш, который хранит множество контактов 2-й степени каждого пользователя.
Когда пользователь заходит на LinkedIn, NCS строит в реальном времени его множество контактов 2-й степени. После этого любой запрос на определение степени связи сводится к операции пересечения множеств, а не к обходу графа.
Кэш (cache) — промежуточное хранилище, которое сохраняет часто запрашиваемые или дорогие для вычисления данные в быстром хранилище (обычно в RAM), чтобы не вычислять их заново при каждом запросе.
Пример:
- У Анны контакты 1-й степени: {Борис, Вера, Глеб}
- У Бориса контакты 1-й степени: {Анна, Дмитрий, Елена}
- У Веры контакты 1-й степени: {Анна, Елена, Фёдор}
- У Глеба контакты 1-й степени: {Анна, Георгий}
NCS строит для Анны множество 2-й степени: {Дмитрий, Елена, Фёдор, Георгий}
Теперь, если нужно определить степень связи Анны с Елен ой:
- Елена ∈ множество 1-й степени? Нет.
- Елена ∈ множество 2-й степени? Да → связь 2-й степени.
Это O(1) lookup вместо BFS!
Оптимизации
Алгоритм Set Cover. Контакты 1-й степени пользователя могут быть разбросаны по разным партициям (серверам). Чтобы минимизировать количество сетевых вызовов при построении кэша 2-й степени, LinkedIn использует жадный алгоритм покрытия множеств (greedy set cover) — он находит минимальное количество партиций, которые нужно опросить, чтобы получить данные обо всех контактах 1-й степени.
Дельта-сжатие. Множество контактов 2-й степени может содержать сотни тысяч ID. Для сжатия используется дельта-кодирование: вместо хранения полных значений [100, 105, 108, 200] хранятся разности [100, 5, 3, 92]. Разности обычно маленькие числа, которые хорошо сжимаются.
Результат этих оптимизаций: время создания кэша 2-й степени сократилось на 38% (по 99-му перцентилю), а латентность запросов расстояния в графе — на 25%.
Перцентиль (percentile) — статистическая мера. 99-й перцентиль (p99) означает: 99% запросов выполняются быстрее этого значения. Если p99 = 50ms, значит, только 1% запросов работает дольше 50ms. В System Design мы часто смотрим на p99, а не на среднее, потому что среднее может скрывать «хвост» медленных запросов.
1.6. Лента новостей (News Feed)
Лента новостей — это то, что пользователь видит на главной странице: посты, статьи, обновления контактов, рекомендованный контент.
Два подхода к построению ленты
В System Design есть два классических подхода к формированию ленты:
1. Pull-модель (Fan-out on read)
Когда пользователь открывает ленту, система в реальном времени собирает последние посты от всех его контактов, ранжирует их и отдаёт.
Пользователь открывает ленту
│
▼
Получить список контактов (из Graph Service)
│
▼
Для каждого контакта: получить последние посты
│
▼
Объединить, отранжировать, вернуть пользователю
✅ Плюсы: данные всегда свежие, нет лишних вычислений для неактивных пользователей. ❌ Минусы: медленно для пользователей с большим количеством контактов; высокая нагрузка при чтении.
2. Push-модель (Fan-out on write)
Когда пользователь публикует пост, система сразу записывает его в «персональную ленту» каждого его контакта.
Пользователь публикует пост
│
▼
Получить список контактов
│
▼
Записать пост в ленту каждого контакта
│
▼
Когда контакт открывает ленту — данные уже готовы
✅ Плюсы: чтение ленты мгновенное — данные предварительно вычислены. ❌ Минусы: дорого для популярных пользователей (у CEO компании могут быть миллионы подписчиков); много лишней работы, если контакт неактивен.
3. Гибридный подход (используется LinkedIn)
LinkedIn комбинирует оба подхода:
- Для обычных пользователей используется push-модель — их посты «раскладываются» в ленты контактов.
- Для пользователей с очень большим количеством подписчиков (influencers) используется pull-модель — их посты подтягиваются при запросе.
Архитектура Feed Service в LinkedIn
LinkedIn использует систему FollowFeed с двухэтапным ранжированием:
Запрос ленты от пользователя
│
▼
┌──────────────────────────┐
│ First Pass Ranker (FPR) │
│ Быстрая фильтрация │
│ из тысяч кандидатов │
│ (лёгкие ML-модели) │
└────────────┬─────────────┘
│
~500 постов
│
▼
┌──────────────────────────┐
│ Second Pass Ranker (SPR) │
│ Точное ранжирование │
│ (тяжёлые ML-модели) │
└────────────┬─────────────┘
│
~50 постов
│
▼
Отображение пользователю
Этап 1: First Pass Ranker (FPR) — «грубое сито». Из десятков тысяч потенциальных постов отбираются несколько сотен с помощью лёгких, быстрых ML-моделей. Критерии: свежесть поста, тип контента, сила связи с автором.
Этап 2: Second Pass Ranker (SPR) — «тонкая настройка». Из нескольких сотен постов выбираются десятки лучших с помощью более сложных ML-моделей, которые учитывают: вероятность клика, вероятность лайка/комментария, разнообразие контента, коммерческие цели (реклама).
ML-модель (Machine Learning model) — математическая модель, обученная на данных, которая может делать предсказания. Например, модель может предсказать вероятность того, что конкретный пользователь кликнет на конкретный пост, анализируя историю поведения пользователя и характеристики поста.
Хранение ленты
FollowFeed использует Timeline Database — специализированное хранилище, оптимизированное для быстрого извлечения недавних активностей. Дополнительно используется Activity Graph, который хранит связи между объектами и связанными с ними активностями.
1.7. Система обмена сообщениями (Messaging)
Требования
- Доставка в реальном времени — сообщение должно появиться у получателя за миллисекунды.
- Надёжность — ни одно сообщение не должно потеряться.
- Масштабируемость — 100 млн сообщений в день.
- Поддержка rich-контента — текст, изображения, видео, ссылки, вложения.
- Индикаторы — набор текста (typing indicator), прочтение (seen receipt), статус онлайн.
Архитектура
LinkedIn использует двухканальную архитектуру получения данных:
┌──────────────────────────────────────────┐
│ Клиент │
│ (мобильное приложение / веб-браузер) │
│ │
│ ┌───────────────┐ ┌────────────────┐ │
│ │ GraphQL API │ │ Real-Time │ │
│ │ (запрос │ │ Stream │ │
│ │ по требованию│ │ (push в │ │
│ │ ) │ │ реальном │ │
│ │ │ │ времени) │ │
│ └───────┬───────┘ └───────┬────────┘ │
│ │ │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Data Store │ │
│ │ (локальный кэш) │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Reactive UI │ │
│ └─────────────────┘ │
└──────────────────────────────────────────┘
-
GraphQL API — используется для первоначальной загрузки и сложных запросов (например, «покажи последние 20 сообщений в этой переписке»).
-
Real-Time Stream — постоянное соединение, через которое сервер мгновенно «проталкивает» (push) новые сообщения клиенту. LinkedIn использует собственную real-time платформу на основе модели publish-subscribe.
Publish-subscribe (pub/sub) — паттерн обмена сообщениями, при котором отправители (publishers) не отправляют сообщения конкретным получателям, а публикуют их в «канал» (topic). Получатели (subscribers) подписываются на интересующие каналы и получают все сообщения из них. Это позволяет отделить отправителя от получателя.
- Data Store — локальный кэш на клиенте. Все данные (полученные и через API, и через stream) попадают сюда. UI реактивно обновляется при изменении данных в хранилище.
Гарантия доставки
Real-time stream — это быстрый, но не 100% надёжный канал. В редких случаях сообщения могут потеряться (обрыв соединения, сетевой сбой). Для обнаружения пропусков LinkedIn использует указатели на предыдущее сообщение: каждое сообщение содержит ID предыдущего. Если клиент получает сообщение и видит, что его указатель не совпадает с последним известным — значит, что-то пропущено, и клиент запрашивает недостающие сообщения через GraphQL API.
Сообщение 1 (prev: null)
↓
Сообщение 2 (prev: 1)
↓
Сообщение 3 (prev: 2) ← клиент получил
↓
Сообщение 4 (prev: 3) ← потеряно!
↓
Сообщение 5 (prev: 4) ← клиент получил, но prev=4 неизвестен
→ запрашиваем сообщение 4 через API
Система присутствия (Presence)
Зелёный индикатор «онлайн» рядом с именем пользователя — это Real-Time Presence Platform. Она отслеживает, какие пользователи сейчас активны, и транслирует эту информацию их контактам.
1.8. Поиск (Search)
Архитектура Galene
Для поиска LinkedIn разработал собственную систему Galene, построенную поверх Apache Lucene (открытой библиотеки полнотекстового поиска).
Инвертированный индекс (inverted index) — основная структура данных в поисковых системах. Обычный индекс отображает «документ → слова в нём», а инвертированный — наоборот: «слово → список документов, где оно встречается». Это позволяет мгновенно находить все документы по ключевому слову.
Пример инвертированного индекса:
Слово │ Документы (ID профилей)
──────────────────┼─────────────────────────
"java" │ [101, 203, 455, 892, ...]
"python" │ [101, 330, 455, 671, ...]
"senior engineer" │ [203, 455, 1024, ...]
"москва" │ [101, 330, 892, ...]
Запрос «senior engineer java москва» → пересечение списков → {455, ...}.
Компоненты индекса Galene
┌─────────────────────────────────────────┐
│ Galene Index │
│ │
│ ┌─────────────────────────────┐ │
│ │ Base Index │ │
│ │ (Периодически генерируется │ │
│ │ через Hadoop, хранится │ │
│ │ на диске, immutable) │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Live Index │ │
│ │ (In-memory, обновляется │ │
│ │ в реальном времени при │ │
│ │ изменении данных) │ │
│ └─────────────────────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Snapshot Index │ │
│ │ (Периодический снимок │ │
│ │ Live Index на диск) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────────┘
- Base Index — полный индекс, который периодически перестраивается в Hadoop. Это неизменяемый (immutable) файл на диске в формате Lucene.
- Live Index — инвертированный индекс в памяти, который обновляется в реальном времени при изменении профилей. Ключевая особенность Galene: обновления происходят на уровне отдельных полей, а не целого документа.
- Snapshot Index — периодический снимок Live Index на диск, чтобы не потерять инкрементальные обновления при перезапуске.
При поиске запрос выполняется по всем трём индексам одновременно, и результаты объединяются.
Ранжирование результатов поиска
Поиск — это не только нахождение подходящих документов, но и их ранжирование (упорядочивание по релевантности). LinkedIn учитывает:
- Текстовую релевантность — насколько запрос совпадает с данными профиля.
- Социальную близость — степень связи с искомым человеком (контакты 1-й степени выше, чем 3-й).
- Популярность — количество связей, просмотров профиля.
- Персонализацию — отрасль, география, общие навыки с ищущим пользователем.
Hosted-Search
Со временем LinkedIn перешёл к модели Hosted-Search — поиск как облачный сервис внутри компании. Любая команда может создать свой поисковый индекс (для вакансий, компаний, постов, навыков), а инфраструктура поиска предоставляется как платформа.
1.9. Система уведомлений (Notifications)
Проблема: notification fatigue
Если отправлять пользователю уведомление о каждом событии (кто-то лайкнул пост, кто-то обновил профиль, новая вакансия), пользователь утонет в уведомлениях и начнёт их игнорировать или отключит. Это называется notification fatigue — усталость от уведомлений.
Air Traffic Controller (ATC)
LinkedIn разработал систему Air Traffic Controller (ATC), которая решает:
- Какое уведомление отправить
- Когда его отправить (с учётом часового пояса и привычного времени активности пользователя)
- Куда его отправить (push-уведомление, email, in-app или их комбинация)
ATC анализирует:
- Историю взаимодействия пользователя с уведомлениями
- Время суток и часовой пояс
- Тип события и его важность
- Текущую активность пользователя (если он онлайн — in-app, если нет — push)
Результат внедрения ATC: жалобы пользователей на уведомления сократились вдвое, а вовлечённость выросла на двузначное число процентов.
Concourse — рекомендации контента
Concourse — это сервис, который решает, какой контент рекомендовать пользователю через уведомления. Он не отправляет уведомления сам, а определяет, какое уведомление будет наиболее релевантным для конкретного получателя.
Многоканальная маршрутизация
Событие (новый лайк, сообщение, вакансия)
│
▼
┌───────────────────┐
│ Air Traffic │
│ Controller │
│ (решает что, │
│ когда, куда) │
└────────┬──────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Push │ │ Email │ │ In-App │
│ (APNS/ │ │ │ │ (badge, │
│ FCM) │ │ │ │ popup) │
└──────────┘ └──────────┘ └──────────┘
APNS (Apple Push Notification Service) и FCM (Firebase Cloud Messaging) — сервисы Apple и Google для отправки push-уведомлений на мобильные устройства.
Если пользователь не прочитал in-app уведомление в течение определённого времени, система «эскалирует» его — отправляет email. Это называется smart routing — умная маршрутизация уведомлений.
1.10. Рекомендации «Люди, которых вы можете знать» (PYMK)
Как это работает
PYMK (People You May Know) — одна из самых важных функций LinkedIn для роста сети. Система рекомендует пользователей, которых вы, вероятно, знаете, опираясь на:
- Общие контакты — если у вас 15 общих знакомых с кем-то, вы, скорее всего, знакомы.
- Общие компании — вы работали в одной компании в одно и то же время.
- Общее образование — вы учились в одном вузе.
- Общие навыки и интересы.
- Географическая близость.
- Паттерны взаимодействия — вы часто просматриваете профили людей из определённой компании.
Edge-Building System
LinkedIn в 2025 году перестроил свою систему построения рёбер (edge-building system), которая отвечает за рекомендации связей между пользователями и контентом. Новая архитектура использует DAG (Directed Acyclic Graph — направленный ациклический граф) для оркестрации рабочих процессов.
DAG (Directed Acyclic Graph) — граф, в котором рёбра имеют направление и нет циклов. В контексте обработки данных DAG описывает порядок выполнения задач: задача B может начаться только после завершения задачи A, но задачи C и D могут выполняться параллельно.
Система поддерживает четыре режима инференса (применения ML-моделей):
- Offline — предвычисление рекомендаций в пакетном режиме (например, ночью).
- Nearline — вычисление с небольшой задержкой при возникновении события (например, при добавлении нового контакта — пересчёт рекомендаций).
- Online — вычисление в реальном времени при запросе пользователя.
- Remote — вызов внешних ML-сервисов.
1.11. Масштабирование и отказоустойчивость
Горизонтальное масштабирование
LinkedIn масштабируется горизонтально — путём добавления новых серверов, а не увеличения мощности существующих.
Горизонтальное масштабирование (horizontal scaling, scale out) — добавление новых машин в кластер. Например, если один сервер обрабатывает 1 000 запросов в секунду, добавляем второй — и получаем 2 000.
Вертикальное масштабирование (vertical scaling, scale up) — увеличение мощности существующей машины (больше CPU, RAM, дисков). Проще, но имеет физический предел.
Партиционирование (Sharding)
Данные разбиваются на шарды (partitions) — части, каждая из которых хранится на отдельном сервере.
Шардинг (sharding) — разделение данных на части по определённому ключу. Например, пользователи с ID 1-1 000 000 на сервере 1, 1 000 001-2 000 000 на сервере 2 и т. д. Это позволяет распределить нагрузку и хранить больше данных, чем помещается на одной машине.
Примеры партиционирования в LinkedIn:
- Espresso: данные профилей партиционированы по
memberId. - GraphDB: граф связей партиционирован по
memberId, рёбра хранятся с узлом-владельцем. - Kafka: события распределяются по топикам и партициям.
Репликация
Каждый шард реплицируется (копируется) на несколько серверов:
┌──────────────┐
│ Primary │ ← запись
│ Replica │
└──────┬───────┘
│ репликация
┌────────┼────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Secondary │ │ Secondary │ ← чтение
│ Replica 1 │ │ Replica 2 │
└──────────────┘ └──────────────┘
Репликация — создание копий данных на нескольких серверах. Это даёт: (1) отказоустойчивость — если один сервер упадёт, данные не потеряются; (2) масштабирование чтения — запросы на чтение можно распределять между репликами.
Кэширование
LinkedIn использует многоуровневое кэширование:
- CDN (Content Delivery Network) — для статического контента (изображения, CSS, JS). CDN — это сеть серверов по всему миру, которые хранят копии контента ближе к пользователю.
- Application-level cache — кэш на уровне приложения (Memcached, Couchbase).
- NCS — специализированный кэш для графа связей (описан выше).
- Database-level cache — кэш на уровне базы данных.
Важный урок из опыта LinkedIn: они убрали многие промежуточные кэши (mid-tier caches), потому что те хранили производные данные из нескольких доменов, и инвалидация кэша (определение, когда данные в кэше устарели) стала слишком сложной.
Инвалидация кэша — процесс определения, когда данные в кэше стали неактуальными и их нужно обновить. Это одна из самых сложных проблем в Computer Science. Фил Карлтон (Phil Karlton) говорил: «В Computer Science есть только две сложные вещи: инвалидация кэша и именование переменных».
Обработка отказов
LinkedIn проектирует системы с расчётом на отказы (design for failure):
- Circuit Breaker (предохранитель) — если сервис не отвечает, вызовы к нему прекращаются на определённое время, чтобы дать ему восстановиться и не перегрузить систему каскадными отказами.
- Graceful Degradation (плавная деградация) — если часть системы недоступна, остальные продолжают работать. Например, если сервис рекомендаций упал, лента всё равно показывается — просто без рекомендаций.
- Rate Limiting (ограничение частоты запросов) — защита от перегрузки: каждый клиент может делать не более N запросов в секунду.
1.12. GraphQL в LinkedIn
LinkedIn перешёл на GraphQL — язык запросов для API, который позволяет клиенту запрашивать ровно те данные, которые ему нужны.
GraphQL — альтернатива REST API. В REST каждый endpoint возвращает фиксированный набор данных. В GraphQL клиент описывает, какие именно поля ему нужны, и получает ровно их — ни больше, ни меньше. Это решает проблемы over-fetching (получение лишних данных) и under-fetching (нехватки данных, требующей дополнительных запросов).
Особенности реализации в LinkedIn:
- Автогенерация типов — схема GraphQL полностью генерируется автоматически из описаний сервисов.
- Распределённое выполнение — эндпоинт выполнения запросов распределён и доступен на каждом фронтенд-микросервисе (а не на едином сервере).
- Предрегистрация запросов — на продакшене выполняются только заранее зарегистрированные запросы. Каждый запрос имеет уникальный идентификатор, сгенерированный на этапе сборки, и регистрируется в центральном Query Registry Service.
┌────────────────┐ ┌────────────────────┐ ┌─────────────┐
│ Client Code │────►│ Build Pipeline │────►│ Query │
│ (GraphQL │ │ (генерирует ID │ │ Registry │
│ запрос) │ │ запроса) │ │ Service │
└────────────────┘ └────────────────────┘ └──────┬──────┘
│
▼
Продакшен сервер
принимает только
запросы с известным ID
Это даёт: безопасность (нельзя выполнить произвольный запрос), предсказуемость нагрузки и возможность оптимизации.
1.13. Итоговая архитектура
Соберём всё вместе в единую картину:
┌─────────────────────────────────────────────────────────────────────┐
│ Клиенты │
│ Web (React) iOS Android │
└──────────────────────────┬──────────────────────────────────────────┘
│
┌──────▼──────┐
│ CDN │ (статика, изображения)
└──────┬──────┘
│
┌──────▼──────┐
│ Load │
│ Balancer │
└──────┬──────┘
│
┌──────▼──────┐
│ API │ (Rest.li / GraphQL)
│ Gateway │
└──────┬──────┘
│
┌──────────┬───────────┼───────────┬──────────┬──────────┐
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐┌────────┐┌──────────┐┌─────────┐┌────────┐┌──────────┐
│Profile ││ Graph ││ Feed ││Messaging││ Search ││Notifica- │
│Service ││Service ││ Service ││ Service ││Service ││tion Svc │
└───┬────┘└───┬────┘└────┬─────┘└────┬────┘└───┬────┘└────┬─────┘
│ │ │ │ │ │
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
┌────────┐┌────────┐┌──────────┐┌─────────┐┌────────┐┌──────────┐
│Espresso││LIquid ││Timeline ││Espresso ││ Galene ││ ATC + │
│ (DB) ││(Graph) ││ DB ││ (DB) ││(Lucene)││Concourse │
└────────┘└────┬───┘└──────────┘└─────────┘└────────┘└──────────┘
│
▼
NCS (кэш
2-й степени)
┌──────────────────────────────────────────┐
│ Apache Kafka │
│ (связывает все сервисы, потоки событий) │
└──────────────────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────┐ ┌─────────┐ ┌─────────┐
│ Hadoop │ │ Venice │ │ ML │
│(анали- │ │(derived │ │Pipeline │
│ тика) │ │ data) │ │ │
└────────┘ └─────────┘ └─────────┘
1.14. Ключевые уроки и принципы
Подведём итоги — какие принципы проектирования мы можем извлечь из архитектуры LinkedIn:
1. Начинай просто, усложняй по необходимости
LinkedIn начинал с монолита и одной базы данных. Микросервисы, Kafka, графовые БД — всё это появилось, когда существующая архитектура перестала справляться. Не нужно строить сложную систему на старте.
2. Polyglot persistence — разные данные, разные хранилища
Не существует одной базы данных, которая идеально подходит для всего. Профили — в документо-ориентированной БД, граф — в графовой, события — в Kafka, кэш — в Memcached.
3. Кэшируй с умом
Кэширование ускоряет систему, но создаёт проблему инвалидации. LinkedIn убрал промежуточные кэши, которые хранили данные из нескольких доменов, потому что поддерживать их консистентность стало невозможно. Кэшируйте данные одного домена.
4. Предвычисляй, где возможно
Вместо обхода графа в реальном времени LinkedIn предвычисляет и кэширует множества контактов 2-й степени. Вместо ранжирования всех постов при каждом запросе — использует двухэтапное ранжирование с предварительной фильтрацией.
5. Design for failure
Каждый компонент может отказать. Используйте репликацию, circuit breakers, graceful degradation. Система должна продолжать работать, даже если часть компонентов недоступна.
6. Асинхронность через очереди сообщений
Kafka как «центральная нервная система» позволяет развязать сервисы. Сервис профилей не знает о поисковом индексе — он просто публикует событие «профиль обновлён» в Kafka, а поисковый сервис сам его подхватывает и обновляет индекс.
7. Гибридные подходы
Не выбирайте между push и pull — комбинируйте. Не выбирайте между offline и online ML — используйте оба. Гибридные решения часто лучше чистых.
1.15. Вопросы для самопроверки
- Почему LinkedIn перешёл от монолита к микросервисам? Какие проблемы это решило и какие создало?
- Объясните, почему BFS не подходит для определения степени связи в реальном времени при масштабе LinkedIn. Как NCS решает эту проблему?
- Чем pull-модель ленты отличается от push-модели? Почему LinkedIn использует гибридный подход?
- Зачем LinkedIn создал свою GraphQL-реализацию с предрегистрацией запросов, а не использовал стандартную?
- Что такое инвалидация кэша и почему LinkedIn убрал промежуточные кэши?
- Как система ATC решает проблему notification fatigue?
- Объясните, как работает двухэтапное ранжирование ленты (FPR → SPR). Зачем нужны два этапа?
Источники
- A Brief History of Scaling LinkedIn — LinkedIn Engineering
- LinkedIn Architecture Evolution to 930 Million Users — Medium
- How LinkedIn Adopted a GraphQL Architecture — LinkedIn Blog
- Introducing Espresso — LinkedIn Engineering
- LIquid: The Soul of a New Graph Database — LinkedIn Blog
- LinkedIn's Real-Time Graph Database Is LIquid — The New Stack
- Did You Mean "Galene"? — LinkedIn Engineering
- Hosted-Search: LinkedIn Search as a Cloud Service — LinkedIn Engineering
- Rebuilding Messaging: How We Designed Our New System — LinkedIn Engineering
- Unifying Messaging Experiences across LinkedIn — LinkedIn Engineering
- Now You See Me, Now You Don't: LinkedIn's Real-Time Presence Platform — LinkedIn Blog
- Concourse: Generating Personalized Content Notifications — LinkedIn Engineering
- LinkedIn Re-Architects Edge-Building System — InfoQ
- Using Set Cover Algorithm to Optimize Query Latency — LinkedIn Engineering
- Feed Infrastructure — LinkedIn Engineering
- LinkedIn Statistics — DemandSage
- LinkedIn Statistics — Cognism