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

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

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

27 мин чтения29 подробных ответовОбновлено
Главная мысль

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

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

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

Проверка по первоисточникам опубликована для 29 из 29 ответов. Дата и область проверки указаны у каждого проверенного ответа.

01

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

Короткий ответ: До архитектуры уточните функции, пользователей и границы задачи, затем нагрузку, задержку, доступность, согласованность и сохранность данных. Явно согласуйте предположения: именно требования определяют допустимые компромиссы.

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

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

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

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

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

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

Источники: Документация 1, Документация 2.

02

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

Короткий ответ: Оцените средний и пиковый QPS, соотношение чтений и записей, объём записи, трафик и срок хранения. Проверяйте единицы и объясняйте, какое архитектурное решение следует из каждой оценки; пиковый множитель задаётся предположением или измерениями.

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

Что оценить:

  • 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 ТБ.

💡 Вывод из чисел сразу формулируйте: «По этим оценкам начнём с одной БД, затем проверим реальные запросы и целевую задержку, прежде чем добавлять кэш или реплики.».

Источники: Документация 1.

03

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

Короткий ответ: Определите операции 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

Связывай ключ идемпотентности с вызывающей стороной и содержимым запроса, а результат сохраняй вместе с бизнес-эффектом. Два одновременных повтора не должны проходить раздельную проверку ключа. Срок хранения задаёт поддерживаемое окно повторов.

Источники: Документация 1.

04

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

Короткий ответ: Выбирайте модель и базу по запросам и инвариантам: сущностям, ключам, связям и атомарным изменениям. Проверяйте гарантии конкретной БД и режима чтения; названия SQL или NoSQL сами по себе не определяют согласованность.

Начни с доступа к данным и инвариантов. Перечисли сущности, ключи, связи, частые запросы и атомарные изменения до выбора БД.

  • Реляционная БД полезна для JOIN, ограничений и транзакций над несколькими строками. Задай изоляцию, репликацию и политику чтения: слово SQL само по себе не гарантирует актуальность каждого чтения.
  • Key-value и wide-column подходят для известных запросов по ключу и распределённой нагрузки. Гарантии согласованности зависят от продукта и операции: DynamoDB поддерживает strongly consistent reads для определённых ресурсов, но глобальные вторичные индексы читаются eventual.
  • Документная модель удобна, когда вложенные данные обычно читаются и обновляются вместе. Гибкая схема всё равно требует валидации и миграций.
  • Redis подходит для кэша, счётчиков и временной координации; прежде чем сделать его источником истины, определи сохранность и восстановление.

Для сокращателя ссылок начни с key → destination, owner, expiry и ограничения уникальности ключа. Шардирование добавляй, когда его оправдывают ёмкость, изоляция или география; объясни partition key, hot keys и операции между разделами.

Источники: Документация 1.

05

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

Короткий ответ: Покажите минимальный путь запроса через нужные компоненты и назовите источник истины. Для связей опишите протокол, таймауты и синхронность, а для состояния — владельца и восстановление. Пройдите нормальный сценарий и один отказ.

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

[Client] -- DNS lookup: адрес; HTTP-запрос --> [Load Balancer] -> [API / App Servers (stateless)]
                                              |-> [Cache (Redis)]
                                              |-> [Database (+ read replicas)]
                                              |-> [Message Queue] -> [Workers]
                                         [CDN] -> [Blob Storage (S3)]

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

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

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

Источники: Документация 1, Документация 2.

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

Короткий ответ: Балансировщик распределяет трафик, reverse proxy пересылает запросы от имени серверов; один компонент может выполнять обе роли. L4 работает с транспортом, L7 понимает HTTP. Выбирайте политику, health checks, завершение соединений и резервирование под нагрузку.

Балансировщик распределяет трафик; reverse proxy пересылает запросы от имени серверов. Один компонент часто выполняет обе роли.

L4-балансировщик выбирает соединения по транспортным данным. L7-прокси понимает HTTP, маршрутизирует по пути или заголовкам, завершает TLS и применяет политики приложения. L4 не всегда быстрее: результат зависит от повторного использования соединений, TLS, реализации и нагрузки.

Выбери политику вроде round-robin или least-connections, определи health checks и завершение старых соединений при деплое. Несколько экземпляров приложения не спасают, если сам балансировщик — единственная точка отказа.

Не храни долговечную сессию только в памяти одного процесса: используй подходящее общее хранилище или безопасно спроектированные токены. Redis — вариант, не обязательное условие. WebSocket остаётся на выбранном gateway до переподключения; восстановление соединения и состояния нужно спроектировать отдельно.

Источники: Документация 1, Документация 2.

09

Кэширование

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

Кэш хранит переиспользуемую копию ближе к читателю, чтобы уменьшить задержку или нагрузку на источник. Сначала назови источник истины и допустимую устарелость.

  • Cache-aside: чтение кэша; при промахе — БД и заполнение кэша. После записи обнови или инвалидируй нужный ключ.
  • Write-through: запись проходит через слой, обновляющий основное хранилище и кэш. Это добавляет работу к записи; согласованность всё равно зависит от атомарности, сбоев и конкурентных чтений.
  • Write-back: запись подтверждается в кэше до асинхронного сохранения. Если подтверждённые данные нельзя терять, нужны долговечный буфер и восстановление.

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

При stampede объединяй одновременные заполнения одного ключа, ограничивай ожидание, добавляй TTL jitter или раннее обновление. Измеряй hit rate и хвостовые задержки: кэш не ускоряет любую нагрузку автоматически.

Источники: Документация 1, Документация 2.

10

CDN

Короткий ответ: CDN обслуживает кешируемый контент ближе к пользователю и снижает нагрузку на origin. Настройте ключ кеша, срок свежести и обновление; персональные ответы нельзя случайно объединять в общий кеш. Измеряйте попадания и промахи отдельно.

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

⚠️ Промах кэша всё равно требует обращения к origin; маршрутизация и переиспользование соединений могут менять задержку, поэтому измеряй hits и misses отдельно. защищайте origin от stampede и наблюдайте hit ratio отдельно по типам контента.

immutable сообщает кэшу, что свежий ответ не изменится; сама директива не задаёт срок свежести. Документация.

Источники: Документация 1, Документация 2.

11

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

Короткий ответ: Репликация создаёт копии данных для чтения и восстановления; партиционирование делит набор данных, а шардинг распределяет части по узлам. Выбор ключа влияет на горячие разделы и межузловые операции; гарантии реплик зависят от протокола и режима чтения.

  • Репликация (read replicas): копии данных помогают масштабировать чтение и восстанавливаться. В схеме primary/async replica запись идёт в primary, а чтение реплик может отставать; выбирай источник чтения по требованиям свежести.
  • Шардирование (sharding): горизонтальное разбиение данных по нескольким БД по ключу. Масштабирует запись и объём. Стратегии: по хэшу ключа, по диапазону, по гео. Минусы: сложные JOIN/транзакции между шардами, ребалансировка, hot shards.
  • Партиционирование — разделение данных на части, например по диапазону дат или списку. БД может делить таблицу внутри себя; sharding размещает разделы по узлам.
  • Выбор ключа шардирования критичен: должен равномерно распределять и совпадать с паттерном доступа.

Описание реплик относится к распространённой схеме primary/async replica, а не ко всем БД. Синхронная репликация и правила чтения меняют гарантии. Partitioning — общее разделение данных; sharding обычно означает размещение разделов на независимых узлах БД.

Источники: Документация 1.

12

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

Короткий ответ: Очередь отделяет приём работы от выполнения и сглаживает всплески, но не убирает нагрузку. Определите доставку, идемпотентность, повторы, DLQ и backpressure; атомарное изменение своей БД не делает внешний побочный эффект автоматически однократным.

  • 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 и явная политика отбрасывания или деградации.

Транзакция БД может атомарно объединить dedup и изменение этой БД, но не произвольный внешний API отправки писем или оплаты. Для внешнего эффекта нужны собственная идемпотентность или восстанавливаемая передача работы.

Источники: Документация 1, Документация 2.

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-теорема и консистентность

Короткий ответ: Во время сетевого разделения нельзя одновременно гарантировать линейризуемость и успешный ответ на каждый запрос к неотказавшему узлу. Выбор делается для конкретных операций и инвариантов; CAP не сводится к постоянному выбору любых двух букв.

CAP описывает поведение при сетевом разделении. Partition мешает части узлов обмениваться сообщениями. В этой ситуации нельзя одновременно гарантировать линеаризуемость операций и успешный ответ на каждый запрос к каждому неотказавшему узлу.

  • C здесь — единая история операций, согласованная с порядком реального времени, а не общее понятие правильного бизнес-результата.
  • A означает возможность успешно завершать запросы к неотказавшим узлам; отказ ради согласованности жертвует CAP-доступностью этой операции.
  • Система, сохраняющая согласованность, может остановить записи в меньшей части разделённого кластера. Система, выбирающая доступность, может принять расходящиеся записи и согласовать их после восстановления связи.

Выбирай гарантии для инварианта и операции, а не навешивай CP/AP на все БД или банковские системы. Важны конфигурация и режим чтения: например, DynamoDB предлагает разные режимы согласованности.

PACELC добавляет вопрос: какой компромисс latency/consistency создаёт репликация без partition? Ни одна аббревиатура не заменяет проверку протокола, поведения при отказах и документированных гарантий.

Источники: Документация 1, Документация 2.

15

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

Короткий ответ: Линейризуемость связывает операции с единым порядком, согласованным с реальным временем; eventual consistency обещает сходимость при прекращении обновлений и обмене данными. W + R > N обеспечивает пересечение кворумов фиксированного набора реплик, но само по себе не доказывает линейризуемость.

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

Eventual consistency обещает сходимость, когда обновления прекращаются и реплики могут обменяться нужной информацией. Само по себе это не задаёт максимальную устарелость, монотонность чтений или сохранение каждого конкурентного обновления: важны правила разрешения конфликтов.

Пересечение кворумов: при фиксированном наборе N реплик запись в W и чтение из R пересекаются, если W + R > N. При N=3, W=2, R=2 хотя бы одна реплика чтения входит в набор записи. Протокол всё равно должен выбрать нужную версию и обработать конкурентные или частично завершённые записи, сбои и изменения состава.

Ловушка: одно пересечение не доказывает линеаризуемость. Sloppy quorums, last-write-wins по времени и конкурентные писатели могут нарушить упрощённое обещание свежего чтения. Назови БД, consistency level и операцию; опирайся на её гарантии и проверяй сбои, от которых зависит инвариант.

Источники: Документация 1, Документация 2.

16

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

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

  • Идемпотентность: повтор операции имеет тот же предполагаемый эффект на состояние сервера (повторный 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

Ограничь область ключа аутентифицированным вызывающим. Локальная транзакция БД не включает внешнего платёжного провайдера: передавай ему стабильный ключ идемпотентности или используй долговечную восстанавливаемую передачу работы. Не повторяй вслепую операцию с неизвестным исходом.

Источники: Документация 1.

17

Rate Limiting

Короткий ответ: Rate limiter ограничивает нагрузку по пользователю, ключу, tenant или IP. Выберите окно или token bucket, атомарно обновляйте общий счётчик и возвращайте 429 при превышении. Заранее решите, что делать при недоступности хранилища лимитов.

  • 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 между сервисами.
  • 💡 На собесе: «Начал бы с модульного монолита, выделил бы в сервисы то, что нужно масштабировать независимо или что владеет отдельная команда».
  • Границу сервиса проводят вокруг бизнес-владения и данных, а не вокруг технического слоя. Выделение оправдано, когда модуль требует независимого релизного цикла, профиля масштабирования или команды; иначе сеть превращает простой вызов функции в контракт с таймаутами, ретраями и частичными отказами.

Монолит не объединяет всю работу в одну транзакцию автоматически, а микросервисы не гарантируют изоляцию сбоев. Границы транзакций, таймауты зависимостей и изоляцию ресурсов нужно проектировать в обеих архитектурах.

Источники: Документация 1.

19

API Gateway

Короткий ответ: API Gateway объединяет маршрутизацию и общие политики: проверку аутентификации, лимиты и TLS. Он не заменяет авторизацию на уровне ресурса в сервисе-владельце. Учитывайте таймауты, частичные отказы, наблюдаемость и резервирование самого 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)

Короткий ответ: Object storage хранит файлы по ключам; метаданные обычно остаются в БД. Клиент может загружать объект по ограниченной по времени подписанной ссылке, а читать через CDN. Проверяйте загруженный файл до публикации и не считайте object storage обычной файловой системой.

  • Объектное хранилище для файлов, картинок, видео, бэкапов. Дёшево, 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

Источники: Документация 1.

21

Поиск (Elasticsearch)

Короткий ответ: Поисковый индекс связывает термины с документами и поддерживает полнотекстовый поиск, фильтры и агрегации. В описанной схеме он обновляется из основной БД через CDC или очередь; нужны идемпотентные обновления, контроль версий и возможность перестроения.

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

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

Источники: Документация 1.

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 и отказоустойчивость

Короткий ответ: Ищите не только одинокие серверы, но и общие зависимости, способные остановить систему. Резервирование дополняйте проверенным failover, таймаутами, безопасными повторами и ограничением каскадных отказов. Измеряйте восстановление, а не только число реплик.

  • 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

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

Короткий ответ: Считайте с единицами: средний QPS — запросы за день / 86 400, трафик — QPS × размер ответа, объём — число записей × размер. Ёмкость конкретного сервера проверяется на целевой нагрузке и задержке; универсального числа QPS нет.

Считай с единицами и явными допущениями, затем измеряй. Универсального QPS для «одного Go-сервера», «одной БД» или «одного Redis» нет. Стоимость запроса, размер данных, индексы, durability, конкурентность, железо и целевые хвостовые задержки меняют ёмкость на порядки.

Полезная арифметика:

Величина Расчёт
Средние запросы/сек запросы/день ÷ 86 400
1 миллион запросов/день около 11,6 запроса/сек
Полоса полезных данных запросы/сек × байты/ответ
Сырые хранимые данные число записей × средний размер записи

Например, 1 000 ответов/сек по 10 КБ — около 10 МБ/сек, или 80 Мбит/сек, без накладных расходов протокола. В хранилище дополнительно учитывай индексы, реплики, бэкапы и срок хранения.

Коэффициент пика обозначай как допущение, а не закон. Измеряй типичную нагрузку при нужных p95/p99 и доле ошибок, оставляй резерв на отказ и указывай окружение результата. Задержки сети и диска — тоже измерения, не гарантии названия компонента.

Источники: Документация 1, Документация 2.

25

URL Shortener (TinyURL)

Короткий ответ: Для сокращателя ссылок храните уникальный ключ, адрес назначения, владельца и срок действия. Выберите предсказуемые или случайные ключи по требованиям, атомарно обрабатывайте коллизии и проверяйте срок при чтении. Кеш и редиректы должны учитывать свежесть и отзыв ссылки.

Проектируй сокращатель ссылок от гарантий. Уточни создание, редиректы, custom alias, срок жизни и защиту от злоупотреблений. Реши, должны ли ссылки быть трудно угадываемыми: уникальность сама этого не даёт.

Пример масштаба: 100 млн новых ссылок в месяц — около 40 записей/сек. При 100 редиректах на создание — около 4 000 чтений/сек в среднем. По 500 байт на запись — 50 ГБ/месяц сырых данных без индексов, реплик и бэкапов. Это допущения упражнения.

API и данные: POST /urls создаёт ссылку; GET /{key} ищет её и перенаправляет. Храни уникальный primary key вместе с destination, owner и expiry. Проверяй допустимые схемы URL и срок жизни при чтении: фоновое удаление не обеспечивает точного истечения.

Ключи: счётчик в base62 компактен и уникален при корректной выдаче непересекающихся диапазонов, но предсказуем. Если нужна неугадываемость, генерируй случайные ключи криптографическим генератором и повторяй при конфликте UNIQUE. Префикс хэша тоже сталкивается; проверка и вставка должны быть атомарны. Семь base62-символов дают около 3,5 трлн вариантов, а не гарантию отсутствия коллизий.

Чтения: начни с индексированного запроса к БД, добавляй кэш по измерениям и ограничивай его TTL сроком жизни ссылки. Новая ссылка должна работать сразу даже при задержке части реплик.

Кэш редиректов: 301 может кэшироваться эвристически; 302 тоже кэшируется при явных условиях свежести. Ни «301 навсегда», ни «302 всегда доходит до origin» не гарантированы. Выбирай статус вместе с Cache-Control, учитывая изменение назначения, expiry и аналитику.

Источники: Документация 1, Документация 2.

26

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

Короткий ответ: В новостной ленте главный выбор — собирать публикации при чтении или заранее раскладывать их по лентам подписчиков. Push ускоряет чтение ценой fan-out, pull удешевляет публикацию ценой чтения; гибрид помогает учитывать авторов с очень большой аудиторией.

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-ленту с «живыми» постами звёзд. Выбирай разделение по измеренной стоимости fan-out и требованиям свежести.

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.

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

Источники: Документация 1.

27

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

Короткий ответ: Для чата разделите постоянные соединения, долговременное хранение и доставку с подтверждениями. Сначала сохраняйте сообщение, затем доставляйте; учитывайте повторы и офлайн-получателей. Строгий порядок внутри чата требует отдельного протокола, а не только ID с временной меткой.

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. Snowflake-подобный ID примерно упорядочен по времени, но не гарантирует причинный или общий порядок между писателями. Для строгого порядка чата выдавай последовательность через одного владельца или подходящий протокол БД.
  • 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 запрашивает недоставленные (после последней непрерывно подтверждённой серверной последовательности чата). Можно 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 при реальном расходе памяти, heartbeat и нагрузке сообщениями.
  • Маршрутизация между gateway → Redis-реестр + pub/sub.
  • Порядок и идемпотентность: client-generated msg_id для dedup при ретраях.
  • Durability: писать в БД до подтверждения отправителю.

Разделяй клиентский request ID для dedup и серверную последовательность чата для порядка. При необходимости храни delivery/read ack по получателю и устройству: одного status недостаточно для группы. Число одновременных соединений — отдельное допущение, оно не следует только из DAU.

Redis Pub/Sub доставляет уведомления at-most-once и не может быть единственной записью доставки. Сохраняй сообщения и курсоры получателей долговечно; восстанавливай пропуски из журнала при переподключении или через явный retry/sync. Для нескольких устройств отслеживай каждое устройство/сессию, а не перезаписывай единственный слот user-to-gateway. Документация.

Источники: Документация 1, Документация 2.

28

Rate Limiter

Короткий ответ: Проектируя распределённый rate limiter, задайте ключ, квоту, допустимый всплеск и поведение при отказе. Выберите fixed/sliding window или token bucket и обеспечьте атомарную проверку с обновлением общего состояния. Объясните баланс точности, памяти и задержки.

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.

У fixed-window счётчика устанавливай expiry при создании ключа, а не продлевай каждым запросом. Задержка реплик, failover и локальные счётчики могут допускать превышение: задай границу либо авторитетную схему для строгого лимита. Задержка менее миллисекунды — измеряемая цель, не гарантия Redis или сети.

Источники: Документация 1.

29

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

Короткий ответ: Храните метаданные файлов в БД, содержимое — в object storage, а выдачу организуйте через CDN. Для больших файлов используйте возобновляемую загрузку; транскодирование вынесите в очередь и воркеры. До публикации проверьте доступ, размер и содержимое.

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

2. Масштаб. Допустим 1M загрузок/день, средний файл 5 МБ → 5 ТБ/день нового контента; при 20 полных скачиваниях на загрузку получится 100 ТБ/день, около 9,3 Гбит/сек в среднем. Пик и география определяют схему раздачи; CDN может уменьшить нагрузку на origin.

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; оцени cross-region replication для восстановления при региональном сбое.
  • Транскодирование — CPU-тяжёлое → масштабируем воркеры по длине очереди (autoscaling); ретраи на сбоях; статус задачи в БД.
  • Большие загрузки → multipart + докачка + проверка контрольной суммы.
  • Дедупликация по хэшу содержимого (одинаковые файлы хранить раз).

Авторизуй загрузку и скачивание, проверяй итоговые размер, checksum и состояние объекта до ready. S3 events могут дублироваться и приходить не по порядку: воркеры делают dedup по объекту/версии и сохраняют переходы состояния. Multi-AZ durability зависит от класса хранения и не свойственна всем классам S3.

Источники: Документация 1, Документация 2.

Источники

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

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

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

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

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

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

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