Искусство компромиссов: системное проектирование высоконагруженных сервисов

freeCodeCamp.org 235 тыс. 3 ч 16 мин 28 мин 13.10.2026
Главное

Обычный INSERT в базу данных — это верная смерть для системы масштаба WhatsApp, тогда как переход на пакетную запись ускоряет пропускную способность сразу в сто раз. На уровне Senior-дизайна дьявол кроется не в умении писать код, а в способности предсказать race conditions при бронировании отеля или выбрать между SQL и NoSQL, основываясь на миллисекундах задержки. Это подробный гид по архитектурному мышлению, где каждый выбор — от WebSockets до CDC — является компромиссом между стоимостью и масштабируемостью.

🏗️ Профессиональный подход к System Design: Фреймворк и Оценка 0:00

Проектирование программных систем (System Design) сегодня является критически важным навыком не только для Senior и Staff-инженеров, но и для позиций уровня Mid . В условиях, когда ИИ берет на себя написание кода, компании все больше ценят специалистов, способных видеть «общую картину», понимать взаимодействие компонентов и аргументированно обсуждать компромиссы (trade-offs) . Процесс оценки кандидата выходит далеко за рамки простого рисования схем: это комплексная проверка технического суждения, умения работать в условиях неопределенности и навыков коммуникации .

📏 Пять измерений оценки кандидата 3:06

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

🔄 Пятишаговый фреймворк проектирования 9:18

Для успешного прохождения интервью (обычно длительностью 60 минут) используется повторяемая структура, помогающая сохранять фокус и демонстрировать ход мыслей :

  1. Уточнение требований (5–10 минут). Самая частая ошибка — сразу приступать к рисованию . Необходимо разделить требования на функциональные (основные сценарии использования) и нефункциональные (масштаб, задержки, доступность) . Например, при проектировании системы типа Spotify невозможно охватить всё за час, поэтому критически важно ограничить область (scope) .
  2. Проектирование API и модели данных (15–20 минут). Определение основных сущностей (например, видео и пользователи для YouTube) и связей между ними . Здесь же обсуждаются контракты API (пагинация, ошибки, версионирование) и выбор типа базы данных .
  3. Высокоуровневая архитектура (15 минут). Отрисовка базовых компонентов: API-серверов, балансировщиков нагрузки, баз данных и слоев кэширования . Важно проследить путь запроса от фронтенда до базы данных .
  4. Узкие места и надежность (5–10 минут). Обсуждение точек отказа (Single Points of Failure), механизмов резервирования и мониторинга . На этом этапе senior-инженер показывает, как система будет вести себя под нагрузкой или при сбоях .
  5. Компромиссы и расширение. Завершающий этап, где кратко перечисляются принятые жертвы (например, выбор согласованности в ущерб доступности) и возможные пути развития системы в будущем .

💡 Практические советы и этикет интервью 20:09

Интервью по System Design — это не монолог и не домашнее задание, а обсуждение между двумя коллегами .

В завершение вводной части автор упоминает, что далее эти принципы будут применены к проектированию музыкальной платформы (аналога Spotify) с нагрузкой в 500 000 пользователей и библиотекой в 30 миллионов песен .

🎧 Проектирование систем: от емкости до масштабирования 25:24

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

Расчет емкости и стоимости данных 26:30

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

Для оптимизации передачи используются разные форматы: 64 кбит/с (AAC) для экономии мобильного трафика, 128 кбит/с как стандарт и 320 кбит/с для премиум-пользователей .

Высокоуровневая архитектура и рабочие процессы 29:20

Система строится вокруг разделения тяжелых медиафайлов и легких метаданных. В центре находится балансировщик нагрузки (Load Balancer), использующий алгоритмы Round Robin или Least Connections для распределения HTTP-запросов между API-серверами .

Процесс чтения (воспроизведение песни):

  1. Приложение запрашивает песню через GET /songs/:id .
  2. API-сервер проверяет JWT-токен пользователя .
  3. Сервер запрашивает метаданные из SQL-базы и генерирует Signed URL (подписанную одноразовую ссылку) .
  4. Приложение использует этот URL для запроса фрагментов аудио напрямую из объектного хранилища (например, AWS S3), которое стримит данные обратно пользователю .

Процесс записи (загрузка артистом): Артист отправляет POST-запрос, API проверяет формат и размер файла . Аудио сохраняется в Blob-хранилище, после чего из него извлекаются метаданные (длительность, битрейт), которые записываются в SQL-базу . Если используется CDN, инициируется процесс инвалидации кеша, чтобы пользователи сразу увидели обновление .

Дизайн API и структура базы данных 35:25

Для обеспечения RESTful-принципов API разделяется на функциональные блоки:

В качестве базы данных выбрана реляционная СУБД (PostgreSQL/MySQL), так как структура требует множества соединений (Joins) . Например, для связи песен и артистов, а также плейлистов и треков используются связующие таблицы (Join Tables) . Для ускорения поиска по названиям и именам создаются индексы .

Стратегии масштабирования до 50 миллионов пользователей 44:30

Когда база пользователей вырастает в 100 раз (с 500 тыс. до 50 млн), а библиотека до 200 млн песен, система требует горизонтального масштабирования:

  1. Географическое распределение: Развертывание CDN-узлов и балансировщиков в разных регионах (Северная Америка, Европа и т.д.) для снижения задержек .
  2. Управление кешем: Использование политики LRU (Least Recently Used) для автоматического удаления редко прослушиваемого контента из кеша при заполнении хранилища .
  3. Масштабирование БД: Разделение операций чтения и записи (Read Replicas) — запись идет в мастер-базу, а чтение — в ведомые реплики (followers) .
  4. Шардирование: Разделение данных по географическому признаку (шард для США, шард для Европы) или по диапазонам ID артистов для обеспечения бесконечной горизонтальной масштабируемости .

Философия Senior-инженера 48:50

Различие между Middle и Senior разработчиком заключается в подходе к проектированию. Senior-инженер представляет процесс как пирамиду:

Мидл-разработчики часто переворачивают эту пирамиду, начиная сразу с кодинга без четкого плана, что ведет к архитектурным тупикам при масштабировании .

🏨 Проектирование системы бронирования отелей (Booking.com/Airbnb) 51:14

Разработка системы бронирования — классическая задача на интервью для системных архитекторов . Ошибка многих разработчиков заключается в том, что они рассматривают её как типичное CRUD-приложение с кэшированием и репликацией . Однако главная сложность здесь заключается в обработке конкурентности: когда два пользователя одновременно пытаются забронировать последний свободный номер . Простой подход «select, затем insert» приведет к проблеме двойного бронирования, списанию средств у обоих клиентов и потере доверия к платформе . Senior-уровень подразумевает понимание того, как сохранять целостность данных под высокой нагрузкой и проектировать систему с учетом возможных отказов отдельных компонентов .

Сбор требований и функционал 53:29

Проектирование начинается с уточняющих вопросов для определения рамок (scope) . В рамках данной системы мы принимаем следующие условия:

К основным функциональным требованиям относятся: поиск отелей по локации, датам и количеству гостей , управление бронированиями (просмотр, отмена) и предотвращение двойных бронирований . Нефункциональные требования включают строгую консистентность для процесса бронирования (нельзя допустить два бронирования на один номер) , низкую задержку (поиск менее 500 мс) и высокую доступность на уровне «трех девяток» (99.9%) .

Оценка масштаба и нагрузки (QPS vs TPS) 58:49

Для оценки нагрузки можно использовать два подхода: «сверху вниз» (на основе трафика посещений) или «снизу вверх» (на основе количества бронирований) . Если взять за основу показатели Booking.com, система получает около 1.1 млрд бронирований в год . При среднем сроке проживания в 2 ночи это дает примерно 550 млн успешных транзакций в год или 1.5 млн бронирований в день . В секунду это составляет всего 17 транзакций (TPS), которые записываются в базу данных .

Однако нагрузка на чтение значительно выше. При 500 млн визитов в месяц и просмотре примерно 8 страниц за визит, мы получаем около 200 визитов в секунду . Учитывая запросы на поиск и проверку доступности, система должна поддерживать около 1000 запросов в секунду (QPS) на чтение . Это делает систему «тяжелой на чтение» (read-heavy), что диктует выбор архитектуры, оптимизированной для быстрого поиска при сохранении строгой согласованности транзакций .

Высокоуровневая архитектура и сервисы 1:04:20

Система строится на базе сервис-ориентированной архитектуры с API Gateway в качестве единой точки входа . Основные компоненты включают:

Для обеспечения консистентности на уровне данных используется SQL-база (например, PostgreSQL), обеспечивающая ACID-гарантии . Однако для поиска 1000 QPS возможностей SQL недостаточно, поэтому внедряется ElasticSearch . Он обеспечивает полнотекстовый поиск и сложную фильтрацию (по радиусу от локации, цене, рейтингу) . Данные в ElasticSearch попадают через механизм событийной согласованности (eventual consistency): после успешной оплаты сообщение публикуется в очередь (Kafka или RabbitMQ), откуда оно считывается для обновления поискового индекса .

Проектирование API и проблема микросервисов 1:10:01

При выборе между монолитом и микросервисами важно найти баланс. Полные микросервисы с разделенными базами данных порождают проблемы распределенных транзакций . Прагматичный подход для этой системы — общая база данных для тесно связанных сущностей (Hotels и Reservations) . Это позволяет использовать локальные ACID-транзакции вместо сложных распределенных протоколов, сохраняя при этом разделение логики на уровне приложений .

API проектируется по принципам REST :

  1. GET /hotels — поиск через ElasticSearch с фильтрами .
  2. GET /hotels/{id} — получение детальной информации напрямую из основной БД .
  3. POST /hotels — создание нового объекта админом (сначала запись в SQL, затем асинхронное обновление индекса поиска) .
  4. PUT/PATCH /hotels/{id} — обновление данных .
  5. Номера отелей обрабатываются как подресурсы: /hotels/{id}/rooms .

🏨 Управление бронированием: API и архитектура данных 1:15:31

Процесс проектирования системы бронирования отелей требует четкого разделения зон ответственности между сервисами и тщательной проработки структуры базы данных. На этапе реализации API важно следовать принципам RESTful для обеспечения предсказуемости интерфейса . В то время как управление информацией об отелях и номерах кажется тривиальным, настоящая сложность кроется в логике резервирования, предотвращении двойных бронирований и эффективном поиске .

Проектирование REST API и обработка транзакций 1:15:31

Основные эндпоинты системы делятся на управление каталогом и управление бронированиями. Для работы с отелями используются стандартные GET-запросы к /hotels и /hotels/{id}, которые возвращают список доступных объектов или детальную информацию из базы данных . Аналогичная логика применяется к номерам: добавление нового типа номера происходит через POST-запрос к /hotels/{id}/rooms .

Специфика бронирования (Reservation Service) требует более сложных механизмов:

Для предотвращения дублирующихся запросов от одного и того же пользователя рекомендуется генерировать уникальный идентификатор бронирования (Reservation ID) на стороне фронтенда . Это «легковесное» решение позволяет бэкенду просто проверять наличие ID в базе: если транзакция с таким идентификатором уже существует, система вернет существующую бронь вместо создания новой .

Модели данных и инвентаризация номеров 1:12:53

Фундаментальный принцип системы: пользователь бронирует тип номера (Room Type), а не конкретную физическую комнату . Конкретный номер комнаты присваивается гостю только в момент регистрации (check-in) .

Архитектура БД строится на следующих сущностях:

  1. Hotels: Содержит ID, название, адрес и описание. Для крупных сетей можно использовать таблицу Hotel Groups, объединяющую разные филиалы под одним брендом .
  2. Room Types: Определяет характеристики (количество кроватей, вместимость). Отношение с отелями — «один ко многим» .
  3. Room Type Rates: Позволяет гибко управлять ценами для одного типа номера (например, базовый тариф, вид на океан или тариф с завтраком) .
  4. Room Type Inventory: Ключевая таблица, отслеживающая наличие мест по дням для каждого типа номера в конкретном отеле . Она предзаполняется на 2 года вперед .
  5. Transactions: Вынесение платежей в отдельную таблицу позволяет отслеживать несколько операций по одной брони, например, доплату при смене дат или частичный возврат при отмене .

Гарантии консистентности и борьба с Race Conditions 1:13:14

Выбор реляционной базы данных (SQL) обусловлен необходимостью соблюдения ACID-гарантий . При соотношении 1000 чтений к 17 записям в секунду SQL-системы обеспечивают нужную консистентность, предотвращая ситуацию, когда два разных пользователя одновременно бронируют последний доступный номер .

Для решения проблем конкурентности рассматриваются два подхода:

Масштабирование через шардирование и поиск 1:17:11

Когда нагрузка на БД начинает расти, применяется шардирование. В качестве ключа шардирования (shard key) эффективнее всего использовать hotel_id . Это гарантирует, что все данные по одному отелю (типы номеров, инвентарь) останутся на одном узле, позволяя выполнять локальные джойны без дорогостоящих кросс-шардовых запросов .

Для распределения данных используются два метода:

  1. Modulo Sharding: hash(hotel_id) % N. Подходит, если количество шардов стабильно . Однако добавление нового сервера потребует перераспределения почти всех данных .
  2. Consistent Hashing: Использует хэш-кольцо, где добавление нового узла затрагивает лишь малую часть данных (около 1/N) . Это предпочтительный вариант для динамически растущих систем .

Ранее в разговоре они касались использования Elastic Search для ускорения поиска по фильтрам (локация, цена, даты), так как выполнение таких тяжелых запросов напрямую в SQL-базе было бы слишком медленным для масштабируемой системы .

🏨 Оптимизация поиска и масштабирование системы бронирования 1:40:34

Кэширование инвентаря и управление доступностью 1:40:34

Для эффективной работы системы бронирования отелей необходимо кэшировать «горячие» данные об инвентаре в Redis или Memcached . Структура кэша представляет собой ключ, состоящий из комбинации ID отеля, типа номера и даты, а значение содержит количество доступных номеров и общее число забронированных мест . При расчетах также учитывается фиксированный процент овербукинга (например, 10%), который не хранится в кэше, но используется логикой приложения .

Процесс работы с кэшем выглядит следующим образом:

Оптимизация поиска с Elastic Search 1:43:09

Использование стандартных SQL-запросов с оператором LIKE для поиска отелей крайне неэффективно, так как это приводит к полному сканированию таблиц (full table scan), что убивает производительность даже при наличии индексов . SQL плохо справляется с ранжированием по релевантности, опечатками в запросах пользователей и сложными географическими расчетами .

Elastic Search решает эти проблемы, предлагая специализированный движок для поиска и аналитики :

  1. Сложные фильтры: Позволяет быстро находить отели по множеству критериев (цена, звездность, локация) среди миллионов документов .
  2. Полнотекстовый поиск: Токенизирует запросы (например, «luxury hotel Paris») и ранжирует результаты по релевантности .
  3. Обработка опечаток: Благодаря алгоритмам расстояния редактирования система поймет запрос, даже если пользователь пропустил букву в названии города .
  4. Гео-поиск: Оптимизирован для нахождения объектов в радиусе определенного количества километров от заданных координат .

Данные в Elastic Search денормализуются: вместо множества связанных таблиц SQL (отели, типы номеров, удобства) создается один JSON-документ, содержащий всю необходимую информацию, что исключает необходимость дорогостоящих JOIN-операций при поиске .

Синхронизация данных и репликация 1:47:48

Поскольку PostgreSQL остается единственным источником истины (Single Source of Truth) для транзакций, необходимо поддерживать актуальность данных в Elastic Search . Для этого используется механизм Change Data Capture (CDC). Инструмент с открытым исходным кодом Debezium считывает логи упреждающей записи (Write Ahead Log) из Postgres и транслирует изменения в Kafka . Затем специальные потребители (consumers) обновляют индексы Elastic Search, обеспечивая итоговую согласованность (eventual consistency) .

Для обеспечения масштабируемости при нагрузке в тысячи запросов в секунду применяется репликация базы данных :

💬 Проектирование мессенджера (аналог WhatsApp) 1:53:18

Требования и оценка масштаба 1:54:38

Проектирование систем обмена сообщениями — классическая задача для Senior-интервью, требующая понимания работы с постоянными соединениями (stateful connections) и обеспечения надежности при сбоях . Основные функциональные требования включают:

Система должна обладать низкой задержкой (менее 500 мс) и гарантировать доставку сообщений . При масштабе в 500 млн активных пользователей ежедневно генерируется около 10 млрд сообщений . Это создает нагрузку в 115 000 записей в секунду в среднем и до 500 000 в пиковые часы . Общий объем данных может достигать 3.6 петабайт в год, однако стратегия мессенджеров обычно подразумевает удаление сообщений с сервера сразу после их успешной доставки на устройство пользователя .

Высокоуровневая архитектура компонентов 1:59:11

Архитектура системы строится на следующих ключевых элементах:

Протоколы связи: почему WebSocket? 2:03:41

Существует три основных подхода к организации связи между клиентом и сервером:

  1. Polling (опрос): Клиент периодически запрашивает новые данные. Это неэффективно, так как создает задержки и тратит ресурсы сервера на пустые ответы .
  2. Long Polling: Сервер удерживает запрос открытым до появления новых данных или тайм-аута. Это лучше обычного опроса, но все равно требует частого переустановления соединений .
  3. WebSocket: Создает единое долгоживущее TCP-соединение. Именно этот вариант является стандартом для мессенджеров, так как обеспечивает мгновенную передачу данных в обе стороны с минимальными накладными расходами .

📡 Инфраструктура реального времени: WebSockets и синхронизация данных 2:05:39

В основе современных мессенджеров лежит необходимость мгновенного обмена сообщениями. Использование традиционных HTTP-запросов для этих целей неэффективно: постоянное открытие и закрытие соединений, а также полный HTTP-handshake для каждого сообщения создают огромную нагрузку на систему . В условиях масштабирования чат-серверы тратят больше ресурсов на управление жизненным циклом соединений, чем на саму доставку данных .

Решением является использование WebSockets — постоянного двунаправленного соединения между клиентом и сервером . Процесс начинается с инициации HTTP-соединения, которое затем «апгрейдится» до WebSocket . Это позволяет серверу мгновенно «пушить» сообщения пользователю, как только они становятся доступны, а клиенту — отправлять ответы без создания новых соединений .

Однако у WebSockets есть свои компромиссы:

🗄️ Проектирование схемы данных и API 2:09:31

Для системы уровня WhatsApp требуется комбинация различных типов баз данных . Информация о пользователях (ID, номер телефона, имя, время последнего входа) хранится в реляционных БД, таких как PostgreSQL или MySQL .

Для самих сообщений, учитывая их огромный объем, идеально подходит Cassandra — распределенная NoSQL база данных . Таблица сообщений включает:

Для управления группами используются таблицы groups и group_members в SQL-базе, обеспечивая связь «один ко многим» . Важным элементом является User Connections Registry на базе Redis . Этот кэш хранит соответствие ID пользователя и ID сервера, к которому он подключен, обновляясь каждые 300 секунд через «heartbeat» .

Взаимодействие начинается с аутентификации: клиент отправляет токен и данные устройства, получая в ответ ID сессии и счетчик непрочитанных сообщений . После установления WebSocket-соединения сервер может не только доставлять новые сообщения, но и подтверждать получение (acknowledgement) или сообщать о прочтении .

🔄 Синхронизация между устройствами и обработка медиа 2:15:31

Современный пользователь может одновременно использовать телефон, планшет и десктоп . Когда User A отправляет сообщение, сервер чата должен определить все активные устройства User B через Redis . Если User B подключен к разным серверам (например, серверу B и серверу E), сообщение дублируется на оба узла . Синхронизация статусов (например, «прочитано») также транслируется на все устройства обоих участников .

При конфликтах данных между устройствами используется техника LWW (Last Write Wins) с номерами версий . Для экономии ресурсов применяется инкрементальная синхронизация: новые устройства запрашивают историю только за последние 30 дней, а старые данные подгружаются лениво из «холодного» хранилища .

Передача медиафайлов (фото, видео) реализуется через REST API, а не через WebSockets, чтобы не перегружать чат-серверы тяжелым трафиком . Процесс выглядит так:

  1. Клиент запрашивает у сервера Pre-signed URL для загрузки .
  2. Сервер генерирует временную ссылку на объектное хранилище (например, AWS S3) .
  3. Клиент загружает файл напрямую в S3, минуя чат-сервер .

👥 Групповые чаты и оффлайн-доставка 2:22:31

Если получатель сообщения находится оффлайн, сервер добавляет сообщение в индивидуальный инбокс в Redis и БД . Одновременно с этим инициируется push-уведомление через системы Apple (APNs) или Google (FCM) . При возвращении пользователя в сеть сервер проверяет наличие ожидающих сообщений в инбоксе и доставляет их .

В групповых чатах (на 50–100 человек) нагрузка возрастает . При отправке сообщения сервер запрашивает список участников у Group Service . Затем система проверяет статус каждого участника через Redis:

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

📦 Оптимизация передачи контента и масштабирование записи 2:30:44

При проектировании систем уровня WhatsApp передача тяжелых медиафайлов (видео, изображения, голосовые заметки) требует принципиально иного подхода, чем пересылка текстовых сообщений . Если чат-сервер, обслуживающий 100 000 соединений, попытается одновременно передавать файлы даже по несколько мегабайт, он мгновенно выйдет из строя под нагрузкой на полосу пропускания .

Обработка медиафайлов через Pre-signed URLs и CDN 2:30:44

Для разгрузки системы используется механизм Pre-signed URLs (пре-подписанных ссылок) и объектное хранилище (например, S3). Процесс выглядит следующим образом:

Декуплинг и пакетная запись через Kafka 2:34:05

Чтобы масштабировать обработку сообщений до миллионов транзакций, необходимо отвязать (decouple) прием сообщений от их записи в базу данных . В центре этой архитектуры стоит Kafka или аналогичная очередь сообщений .

  1. Партиционирование: Каждому инстансу сервиса хранения сообщений назначаются свои партиции (например, инстанс №1 обрабатывает партиции 0–9, инстанс №2 — 10–19) .
  2. Batch Writing (Пакетная запись): Вместо того чтобы выполнять 1000 отдельных вставок в БД, каждая из которых занимает ~5 мс (итого 5 секунд), система накапливает пакет в памяти (например, 1000 сообщений за 5 секунд) и выполняет одну атомарную запись [2:38:15, 2:40:29]. Это сокращает время операции до 50 мс, обеспечивая 100-кратный прирост пропускной способности .
  3. Обогащение данных: Перед записью каждый инстанс валидирует схему, проверяет лимиты метаданных, вычисляет хэш сообщения для дедупликации и извлекает упоминания (@username) или ссылки для генерации превью [2:37:22, 2:37:48].
  4. Обработка ошибок: Если запись не удалась, используется логика повторов (retries). Если и это не помогает, сообщение отправляется в Dead Letter Queue (DLQ) — отдельную очередь для проблемных данных, которые будут обработаны позже вручную или специальным механизмом, чтобы не блокировать основной поток [2:35:51, 2:39:35].

Сквозное шифрование (End-to-End Encryption) 2:41:49

Безопасность в WhatsApp строится на базе Signal Protocol, который гарантирует, что даже серверы компании не могут прочитать содержимое переписки .

Борьба с абьюзом и Rate Limiting 2:45:08

Для предотвращения DDOS-атак и скрапинга данных необходимо внедрить строгие лимиты на действия пользователей .

Система проверяет каждый тип активности через распределенное хранилище (например, Redis для обеспечения атомарности между серверами) :

При превышении лимитов применяется прогрессивное наказание: сначала предупреждение («Too many requests»), затем блокировка на 1 час, затем на 24 часа, и, наконец, деактивация аккаунта для ручной проверки модератором [2:46:40, 2:47:06]. Для проверки, является ли пользователь человеком, может быть предложено решение капчи .

Шардирование базы данных 2:52:10

Когда данных становится слишком много для одного сервера, применяется шардирование (разделение данных между инстансами БД) . Существует два основных подхода:

  1. По User ID: Все сообщения пользователя А хранятся в одном шарде. Чтение происходит мгновенно, но активные пользователи создают «горячие точки» (hotspots), делая шарды несбалансированными [2:52:35, 2:53:03].
  2. По Message ID: Обеспечивает равномерное распределение данных, но для получения истории переписки одного пользователя придется опрашивать все шарды системы, что крайне медленно и дорого [2:53:30, 2:53:42].

Оптимальное решение для мессенджера — шардирование по User ID с дублированием записей . Сообщение записывается дважды: в шард отправителя и в шард получателя . Это удваивает объем записи, но делает чтение истории «ослепительно быстрым», так как каждый пользователь обращается только к своему шарду или его реплике [2:54:50, 2:55:31].

🏁 Масштабирование, отказоустойчивость и стратегия проектирования 2:55:44

На финальных этапах проектирования крупной системы, такой как WhatsApp, фокус смещается с базовой логики на обеспечение глобальной доступности и выживаемости сервиса при сбоях. Важно понимать, что идеальных систем не существует, и задача инженера — не избежать ошибок, а минимизировать их последствия через продуманные компромиссы (trade-offs).

Оптимизация хранилища: шардирование и репликация 2:55:44

При работе с огромными объемами данных необходимо заранее определить, что является приоритетом: скорость записи или скорость чтения . Если этот вопрос не был прояснен на этапе сбора требований, его стоит задать именно сейчас, прежде чем внедрять сложные механизмы оптимизации .

Для масштабирования записи обычно применяется шардирование — разделение базы данных на более мелкие фрагменты (шарды) . Например, данные могут распределяться по user_id: пользователи с ID от 1 до 1000 попадают в первый шард, который становится основным узлом (primary node) для их записей .

Для масштабирования чтения используется репликация . Данные из основного узла асинхронно синхронизируются с несколькими репликами (обычно тремя), которые обеспечивают итоговую согласованность (eventual consistency) . Все операции чтения, такие как просмотр списка чатов или непрочитанных сообщений, направляются исключительно на реплики, не нагружая основной узел .

Очистка данных и фоновые задачи 2:58:00

В системах такого масштаба невозможно бесконечно хранить все данные в оперативной памяти или быстрых хранилищах. Необходима инфраструктура для очистки «мусора»: просроченных медиафайлов, старых недоставленных сообщений или устаревших соединений .

Для этого внедряется планировщик задач (например, Airflow или стандартные cron-задания), который запускает воркеры (worker pool) на ежедневной или еженедельной основе . Процесс выглядит следующим образом:

Хотя такие задания усложняют эксплуатацию системы (operational complexity), альтернатива — накопление «мусора» до тех пор, пока система не деградирует полностью .

Стратегии восстановления при отказе кэша и Kafka 3:00:19

Даже самые надежные компоненты, такие как Redis или Kafka, могут выйти из строя. Проектирование «пути отступления» — критическая часть работы Senior-инженера.

Отказ Redis: Поскольку Redis используется для быстрого хранения входящих сообщений, его падение может парализовать доставку. Решением является механизм fallback to database . Если Redis недоступен, сервер чата должен уметь считывать и записывать сообщения напрямую в основную базу данных, пусть это и будет медленнее . После восстановления Redis запускается фоновое задание для синхронизации пропущенных данных из БД обратно в кэш .

Отказ Kafka: Если кластер Kafka становится недоступен, серверы чатов не могут публиковать сообщения, что грозит потерей данных . Для минимизации потерь используется локальный буфер в памяти сервера чата .

  1. При ошибке записи в Kafka сообщение помещается в локальный буфер (например, на 100 000 записей) .
  2. Система ждет заданное время (например, 2 секунды), необходимое для перезапуска Kafka, и пробует снова .
  3. Если сервер чата тоже рискует упасть, данные записываются в локальный лог-файл на диске (append-only log) . После восстановления сервера данные «проигрываются» из этого файла и отправляются в Kafka .

Глобальное распределение и Feature Flags 3:04:28

Для обслуживания пользователей по всему миру (например, в Токио или Лондоне) система должна быть распределена по регионам (US-East, US-West, Singapore и т.д.) . Каждый регион содержит полный стек: балансировщики нагрузки, серверы чатов, кэш и шарды базы данных . Пользователь подключается к ближайшему IP-адресу через DNS-lookup для минимизации задержек .

Для безопасного внедрения обновлений используются Feature Flags (флаги функций) . Инструменты вроде LaunchDarkly позволяют:

Резюме: как подходить к System Design интервью 3:13:44

Успешное проектирование системы — это процесс, а не конечный результат. Автор выделяет следующие шаги:

  1. Начинайте с простого: уточните требования, сделайте предварительные расчеты («back-of-the-envelope calculations») и постройте высокоуровневую схему для небольшого числа пользователей .
  2. Эволюционируйте: постепенно выявляйте «узкие места» и предлагайте решения по масштабированию .
  3. Обсуждайте компромиссы: объясняйте, почему вы выбрали WebSockets вместо HTTP-поллинга, признавая, что они потребляют больше памяти, но дают реальное время .
  4. Отказоустойчивость напоследок: приступайте к обсуждению сценариев сбоев только после того, как основные компоненты системы определены .

Главная цель — показать процесс мышления. Интервьюер хочет видеть не «идеальную» схему, а умение рассуждать вслух, воспринимать подсказки и выбирать инструменты на основе конкретных задач, а не личных предпочтений .

💬 Цитаты

«Системное проектирование — это проверка того, как вы думаете, а не только того, что вы знаете.»

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

«Использование оператора LIKE в SQL убивает производительность, так как приводит к полному сканированию таблицы.»

Интервьюер/Спикер 1:44:53

«A single batched insert of thousand messages will end up costing us 50 milliseconds which is actually 100x throughput improvement.»

«Understanding tradeoffs is another important piece... Every decision has costs.»

«Чат-серверы часто тратят больше времени на управление жизненным циклом соединений, чем на саму доставку сообщений.»

👥 Спикер
📖 Термины
CDC (Change Data Capture)
Процесс отслеживания изменений в базе данных для их синхронизации с другими системами, например, ElasticSearch.
Signed URLs
Ссылки с ограниченным сроком действия, позволяющие безопасно скачивать файлы напрямую из облачного хранилища.
Row Locking
Механизм блокировки строки в базе данных для предотвращения одновременного изменения данных разными транзакциями.
WebSocket
Протокол связи, обеспечивающий двунаправленный обмен данными в реальном времени между клиентом и сервером.
Технологии и IT System Design WhatsApp Architecture ElasticSearch Microservices Scalability