Перейти к содержанию
Бэкенд и системы

29 вопросов по теме «System Design» на собеседовании

В этом материале — 29 вопросов из русской колоды RecallDeck по теме «System Design». Сначала сформулируйте короткий ответ сами, затем откройте подробный разбор и проверьте примеры, ограничения и отказные случаи.

19 мин чтения29 подробных ответовПроверено 24 августа 2026
Главная мысль

Стройте ответ вокруг владения состоянием, лимитов, отказа и восстановления. Определение становится инженерным ответом только в конкретном production-сценарии.

Вопросы и ответы

29 подробных ответов

01

Шаг 1. Прояснить требования

Никогда не начинайте проектировать сразу. Сначала уточните, что именно строим. Разделите требования на две группы.

Функциональные требования — что система делает (фичи):

  • Какие основные сценарии? (например: «сократить ссылку» и «перейти по короткой ссылке»).
  • Кто пользователи? Сколько их? Гео-распределение?
  • Что НЕ входит в скоуп? (аналитика, аутентификация, биллинг — часто можно отбросить, явно проговорив это).

Нефункциональные требования (NFR) — какими свойствами обладает:

  • Масштаб: сколько пользователей / запросов / данных.
  • Доступность (availability): важнее ли «всегда отвечать» или «отвечать корректно»? (CAP).
  • Latency: жёсткие требования (p99 < 100 мс) или нет.
  • Консистентность: допустима ли eventual consistency, или нужна строгая.
  • Durability: можно ли терять данные (логи vs платежи).
  • Read/Write ratio: преобладает чтение или запись.

💡 Задавайте вопросы вслух и фиксируйте ответы — это уже половина оценки. Пример: «Нам нужна аналитика по кликам? Если да — это меняет модель данных. Предположу, что в скоупе только базовый счётчик».

02

Шаг 2. Оценить масштаб (back-of-the-envelope)

Грубые прикидки, которые определят архитектуру. Считайте вслух, округляйте агрессивно.

Что оценить:

  • QPS (запросов в секунду): средний и пиковый (пик ≈ 2-3× среднего).
  • Объём данных: размер одной записи × число записей × горизонт хранения.
  • Соотношение чтение/запись (read:write) — определяет, нужны ли реплики чтения и кэш.
  • Пропускная способность (bandwidth): QPS × размер ответа.
  • Хранилище (storage): рост в год.

Полезные числа для счёта в уме:

Величина Значение
Секунд в сутки ~86 400 ≈ 10⁵
Месяц ~2.5 млн сек
1M запросов/сутки 12 QPS в среднем
1B запросов/сутки 12 000 QPS

Пример расчёта (URL shortener): 100M новых ссылок/мес → 100M / 2.5M сек ≈ 40 записей/сек. Read:write = 100:1 → 4000 чтений/сек. Размер записи ~500 байт → 100M × 500B = 50 ГБ/мес → 600 ГБ/год → за 10 лет ~6 ТБ.

💡 Вывод из чисел сразу формулируйте: «40 записей/сек — это легко для одной БД. 4000 чтений/сек — добавим кэш и реплики чтения».

03

Шаг 3. Определить API

Опишите контракт сервиса — это фиксирует функциональность и помогает интервьюеру понять модель. REST/gRPC, основные эндпоинты:

POST /api/v1/urls           {long_url, custom_alias?, ttl?} -> {short_url}
GET  /{short_key}           -> 301/302 redirect

Проговорите: методы, параметры, идемпотентность (POST идемпотентен?), пагинацию для списков (cursor-based, не offset на больших данных), авторизацию (api_key / токен).

Контракт должен включать не только happy path: коды ошибок, лимиты размера, таймауты и поведение повторного запроса. Для создания ресурса клиент может прислать Idempotency-Key; сервер сохраняет ключ вместе с результатом и возвращает тот же ответ при ретрае. Так API становится проверяемой границей системы, а не списком случайных URL.

request → validate/auth → domain operation → stable response
              │                 │
          4xx contract      retry-safe effect
04

Шаг 4. Модель данных и выбор БД

  • Опишите ключевые сущности и связи (таблицы / коллекции).
  • Выберите тип БД и обоснуйте:
    • SQL (Postgres, MySQL): сложные связи, транзакции, строгая консистентность, аналитические запросы, JOIN. Берите по умолчанию, если нет причины не брать.
    • NoSQL key-value / wide-column (DynamoDB, Cassandra): огромный масштаб записи, простой паттерн доступа по ключу, горизонтальное шардирование «из коробки», eventual consistency.
    • Документная (MongoDB): гибкая схема, вложенные документы.
    • In-memory (Redis): кэш, счётчики, rate limiting, очереди, leaderboard.
  • Сразу подумайте о ключе шардирования (по чему партиционируем).
05

Шаг 5. High-level архитектура

Опишите словами (или схемой) компоненты и поток запроса:

[Client] -> [DNS] -> [Load Balancer] -> [API / App Servers (stateless)]
                                              |-> [Cache (Redis)]
                                              |-> [Database (+ read replicas)]
                                              |-> [Message Queue] -> [Workers]
                                         [CDN] -> [Blob Storage (S3)]

Проведите типичный запрос по этому пути от клиента до БД и обратно.

Не рисуйте каждый известный компонент. Начните с минимального пути, который выполняет требования, и добавляйте кэш, очередь или реплики только когда расчёты показывают необходимость. Для каждого ребра назовите протокол, синхронность, таймаут и источник истины; для каждого stateful-компонента — владельца данных, репликацию и восстановление.

Затем проведите два сценария: обычный запрос и отказ. Например: cache miss идёт в БД и заполняет кэш; при недоступной БД запрос быстро завершается по таймауту, circuit breaker ограничивает каскад, а метрики показывают деградацию. Схема ценна только вместе с поведением потоков.

06

Шаг 6. Детализация ключевых компонентов

Углубитесь в 1–2 части, которые определяют корректность или масштаб именно этой задачи: генерацию ключей, ранжирование ленты, доставку сообщений либо схему кэширования. Не пересказывайте весь стек одинаково поверхностно.

Для выбранной части зафиксируйте данные и инварианты, пройдите алгоритм на конкретном запросе, оцените сложность и разберите хотя бы один сбой. Например, для резервирования билета инвариант — sold + held ≤ capacity; одной диаграммы сервисов недостаточно, нужно показать атомарный UPDATE ... WHERE available > 0, TTL удержания и идемпотентный платёжный ретрай.

требование → инвариант → алгоритм → состояние → отказ → восстановление

Хорошая детализация заканчивается измеримым trade-off: что выигрываем, чем платим и при каком изменении нагрузки решение придётся пересмотреть.

07

Шаг 7. Узкие места, масштабирование, отказоустойчивость, trade-offs

  • Где bottleneck? (обычно БД на чтение/запись, или один компонент).
  • Как масштабировать каждый слой (горизонтально предпочтительнее).
  • Где single point of failure? Как продублировать.
  • Какие компромиссы приняли и почему (консистентность vs доступность, стоимость vs latency).

💡 Финал хорошего ответа всегда звучит как: «Здесь я выбрал X, пожертвовав Y, потому что для этого сценария важнее Z. Альтернатива — W, она лучше при другом профиле нагрузки».

08

Load Balancer и Reverse Proxy

  • LB распределяет трафик между инстансами (round-robin, least-connections, consistent hashing). Даёт горизонтальное масштабирование и отказоустойчивость (убирает мёртвые ноды через health checks).
  • L4 (TCP) — быстрее; L7 (HTTP) — умеет роутинг по URL/заголовкам, TLS-терминацию.
  • Reverse proxy (Nginx, Envoy): TLS-терминация, сжатие, кэш, защита бэкенда, единая точка входа.
  • 💡 Приложения держите stateless — тогда LB может слать запрос на любой инстанс. Сессии — в Redis, не в памяти процесса.
09

Кэширование

  • Где: на клиенте, CDN, на уровне приложения (Redis/Memcached), в БД.
  • Паттерны:
    • Cache-aside (lazy): приложение читает кэш, при промахе — БД, затем кладёт в кэш. Самый частый.
    • Write-through: пишем в кэш и БД синхронно (консистентно, но медленнее запись).
    • Write-back: пишем в кэш, в БД асинхронно (быстро, риск потери).
  • Инвалидация (одна из двух сложнейших проблем в CS): TTL, явное удаление при записи, версионирование ключей.
  • Проблемы: cache stampede (множество промахов одновременно → блокировки/early recompute), hot keys, согласованность кэша и БД.
  • При отказе кэша защищайте источник: ограничивайте конкурентные промахи, добавляйте jitter к TTL и не позволяйте всем инстансам одновременно прогревать один hot key.
10

CDN

  • Кэширует статику (картинки, видео, JS/CSS) ближе к пользователю (edge). Снижает latency и нагрузку на origin.
  • Применять для любого тяжёлого статического / редко меняющегося контента и для географически распределённой аудитории.
  • Ключ кэша строится из URL и выбранных заголовков; версионированные имена файлов позволяют долгий Cache-Control: immutable, а изменяемый HTML требует короткого TTL или purge.
  • Динамический запрос тоже можно кэшировать, если ответ публичный и ключ учитывает все варианты. Персональные ответы и cookie нельзя случайно разделить между пользователями.
client ─► nearest edge ──HIT──► response

               MISS ─► origin ─► cache at edge

⚠️ CDN не исправляет медленный origin на первом промахе; защищайте origin от stampede и наблюдайте hit ratio отдельно по типам контента.

11

Базы: репликация, шардирование, партиционирование

  • Репликация (read replicas): копии БД для масштабирования чтения и отказоустойчивости. Запись идёт в primary, чтение — с реплик. Trade-off: replication lag → eventual consistency на чтении.
  • Шардирование (sharding): горизонтальное разбиение данных по нескольким БД по ключу. Масштабирует запись и объём. Стратегии: по хэшу ключа, по диапазону, по гео. Минусы: сложные JOIN/транзакции между шардами, ребалансировка, hot shards.
  • Партиционирование — то же внутри одной БД (по диапазону дат, по списку). Упрощает работу с большими таблицами.
  • Выбор ключа шардирования критичен: должен равномерно распределять и совпадать с паттерном доступа.
12

Очереди и асинхронная обработка

  • Message Queue (Kafka, RabbitMQ, SQS) развязывает producer и consumer. Применять для: тяжёлых задач не в запросе пользователя (отправка email, обработка видео, fan-out ленты), сглаживания пиков нагрузки (буфер), повторов при сбоях.
  • Гарантии доставки: at-most-once / at-least-once / exactly-once. Чаще всего at-least-once + идемпотентные consumer'ы.
  • Продюсер пишет событие через transactional outbox, а консюмер дедуплицирует стабильный message_id в одной транзакции с бизнес-эффектом. Ошибки уходят в retry-топик с backoff, затем в DLQ для разбора.
  • Очередь не убирает нагрузку, а переносит её во времени. Следите за lag, возрастом самого старого сообщения и пропускной способностью; при переполнении нужны backpressure и явная политика отбрасывания или деградации.
13

Индексы и денормализация

  • Индексы ускоряют чтение ценой замедления записи и доп. места. Создавайте под реальные запросы (WHERE/JOIN/ORDER BY).
  • Денормализация: дублирование данных, чтобы избежать JOIN при чтении. Применять в read-heavy системах (лента, профили). Trade-off: сложнее поддерживать консистентность при записи.
  • Начинайте с access pattern: индекс (tenant_id, created_at DESC) полезен для конкретного фильтра и сортировки, а «индексировать всё» увеличивает write amplification и время vacuum/rebuild.
  • Для денормализованной копии назовите источник истины и механизм обновления: синхронная транзакция, outbox/CDC или периодическая пересборка. Также определите допустимое окно stale-данных и способ repair после пропущенного события.
write → source of truth → outbox/CDC → read model
                                  ↘ retry + reconciliation
14

CAP-теорема и консистентность

  • При сетевом разделении (P, неизбежно в распределённых системах) выбираем между C (consistency) и A (availability):
    • CP: при разрыве жертвуем доступностью ради корректности (банки, инвентарь). Пример: HBase, etcd.
    • AP: продолжаем отвечать, допуская расхождение (лента, лайки). Пример: Cassandra, DynamoDB.
  • На практике важнее PACELC: даже без разрыва (Else) есть выбор Latency vs Consistency.

CAP рассматривает только период сетевого разделения, а не классифицирует систему навсегда как «CA». Во время partition CP-система отклоняет часть операций, чтобы не допустить противоречивых записей; AP принимает их и позже разрешает конфликты. Выбор делают по операции: перевод денег требует строгого инварианта, а счётчик лайков может временно расходиться.

partition? ─ yes ─► consistency or availability
           └ no  ─► PACELC: latency or consistency

⚠️ Репликация сама по себе не даёт strong consistency: важны лидерство, кворумы, правила чтения и поведение при failover.

15

Согласованность: strong vs eventual, кворумы

  • Strong consistency: любое чтение видит последнюю запись. Дороже, выше latency. Нужна для денег, остатков, уникальности.
  • Eventual consistency: реплики сходятся со временем. Дёшево и доступно. Ок для лайков, счётчиков просмотров, ленты.
  • Кворумы: при N репликах требуем подтверждения от W при записи и R при чтении. Если W + R > N — гарантируется чтение свежих данных (например N=3, W=2, R=2). Настройка W/R балансирует консистентность, latency и доступность.
16

Идемпотентность и dedup

  • Идемпотентность: повтор операции даёт тот же результат (повторный POST платежа не списывает дважды).
  • Реализация: idempotency key от клиента → сервер хранит результат по ключу и при повторе возвращает сохранённый, не выполняя операцию снова. Хранить в Redis/БД с TTL.
  • Dedup сообщений в очередях — по message_id.
  • Проверка ключа и бизнес-эффект должны быть атомарны. Надёжный вариант — строка idempotency_key с UNIQUE в той же БД и транзакции; отдельная проверка Redis перед записью оставляет окно гонки.
  • Сохраняйте статус и сериализованный ответ, проверяйте, что повтор пришёл с тем же payload, и выбирайте TTL длиннее максимального окна клиентских ретраев. FAILED с неизвестным исходом нельзя бездумно запускать заново.
same key + same payload → same stored result
same key + other payload → 409 conflict
17

Rate Limiting

  • Rate limiting защищает ёмкость системы и ограничивает злоупотребления. Fixed window прост, но допускает двойной всплеск на границе окна; sliding log/window точнее, но хранит больше состояния; token bucket разрешает контролируемый burst и затем восстанавливает токены с постоянной скоростью.
  • Ключ выбирают по пользователю, API key, tenant или IP; ответ 429 должен содержать Retry-After. Лимит размещают до дорогой работы, но после достаточной идентификации клиента.
  • В распределённой системе состояние держат в Redis и обновляют атомарным Lua-скриптом. При отказе Redis заранее выбирают fail-open для некритичного API либо fail-closed для дорогой/чувствительной операции.
request → identify key → consume token? ─ yes → handler
                                  └ no  → 429 + Retry-After
18

Микросервисы vs монолит

  • Монолит: проще разрабатывать, деплоить, дебажить; одна транзакция. Берите по умолчанию для нового продукта.
  • Микросервисы: независимое масштабирование и деплой команд, изоляция отказов. Цена: сетевые вызовы, распределённые транзакции (saga), сложный observability, eventual consistency между сервисами.
  • 💡 На собесе: «Начал бы с модульного монолита, выделил бы в сервисы то, что нужно масштабировать независимо или что владеет отдельная команда».
  • Границу сервиса проводят вокруг бизнес-владения и данных, а не вокруг технического слоя. Выделение оправдано, когда модуль требует независимого релизного цикла, профиля масштабирования или команды; иначе сеть превращает простой вызов функции в контракт с таймаутами, ретраями и частичными отказами.
19

API Gateway

  • Единая точка входа: маршрутизация, аутентификация, rate limiting, агрегация ответов, версионирование, TLS. Снимает кросс-задачи с сервисов. Риск — стать SPOF/узким местом, поэтому масштабируется и дублируется.
  • Gateway не должен превращаться в новый монолит бизнес-логики. Он проверяет токен и общие политики, но авторизация конкретного ресурса остаётся в сервисе-владельце.
  • Разворачивайте несколько stateless-инстансов за балансировщиком, задавайте короткие downstream timeouts, ограничивайте тело запроса и наблюдайте latency/error rate по каждому маршруту. Агрегация ответов экономит клиентские round trips, но требует частичного ответа и fallback при отказе одного downstream.
client → gateway ┬→ users
                 ├→ orders
                 └→ catalog
20

Blob Storage (S3)

  • Объектное хранилище для файлов, картинок, видео, бэкапов. Дёшево, durable (репликация внутри), бесконечно масштабируемо.
  • Паттерн: метаданные в БД, сам файл — в S3, отдача через CDN, загрузка через pre-signed URL (клиент пишет в S3 напрямую, минуя бэкенд).
  • Объект адресуется ключом и обычно заменяется целиком; это не POSIX-файловая система и не место для частых точечных обновлений. Версионирование и lifecycle rules переводят старые объекты в дешёвый storage или удаляют их.
  • Pre-signed URL должен иметь короткий срок, ограниченный метод и непредсказуемый ключ. После загрузки сервис проверяет размер, MIME/сигнатуру и запускает асинхронное сканирование, прежде чем сделать объект публичным.
client ──metadata──► API ──signed URL──► client
client ─────────────upload──────────────► object storage
21

Поиск (Elasticsearch)

  • Полнотекстовый поиск, фасеты, агрегации (инвертированный индекс). Не основное хранилище — данные приходят из основной БД через CDC/очередь. Eventual consistency допустима.
  • Инвертированный индекс сопоставляет терм со списком документов; analyzer выполняет tokenization, нормализацию и stemming. Mapping нужно проектировать заранее: text подходит для полнотекстового ранжирования, keyword — для точных фильтров и агрегаций.
  • Поток записи обычно выглядит как DB → outbox/CDC → indexer → search index. События должны быть идемпотентны и версионированы, а периодический reconciliation восстанавливает пропуски. При падении поиска основной CRUD продолжает работать, возможно с упрощённым поиском.

⚠️ Не используйте Elasticsearch как единственный источник истины для платежей или остатков: refresh/replication дают окно устаревания, а переиндексация — обычная эксплуатационная операция.

22

Геораспределение и latency

  • Размещайте сервисы и данные ближе к пользователям (мульти-регион), используйте CDN и GeoDNS / Anycast.
  • Скорость света даёт нижнюю границу latency: межконтинентальный round-trip ~100-200 мс. Внутри ДЦ — < 1 мс.
  • Trade-off: мульти-регион усложняет консистентность (геораспределённые записи → конфликты).
  • Сначала разделите чтения и записи: статические ответы и read replicas можно приблизить к пользователю, а записи оставить в одном home region, чтобы сохранить простой порядок и транзакции.
  • Active-active запись требует стратегии владения ключом или разрешения конфликтов; last-write-wins может потерять корректную запись из-за рассинхронизации часов. Для денег и уникального инвентаря лучше направить ключ к одному лидеру, приняв межрегиональную задержку.
user → nearest edge/read replica
write → home region leader → async replicas
23

SPOF и отказоустойчивость

  • Single Point of Failure — компонент, отказ которого валит систему. Устраняется дублированием: несколько инстансов за LB, репликация БД с failover, multi-AZ.
  • Паттерны устойчивости: health checks, retries с backoff, circuit breaker, graceful degradation (отдать кэш/частичный ответ), timeouts, bulkheads.
  • Проверяйте не только количество инстансов, но и общие зависимости: два приложения в одной AZ, один DNS, общий connection pool или ручной failover всё ещё создают SPOF.
  • Ретраи разрешены только для безопасных/идемпотентных операций и должны иметь exponential backoff, jitter и общий deadline. Circuit breaker ограничивает вызовы к больной зависимости, bulkhead не даёт ей занять все воркеры, а graceful degradation сохраняет критический путь без необязательной функции.

⚠️ Реплика без регулярно проверяемого failover — это копия, а не отказоустойчивость. Проводите game day и измеряйте реальные RTO/RPO.

24

Метрики масштаба (порядки величин)

Грубые ориентиры для оценок (порядок, не точные числа):

Компонент Примерная ёмкость
1 app-сервер ~1 000–10 000 QPS (зависит от логики)
1 БД (запись) ~1 000–10 000 writes/сек
1 read-реплика десятки тысяч простых чтений/сек
Redis (один инстанс) ~100 000+ ops/сек
Строка реляц. БД сотни байт – единицы КБ
Сетевой round-trip в ДЦ < 1 мс
Чтение с SSD ~100–200 мкс
Межрегиональный RTT ~50–150 мс
25

3.1 URL Shortener (TinyURL)

1. Требования. Функциональные: сократить длинный URL в короткий; редирект по короткому ключу на оригинал; (опц.) кастомный alias, TTL. Вне скоупа: аналитика кликов, аккаунты. NFR: очень read-heavy (редиректов гораздо больше создания); низкая latency редиректа; высокая доступность; короткие ссылки уникальны и не угадываемы; eventual consistency допустима для редиректа.

2. Масштаб. 100M новых ссылок/мес → ~40 writes/сек. Read:write ≈ 100:1 → ~4000 reads/сек, пик ~10k. Хранение: 100M × ~500 B = 50 ГБ/мес → ~6 ТБ за 10 лет. Длина ключа: алфавит base62 (a-zA-Z0-9). 62⁷ ≈ 3.5 триллиона — 7 символов хватает надолго.

3. API.

POST /api/v1/shorten {long_url, custom_alias?, ttl?} -> {short_url}
GET  /{key}          -> 301/302 -> long_url

301 (permanent) кэшируется браузером навсегда → меньше нагрузки, но теряем аналитику; 302 (temporary) → каждый клик идёт на сервер. Выбор обсудить.

4. Данные. Таблица urls(key PK, long_url, created_at, expires_at, creator_id). Паттерн доступа — точечный lookup по ключу → отлично ложится на key-value NoSQL (DynamoDB/Cassandra) для масштаба, либо Postgres с индексом по key для начала. Шардирование по key (хэш) — равномерно.

5. Генерация ключа (ключевая часть 🟡):

  • Вариант A — хэш (MD5/SHA от URL, взять первые 7 символов base62): просто, но коллизии → проверять и при коллизии добавлять соль/перехэшировать. Одинаковый URL даёт один ключ (можно считать плюсом или минусом).
  • Вариант B — счётчик + base62: глобальный автоинкремент → кодируем в base62. Гарантированно без коллизий, короткие ключи. Минус: единый счётчик — bottleneck/SPOF. Решение — диапазоны ID (ticket/range server раздаёт блоки по 1000 каждому app-серверу) или распределённый генератор (Snowflake/ZooKeeper). Ключи предсказуемы → можно перемешивать.
  • 💡 Обычно предлагаю B с раздачей диапазонов — масштабируется и без коллизий.

6. Архитектура.

Client -> LB -> App Servers -> [Redis cache] -> [KV DB (sharded)]
                            -> [ID/range service] (для записи)

Редирект: app смотрит в Redis (cache-aside, TTL), при промахе — БД, кладёт в кэш, отдаёт 302.

7. Узкие места.

  • Чтение — главный объём. Решается кэшем (горячие ссылки) + read-репликами/широким шардированием. Hit rate высокий, т.к. распределение кликов следует степенному закону (популярные ссылки).
  • Запись — раздача ID-диапазонов убирает контеншн.
  • SPOF — дублировать LB, ID-сервис, реплицировать БД.
  • Очистка expired-ссылок — фоновый job / TTL в БД.
26

3.2 Лента новостей (News Feed)

1. Требования. Функциональные: пользователь видит ленту постов от тех, на кого подписан, отсортированную (по времени/релевантности); публикация поста; подписки. NFR: read-heavy; низкая latency загрузки ленты (это hot path); eventual consistency ок (задержка появления поста в пару секунд приемлема); высокая доступность.

2. Масштаб. Допустим 300M DAU, каждый открывает ленту ~10 раз/день → 3B чтений/день ≈ 35k QPS, пик ~70k. Постов: 100M/день. Среднее число подписок ~ сотни; у звёзд — десятки миллионов фолловеров (важный спецслучай).

3. API.

GET  /v1/feed?cursor=...&limit=20      -> [posts]
POST /v1/posts {content, media_ids}    -> {post_id}
POST /v1/follow {target_user_id}

Пагинация — cursor-based (по post_id/timestamp), не offset.

4. Данные.

  • posts(post_id, author_id, content, media, created_at)
  • follows(follower_id, followee_id)
  • feed_cache (precomputed): per-user список post_id (Redis list / sorted set). SQL для графа подписок и постов; Redis для материализованных лент.

5. Fan-out — ключевое решение 🔴:

  • Fan-out on write (push): при публикации поста сразу записываем его id в ленты (Redis) всех подписчиков. Чтение ленты → быстрый GET готового списка. Плюс: молниеносное чтение. Минус: дорогая запись для популярных авторов («fan-out problem»: пост звезды с 50M фолловеров = 50M записей).
  • Fan-out on read (pull): ленту собираем в момент запроса — берём посты всех, на кого подписан, мержим. Плюс: дешёвая запись. Минус: дорогое и медленное чтение, особенно при многих подписках.
  • Гибрид (правильный ответ): обычные пользователи — push; знаменитости (порог по числу фолловеров) — pull. При чтении мержим precomputed-ленту с «живыми» постами звёзд. 💡 Так делают реальные соцсети.

6. Архитектура.

Post -> API -> posts DB -> [Queue] -> Fan-out workers -> Redis feed lists
Read feed -> API -> Redis (готовая лента) + pull посты celebrity -> merge -> rank

Fan-out — асинхронно через очередь, чтобы не блокировать публикацию.

7. Ранжирование и узкие места.

  • Ранжирование: хронология просто; ML-ранжирование (релевантность) — отдельный сервис со скорами; на собесе достаточно упомянуть.
  • Узкие места: fan-out звёзд (→ гибрид), память Redis (хранить только N последних post_id на пользователя, остальное — pull из БД), горячие ключи.
  • Отказоустойчивость: feed-кэш можно перестроить из БД; реплики Redis.
27

3.3 Чат / мессенджер (WhatsApp-like)

1. Требования. Функциональные: 1-на-1 сообщения, групповые чаты, статусы доставки (sent/delivered/read), presence (онлайн/был в сети), история. NFR: низкая latency доставки; высокая доступность; durability сообщений (не терять); порядок сообщений в чате; масштаб подключений.

2. Масштаб. 500M DAU, 40 сообщений/день → 20B сообщений/день ≈ 230k msg/сек. Одновременных подключений — сотни миллионов. Хранение: 20B × ~200 B ≈ 4 ТБ/день.

3. API / протокол. Не классический REST для доставки — нужна двусторонняя связь: WebSocket (или MQTT) для live, плюс HTTP для истории и регистрации.

WS: connect; send {chat_id, msg}; receive {msg}; ack {msg_id}
GET /v1/chats/{chat_id}/messages?cursor=...

4. Данные.

  • messages(chat_id, msg_id, sender_id, content, created_at, status) — партиционирование/шард по chat_id, сортировка по времени. Wide-column (Cassandra) идеально: огромная запись, доступ по ключу чата, time-series. msg_id — Snowflake (монотонный → порядок).
  • chats, chat_members, user_presence.

5. Доставка — ключевая часть 🔴:

  • Пользователи держат постоянное WebSocket-соединение с gateway-серверами (stateful). Нужен реестр: какой пользователь на каком gateway → хранить в Redis (user_id -> gateway_id).
  • Отправка: A пишет в свой gateway → сервис сообщений сохраняет в БД (durability) → ищет gateway получателя B → пушит по его WS. B шлёт ack → статус delivered.
  • B оффлайн: сообщение лежит в БД/очереди; при подключении B запрашивает недоставленные (по последнему seen msg_id). Можно push-нотификация (APNs/FCM).
  • Группы: fan-out сообщения членам (для больших групп — как fan-out ленты).

6. Presence. Heartbeat по WS каждые N секунд → обновляем last_seen в Redis с TTL. Нет heartbeat → offline. Не пушить presence всем подряд — только активным наблюдателям.

7. Узкие места.

  • Миллионы постоянных соединений: много gateway-серверов, connection LB (L4, sticky). Каждый сервер держит ~десятки-сотни тысяч соединений.
  • Маршрутизация между gateway → Redis-реестр + pub/sub.
  • Порядок и идемпотентность: client-generated msg_id для dedup при ретраях.
  • Durability: писать в БД до подтверждения отправителю.
28

3.4 Rate Limiter

1. Требования. Ограничить число запросов на клиента (user/IP/API-key) за период: например 100 req/min. NFR: низкие накладные расходы и latency; точность; работа в распределённой среде (много инстансов); fail-open или fail-closed при сбое стораджа (обсудить).

2. Масштаб. Срабатывает на каждый запрос → должен быть очень быстрым (< 1 мс). При 100k QPS — 100k проверок/сек.

3. API. Обычно middleware/часть API Gateway. allow(key) -> bool. При отказе → HTTP 429 + заголовки X-RateLimit-Remaining, Retry-After.

4. Алгоритмы (ключевая часть 🟡):

  • Fixed window counter: счётчик на окно (минуту). Просто, но всплеск на границе окон (до 2× лимита).
  • Sliding window log: храним timestamp каждого запроса, считаем за последние 60 сек. Точно, но дорого по памяти.
  • Sliding window counter: интерполяция между текущим и предыдущим окном. Хороший баланс точности и памяти. Частый выбор.
  • Token bucket: бакет наполняется N токенов/сек, запрос берёт токен; пустой → отказ. Допускает burst до размера бакета. Очень популярен (гибкий).
  • Leaky bucket: запросы в очередь, обрабатываются с постоянной скоростью. Сглаживает трафик.

5. Распределённая реализация 🔴:

  • Состояние общее для всех инстансов → Redis (быстро, атомарно). Счётчик/токены по ключу rl:{user}:{window} с TTL.
  • Атомарность — Lua-скрипт (read-check-write одной операцией) или INCR+EXPIRE. Без атомарности — гонки и перерасход лимита.
  • Trade-off латентности: сетевой вызов в Redis на каждый запрос. Оптимизация — локальный кэш + периодическая синхронизация (немного жертвуем точностью).
  • При недоступности Redis: fail-open (пропускать, чтобы не уронить сервис) vs fail-closed (блокировать) — решение по требованиям.

6/7. Узкие места. Redis как горячая точка → шардировать по ключу клиента; реплики. Hot key одного крупного клиента — отдельный шард / local limiting.

29

3.5 Хранение и раздача файлов / видео

1. Требования. Функциональные: загрузка файла/видео, хранение, скачивание/стриминг, (для видео) транскодирование в разные качества. NFR: durability (не терять файлы); высокая пропускная способность; низкая latency отдачи глобально; большой объём.

2. Масштаб. Допустим 1M загрузок/день, средний файл 5 МБ → 5 ТБ/день нового контента; чтений в десятки раз больше → терабиты bandwidth → CDN обязателен.

3. API.

POST /v1/files/initiate {filename, size, content_type} -> {upload_id, presigned_url}
PUT  <presigned_url>  (клиент грузит напрямую в S3, multipart для больших)
POST /v1/files/complete {upload_id} -> {file_id, url}
GET  /v1/files/{file_id} -> 302 -> CDN URL

4. Данные.

  • Метаданные в БД: files(file_id, owner, name, size, s3_key, status, created_at) — реляционная БД ок.
  • Сам контент — blob storage (S3), не БД. Большие файлы — multipart upload (чанки, параллельно, докачка).

5. Архитектура (ключевая часть 🟡):

Client --presigned PUT--> S3 (origin)
       <--metadata-- App Server -- DB
S3 --event--> Queue --> Transcoding Workers --> S3 (variants 240p/720p/1080p)
Client --GET--> CDN (edge) --miss--> S3
  • Pre-signed URL: бэкенд выдаёт временный URL, клиент пишет прямо в S3, минуя app-серверы (не гоняем терабайты через бэкенд).
  • Транскодирование видео: загрузка триггерит событие → очередь → пул воркеров режет на сегменты и кодирует в несколько качеств/битрейтов (для адаптивного стриминга HLS/DASH). Асинхронно, идемпотентно.
  • CDN раздаёт и статику, и видеосегменты — главный слой чтения.

6/7. Узкие места и устойчивость.

  • Bandwidth чтения → CDN снимает основную нагрузку с origin.
  • Durability → S3 реплицирует объекты по AZ; критичное — кросс-регион репликация.
  • Транскодирование — CPU-тяжёлое → масштабируем воркеры по длине очереди (autoscaling); ретраи на сбоях; статус задачи в БД.
  • Большие загрузки → multipart + докачка + проверка контрольной суммы.
  • Дедупликация по хэшу содержимого (одинаковые файлы хранить раз).

Источники

Источники и редакционная политика

Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.

От чтения к воспроизведению

Отрепетируйте полный цикл интервью.

RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.

Начать подготовку

Продолжить подготовку

Библиотека собеседований RecallDeck

Подробные русские ответы, разборы этапов найма и планы подготовки для российского IT-рынка.

RSS