Стройте ответ вокруг владения состоянием, лимитов, отказа и восстановления. Определение становится инженерным ответом только в конкретном production-сценарии.
Вопросы и ответы
29 подробных ответов
01Вертикальное vs горизонтальное масштабирование?
junior
Короткий ответ: Вертикальное (scale up) — добавляем ресурсы одной машине (CPU/RAM). Горизонтальное (scale out) — добавляем больше машин и распределяем нагрузку между ними.
Подробно:
- Вертикальное (scale up/down): взять сервер помощнее. Просто (код не меняется), но есть физический потолок, дорого на верхней границе, и сама машина остаётся single point of failure. Часто требует downtime при апгрейде.
- Горизонтальное (scale out/in): добавляем узлы за балансировщиком. Практически безграничный потолок, отказоустойчивость (узел упал — остальные живут), линейная стоимость. Но требует stateless-архитектуры, балансировки, распределённого состояния, усложняет консистентность и дебаг.
- На практике сначала выжимают вертикальное (быстро и дёшево до определённого предела), затем переходят на горизонтальное. БД масштабировать горизонтально труднее всего (отсюда шардирование/реплики).
⚠️ Ловушка: «Просто добавим серверов» не работает, если сервис stateful (хранит сессии/файлы в памяти/на локальном диске). Сначала сделай сервис stateless, потом масштабируй.
02Stateless vs stateful сервисы?
middle
Короткий ответ: Stateless-сервис не хранит данные между запросами локально — любой запрос может обработать любой инстанс. Stateful хранит состояние (сессию, кэш, файлы) в себе, что привязывает клиента к конкретному инстансу.
Подробно:
- Почему stateless легче масштабировать: инстансы взаимозаменяемы. Можно добавлять/убирать узлы, рестартовать, делать rolling deploy без потери данных. Балансировщик кидает запрос на любой узел. Не нужны sticky sessions.
- Где хранить состояние: выносим наружу в общее хранилище:
- сессии → Redis / Memcached / БД (или вообще JWT — состояние у клиента);
- файлы/загрузки → объектное хранилище (S3), не локальный диск;
- кэш → распределённый Redis;
- очереди задач → брокер (RabbitMQ/Kafka).
- Сам сервис становится «функцией»: вход → обработка → выход, без внутренней памяти между запросами.
💡 Stateless не значит «без состояния вообще» — состояние просто живёт во внешних системах, разделяемых всеми инстансами.
⚠️ Ловушка: скрытое состояние — in-memory кэш, локальные temp-файлы, in-process планировщики (cron внутри инстанса будет запущен N раз на N инстансах). Всё это ломает горизонтальное масштабирование.
03Балансировка нагрузки: L4 vs L7?
middle
Короткий ответ: L4-балансировщик работает на транспортном уровне (TCP/UDP, IP+порт), не смотрит в содержимое. L7 — на прикладном (HTTP), видит URL, заголовки, cookie и может маршрутизировать по ним.
Подробно:
- L4 (транспорт): быстрый, дешёвый по CPU, проксирует пакеты/соединения по IP и порту. Не понимает HTTP. Примеры: AWS NLB, IPVS, HAProxy в TCP-режиме.
- L7 (приложение): разбирает HTTP, умеет content-based routing (
/api→ один пул,/static→ другой), терминирует TLS, добавляет заголовки, делает retry/circuit-breaking. Дороже по ресурсам. Примеры: nginx, AWS ALB, Envoy, HAProxy в HTTP-режиме.
Алгоритмы балансировки:
- Round-robin — по кругу, поровну. Хорошо при одинаковых запросах/серверах.
- Weighted round-robin — с весами (мощный сервер получает больше).
- Least connections — на узел с наименьшим числом активных соединений. Хорошо для разной длительности запросов.
- IP hash — узел выбирается по хэшу IP клиента → один клиент всегда на один узел (примитивная «липкость»).
- Least response time / random two choices — продвинутые варианты.
Health checks: балансировщик периодически пингует узлы (active: HTTP /health; passive: смотрит на ошибки реального трафика). Нездоровый узел выводится из пула, чтобы трафик не шёл на мёртвый инстанс.
Sticky sessions (session affinity): клиента «приклеивают» к одному узлу (по cookie или IP hash), чтобы локальная сессия не терялась. Это костыль для stateful-сервисов — лучше сделать stateless + внешнее хранилище сессий.
⚠️ Ловушка: sticky sessions ломают равномерность нагрузки и отказоустойчивость — если узел упал, все приклеенные к нему клиенты теряют сессию. Не лечит проблему, а откладывает.
04Reverse proxy (nginx) vs load balancer?
middle
Короткий ответ: Это пересекающиеся, но разные роли. Reverse proxy — посредник перед сервером(ами): TLS, кэш, сжатие, маршрутизация. Load balancer — распределение нагрузки между несколькими бэкендами. Один продукт (nginx) умеет и то, и другое.
Подробно:
- Reverse proxy скрывает бэкенд от клиента и берёт на себя кросс-функции: терминацию TLS, gzip/brotli, кэширование статики и ответов, заголовки безопасности, rate limiting, отдачу статических файлов, буферизацию. Может стоять перед одним сервером.
- Load balancer — частный случай/функция: раскидывает запросы по пулу, делает health checks. Может быть L4 (без знания HTTP).
- Разница в фокусе: LB отвечает на вопрос «на какой из N узлов», reverse proxy — «как обработать/преобразовать запрос перед бэкендом». nginx/Envoy/HAProxy совмещают обе роли.
💡 На собеседовании: «nginx как reverse proxy + балансировщик L7 перед stateless-инстансами приложения, TLS терминируется на нём».
⚠️ Ловушка: не путай с forward proxy — тот стоит на стороне клиента и проксирует исходящий трафик клиента наружу (например, корпоративный прокси). Reverse proxy стоит на стороне сервера.
05Кэширование на всех уровнях?
middle
Короткий ответ: Кэш есть на каждом слое пути запроса: браузер → CDN → reverse proxy → приложение → БД. Чем ближе к клиенту, тем дешевле и быстрее, но тем сложнее инвалидация.
Подробно (слои сверху вниз):
- Браузер — HTTP-кэш по заголовкам
Cache-Control,ETag,Last-Modified. Самый дешёвый: запрос вообще не уходит. - CDN / edge — кэширует статику и кэшируемые ответы ближе к пользователю географически.
- Reverse proxy (nginx) — кэш ответов/страниц на стороне дата-центра, разгружает приложение.
- Приложение — in-memory (локальный, быстрый, но не разделяемый) и распределённый (Redis/Memcached — общий для всех инстансов). Кэширование результатов вычислений, сессий, агрегатов.
- БД — buffer pool/page cache, query plan cache, материализованные представления.
Стратегии: cache-aside (приложение само читает/пишет кэш — самая частая), write-through, write-behind, read-through. Ключевые проблемы: инвалидация, TTL, cache stampede (много промахов разом → защита через locks/SETNX), консистентность кэша с БД.
📎 Подробности по Redis (структуры, паттерны, eviction, persistence) — см. отдельный redis-файл.
⚠️ Ловушка: «Phil Karlton: в CS две сложные вещи — инвалидация кэша и именование». Кэширование решает чтение, но добавляет проблему устаревших данных. Не кэшируй то, что должно быть строго консистентным, без продуманной инвалидации.
06CDN: что это и зачем?
junior
Короткий ответ: Content Delivery Network — географически распределённая сеть edge-серверов, кэширующих контент ближе к пользователю, чтобы снизить латентность и разгрузить origin.
Подробно:
- Что кэширует: статику (картинки, CSS/JS, видео), а с edge-логикой — и динамические/API-ответы.
- Зачем: меньше RTT (контент физически ближе), разгрузка origin-сервера, защита от DDoS, TLS-терминация на краю, абсорбция пиков трафика.
- Edge: точки присутствия (PoP) по миру; запрос идёт на ближайший edge, при промахе — на origin, ответ кэшируется. Edge computing — выполнение лёгкого кода на краю (Cloudflare Workers, Lambda@Edge).
- Инвалидация — через purge/версионирование URL (хэш в имени файла →
app.a1b2c3.js).
⚠️ Ловушка: не клади приватные/персональные данные в публичный CDN-кэш без Cache-Control: private. Иначе один пользователь увидит закэшированный ответ другого.
07Зачем нужны очереди сообщений?
middle
Короткий ответ: Чтобы сделать взаимодействие асинхронным, развязать сервисы, сгладить пики нагрузки и повысить надёжность доставки работы.
Подробно — четыре главных мотива:
- Асинхронность: запрос вернул ответ сразу, тяжёлая работа (отправка email, генерация отчёта, обработка видео) идёт в фоне. Пользователь не ждёт.
- Развязка (decoupling): producer не знает про consumer и не зависит от его доступности/скорости. Можно деплоить, масштабировать, менять консьюмеров независимо.
- Сглаживание пиков (load leveling / buffering): всплеск трафика наполняет очередь, консьюмеры разгребают в своём темпе. БД/сервис не захлёбывается.
- Надёжность: сообщение сохранено в брокере; если консьюмер упал — сообщение не потеряется и будет обработано позже (с ack/retry).
Бонус: масштабирование консьюмеров (добавил воркеров — разгрёб быстрее), буфер между быстрым и медленным компонентом.
⚠️ Ловушка: очередь не бесплатна — добавляет инфраструктуру, eventual consistency, сложность отладки и требование идемпотентности консьюмеров. Не вставляй очередь там, где нужен синхронный ответ здесь и сейчас.
08RabbitMQ vs Kafka vs Redis/SQS?
senior
Короткий ответ: RabbitMQ — классический брокер (умная маршрутизация, сообщение удаляется после обработки). Kafka — распределённый лог событий (сообщения хранятся, читаются по offset, отлично для стриминга/больших объёмов). Redis (Streams/Pub-Sub) — простой и быстрый, в одном процессе с кэшем. SQS — managed-очередь в AWS, минимум эксплуатации.
Подробно — broker vs log (ключевое различие):
- Брокер (RabbitMQ, SQS): push/pull сообщения, брокер ведёт состояние доставки; после ack сообщение удаляется. Богатая маршрутизация (exchanges, routing keys, priority). Очередь — обычно один потребитель на сообщение (work queue).
- Лог (Kafka): сообщения append-only пишутся в партиции и хранятся (по retention/размеру), даже после чтения. Консьюмеры держат свой offset — могут перечитать историю, разные группы читают независимо. Порядок гарантируется внутри партиции. Масштабируется на огромный throughput.
Когда что:
- RabbitMQ — таски, RPC, сложная маршрутизация, относительно умеренный объём, нужны per-message гарантии и приоритеты.
- Kafka — event sourcing, аналитика/стриминг, лог изменений, миллионы сообщений/сек, нужна перечитка и многим потребителям одно событие.
- Redis Streams — лёгкие очереди, когда Redis уже есть; меньше durability-гарантий, проще.
- SQS — «не хочу администрировать брокер», AWS-стек; standard (at-least-once, без строгого порядка) или FIFO (порядок + дедуп).
⚠️ Ловушка: Kafka — не «лучшая очередь», а другой инструмент. Для классической work-queue с ack/retry/приоритетами RabbitMQ удобнее. Kafka силён там, где нужны лог, перечитка и стриминг. Выбор по задаче, а не «модно».
09Гарантии доставки: at-most- / at-least- / exactly-once?
senior
Короткий ответ: at-most-once — максимум один раз (возможна потеря). at-least-once — минимум один раз (возможны дубли). exactly-once — ровно один раз (дорого и условно, обычно достигается at-least-once + идемпотентность/дедупликация).
Подробно:
- At-most-once: отправил и забыл, без ретраев. Сообщение может потеряться, но дублей нет. Подходит для метрик/телеметрии, где потеря не критична.
- At-least-once: ack + ретраи. Если ack не дошёл — повтор. Дубли возможны (обработали, упали до ack → переотправка). Самый распространённый дефолт.
- Exactly-once: «святой грааль». В чистом виде в распределёнке невозможен из-за сбоев сети; на практике эмулируется: at-least-once доставка + идемпотентный консьюмер или дедупликация по message_id. Kafka даёт exactly-once внутри своей экосистемы (транзакции producer→topic→consumer-offset), но при выходе наружу (запись в стороннюю БД) снова нужна идемпотентность.
Идемпотентные консьюмеры: обработка одного и того же сообщения N раз даёт тот же эффект, что и один раз. Реализация: уникальный message_id → хранилище обработанных ID / UPSERT по бизнес-ключу / условные апдейты. Это правильный способ «прожить» дубли.
Dead Letter Queue (DLQ): сообщения, которые не удалось обработать после N ретраев (или с истёкшим TTL, или невалидные), уходят в отдельную очередь. Там их разбирают вручную/алертом, не теряя и не блокируя основную очередь «ядовитыми» сообщениями (poison messages).
⚠️ Ловушка: «у нас exactly-once» почти всегда означает «at-least-once + идемпотентность». Если консьюмер не идемпотентен, любые ретраи (а они будут) приведут к дублированным эффектам: два списания денег, два письма.
10Pub/Sub vs очередь (work queue)?
middle
Короткий ответ: В work queue сообщение получает один консьюмер (конкурирующие воркеры делят работу). В pub/sub сообщение получают все подписчики (broadcast).
Подробно:
- Work queue (point-to-point): N воркеров на одной очереди делят сообщения — каждое уходит ровно одному. Цель — распараллелить обработку. Пример: очередь задач Celery, обработка заказов.
- Pub/Sub (publish-subscribe): publisher шлёт в topic, каждый подписчик получает копию. Цель — оповестить много независимых потребителей. Пример: событие «заказ создан» → биллинг, склад, аналитика, нотификации, каждый делает своё.
- В RabbitMQ это настраивается через exchange (fanout = pub/sub, direct/topic-routing). В Kafka каждая consumer group получает все сообщения (pub/sub между группами), а внутри группы партиции делятся между консьюмерами (work queue).
⚠️ Ловушка: если повесить несколько разных по смыслу обработчиков на одну work-queue, они начнут «воровать» сообщения друг у друга. Для broadcast нужен fanout/topic, а не общая очередь.
11Celery (Python) — как устроен?
middle
Короткий ответ: Celery — фреймворк для фоновых задач в Python. Задачи (@app.task) кладутся в брокер (RabbitMQ/Redis), воркеры их забирают и выполняют; результат опционально пишется в result backend.
Подробно:
- Компоненты:
- Задача (task): функция, помеченная
@app.task, вызывается через.delay()/.apply_async()— это публикует сообщение в брокер. - Брокер (broker): транспорт сообщений (RabbitMQ предпочтителен, Redis популярен для простоты).
- Воркер (worker): процесс, который слушает очередь и исполняет задачи; масштабируется числом процессов/потоков и количеством воркеров.
- Result backend: хранилище результатов/статусов (Redis, БД) — опционально, по умолчанию результат можно не хранить.
- Задача (task): функция, помеченная
- Когда использовать: отправка email/SMS, генерация отчётов/PDF, обработка медиа, вызовы медленных внешних API, ретраи с backoff — всё, что не должно блокировать HTTP-ответ.
- Периодические задачи: Celery Beat — планировщик (cron-подобный), который по расписанию кидает задачи в очередь (один инстанс Beat на кластер, чтобы не дублировать!).
- Возможности: retries (
max_retries,retry_backoff),acks_late(ack после выполнения — at-least-once), маршрутизация по очередям, цепочки/группы (canvas: chain, group, chord).
⚠️ Ловушка: задачи Celery должны быть идемпотентны — при acks_late и падении воркера задача переисполнится. И не запускай несколько инстансов Celery Beat — получишь дублирование периодических задач.
12Синхронное vs асинхронное взаимодействие сервисов?
middle
Короткий ответ: Синхронно — вызывающий блокируется и ждёт ответа (REST/gRPC запрос-ответ). Асинхронно — отправил сообщение/событие и не ждёт, ответ (если нужен) приходит позже или через callback/событие.
Подробно:
- Синхронно: просто, понятно, немедленный результат и ошибка. Но создаёт temporal coupling — оба сервиса должны быть живы одновременно; падение/тормоза callee бьют по caller; цепочки синхронных вызовов множат латентность и риск каскадных сбоев.
- Асинхронно (через очередь/событие): развязка во времени, устойчивость к недоступности, сглаживание пиков. Но eventual consistency, сложнее отслеживать поток, нужен обработчик «а где результат».
- Правило: синхронно — когда нужен немедленный ответ для пользователя (получить профиль). Асинхронно — когда работа может подождать или важна развязка (после оформления заказа: уведомления, начисление бонусов).
⚠️ Ловушка: длинные синхронные цепочки A→B→C→D — антипаттерн: общая латентность складывается, а доступность перемножается (0.99⁴ ≈ 0.96). Часть переводят в асинхронные события.
13Микросервисы vs монолит?
middle
Короткий ответ: Монолит — одно приложение/деплой. Микросервисы — много мелких независимо деплоимых сервисов вокруг бизнес-доменов. Микросервисы дают независимость и масштабируемость ценой огромной операционной и распределённой сложности.
Подробно:
- Монолит — плюсы: простота разработки/деплоя/отладки, транзакции в одной БД, нет сетевых вызовов между модулями, рефакторинг легче. Минусы: масштабируется только целиком, одна точка отказа на уровне процесса, большая кодовая база, рискованные деплои, привязка к одному стеку.
- Микросервисы — плюсы: независимый деплой/масштабирование/выбор стека по сервису, изоляция отказов, команды владеют сервисами автономно. Минусы: распределённые транзакции и консистентность, сетевые сбои и латентность, сложный observability/деплой/тестирование, нужна зрелая инфраструктура (CI/CD, мониторинг, оркестрация).
- Когда что: начинай с (модульного) монолита — большинству он подходит долго. Микросервисы оправданы при больших командах, разной нагрузке на части системы, разных требованиях к масштабированию/релизам. «Modular monolith» — отличный компромисс.
- Размер сервиса: вокруг бизнес-возможности / bounded context (DDD), своя БД, владеет одна команда. Не «по таблице» и не «нанослужбы».
💡 Distributed monolith (антипаттерн): сервисы вроде раздельные, но так связаны (синхронные цепочки, общая БД, совместный деплой), что получили минусы обоих миров — сложность распределёнки без её плюсов. Признаки: нельзя задеплоить один сервис без других; общая схема БД; падает один — падают все.
⚠️ Ловушка: микросервисы — это организационное решение и инфраструктурный налог, а не «модно/масштабируемо по умолчанию». Без зрелого DevOps они замедлят команду, а не ускорят.
14Межсервисное взаимодействие?
middle
Короткий ответ: Синхронно — REST или gRPC (запрос-ответ). Асинхронно — события через брокер. Снаружи — API Gateway. Сетевые кросс-функции — опционально service mesh.
Подробно:
- REST (HTTP/JSON): универсально, человекочитаемо, кэшируемо, простой дебаг. Медленнее, многословно.
- gRPC (HTTP/2 + protobuf): бинарно, быстро, строгий контракт (.proto), стриминг, кодогенерация. Хуже для браузера/дебага. Хорош для внутреннего трафика high-throughput.
- Асинхронные события: публикация событий в брокер (Kafka/RabbitMQ) — максимальная развязка, event-driven архитектура.
- API Gateway: единая точка входа для клиентов: маршрутизация к сервисам, аутентификация, rate limiting, агрегация ответов, версионирование, TLS. Прячет внутреннюю топологию.
- Service mesh (кратко): инфраструктурный слой (Istio/Linkerd, sidecar-прокси Envoy) для service-to-service трафика: mTLS, retries, timeouts, circuit breaking, трейсинг, балансировка — вынесены из кода в платформу. Нужен при большом числе сервисов.
⚠️ Ловушка: API Gateway (north-south, клиент↔система) ≠ service mesh (east-west, сервис↔сервис). Разные задачи, часто используются вместе.
15Согласованность данных в распределёнке?
senior
Короткий ответ: Без общей БД нельзя сделать единую ACID-транзакцию между сервисами. Варианты: 2PC (сильная, но хрупкая), Saga (последовательность локальных транзакций с компенсациями), eventual consistency, Outbox для надёжной публикации событий.
Подробно:
- 2PC (two-phase commit): координатор → prepare всем участникам → если все ok, commit. Даёт атомарность, но блокирующий, координатор — single point of failure, плохо масштабируется, увеличивает латентность. На практике в микросервисах избегают.
- Saga pattern: бизнес-транзакция = цепочка локальных транзакций в разных сервисах; при сбое шага запускаются компенсирующие действия (отменить предыдущие). Два стиля:
- Хореография: сервисы реагируют на события друг друга (без центра). Гибко, но трудно отследить общий поток.
- Оркестрация: центральный оркестратор управляет шагами и компенсациями. Понятнее, но появляется координатор.
- Eventual consistency: данные временно расходятся, но сходятся со временем. Достаточно для большинства бизнес-кейсов (счётчики, ленты, рекомендации).
- Outbox pattern: проблема dual write — нельзя атомарно записать в БД и опубликовать событие. Решение: в одной локальной транзакции пишем и данные, и запись в таблицу
outbox; отдельный процесс (poller / CDC, например Debezium) читает outbox и публикует события в брокер. Гарантирует «записал → событие будет опубликовано» (at-least-once).
📎 CAP/консистентность БД — см. nosql-файл.
⚠️ Ловушка: dual write (записать в БД и тут же отправить в Kafka двумя операциями) — классический баг: если упадём между ними, данные и события разойдутся. Лечится Outbox или transactional log tailing (CDC).
16CAP теорема (кратко)?
middle
Короткий ответ: При сетевом разделении (Partition) распределённая система может сохранить либо Consistency, либо Availability, но не обе. P неизбежно в сети, поэтому реальный выбор — CP или AP.
Подробно:
- C — все узлы видят одни и те же данные (линеаризуемость). A — каждый запрос получает ответ. P — система работает при потере связи между узлами.
- Поскольку сетевые сбои случаются, P обязательно → выбираем: при partition либо отвечать возможно устаревшими данными (AP), либо отказывать ради консистентности (CP).
- Дополнение — PACELC: при Partition выбор A/C, иначе (Else) — компромисс между Latency и Consistency.
📎 Детально с примерами БД (Mongo, Cassandra, etc.) — см. nosql-файл.
⚠️ Ловушка: CAP не про «выбери 2 из 3 навсегда». P нельзя «выбрать не иметь» — это свойство сети. Выбор делается только в момент partition.
17Идемпотентность операций и дедупликация?
middle
Короткий ответ: Идемпотентная операция при повторном выполнении даёт тот же результат, что и единичное. Дедупликация — отбрасывание повторных сообщений/запросов по уникальному ключу.
Подробно:
- Зачем: в распределёнке ретраи неизбежны (таймауты, переотправки очереди, повтор пользователем). Без идемпотентности повтор = дубль эффекта (двойное списание).
- Идемпотентность по HTTP: GET/PUT/DELETE идемпотентны по семантике, POST — нет. Поэтому для POST используют Idempotency-Key (клиент шлёт уникальный ключ, сервер запоминает результат первого вызова и возвращает его на повторах).
- Способы достижения:
- UPSERT / условные апдейты по бизнес-ключу;
- таблица обработанных ID (
processed_messages) — проверяем перед обработкой; - natural idempotency: «установить статус = X» вместо «инкремент».
- Дедупликация: на уровне брокера (SQS FIFO dedup window, Kafka producer idempotence) или приложения (по message_id в хранилище с TTL).
⚠️ Ловушка: инкремент (balance += 100) не идемпотентен — повтор удвоит эффект. «Установить значение» или «провести транзакцию с уникальным id» — идемпотентны. Проектируй операции идемпотентными с самого начала.
18Rate limiting?
middle
Короткий ответ: Ограничение частоты запросов для защиты от перегрузки/abuse. Основные алгоритмы: token bucket, leaky bucket, fixed window, sliding window.
Подробно — алгоритмы:
- Token bucket: ведро на N токенов, пополняется со скоростью r/сек; запрос тратит токен. Допускает всплески до размера ведра, при этом держит среднюю скорость. Самый популярный (гибкий).
- Leaky bucket: запросы — вода, «утекает» с постоянной скоростью; переполнение → отказ. Сглаживает трафик до ровного потока, всплески не пропускает.
- Fixed window: счётчик за фиксированное окно (например, 100/мин). Просто, но проблема границы — двойной всплеск на стыке окон (200 запросов за пару секунд вокруг границы).
- Sliding window log / counter: скользящее окно (точный лог меток времени или взвешенный counter) — точнее на границах, чуть дороже по памяти/вычислениям.
Где применять: API gateway, на пользователя/IP/ключ API, на дорогие эндпоинты, защита downstream-сервисов и БД. Часто реализуют в Redis (атомарные счётчики/Lua). Ответ при превышении — HTTP 429 + Retry-After.
⚠️ Ловушка: fixed window даёт всплеск на границе окон (до 2× лимита). Для строгих лимитов бери sliding window / token bucket. И помни про распределённость: счётчик должен быть общим (Redis), а не локальным на каждом инстансе.
19Шардирование и репликация БД?
senior
Короткий ответ: Шардирование — горизонтальное разбиение данных по нескольким БД (масштаб записи/объёма). Репликация — копии данных на других узлах (масштаб чтения и отказоустойчивость). Разделение чтения/записи и CQRS дополняют это.
Подробно:
- Шардирование (sharding): данные разрезаются по shard key на независимые БД:
- по ключу/диапазону (range): простой поиск по диапазонам, но риск hot shard;
- по хэшу ключа (hash): равномерно, но диапазонные запросы дорогие;
- по справочнику (directory): гибко, но lookup-таблица — точка отказа.
- Hot partition / hot shard: неудачный ключ → один шард получает непропорционально много трафика (например, шардирование по стране, где 80% юзеров). Выбирай ключ с высокой кардинальностью и равномерностью.
- Ребалансировка: при добавлении шарда данные надо перераспределить. Наивный
hash % Nломается при смене N → используют consistent hashing или виртуальные бакеты, чтобы двигать минимум данных.
- Репликация: primary (запись) + read replicas (чтение). Даёт масштаб чтения, бэкап, отказоустойчивость. Реплики отстают (replication lag) → eventual consistency на чтении.
- Разделение чтения/записи: писать в primary, читать с реплик. Бьёт по lag — после записи чтение с реплики может вернуть старое (read-your-writes решают «sticky read с primary» какое-то время).
- CQRS (кратко): Command Query Responsibility Segregation — разделение модели записи (commands) и модели чтения (queries), часто с отдельными хранилищами, оптимизированными под свои паттерны. Усложняет, оправдан при сильно разной нагрузке чтения/записи.
⚠️ Ловушка: шардирование — дорогая и почти необратимая операция; кросс-шард JOIN/транзакции/агрегации болезненны. Сначала выжми вертикаль, индексы, кэш и read replicas. Шардируй, только когда реально упёрся в запись/объём, и тщательно выбирай shard key.
20Single point of failure и failover?
middle
Короткий ответ: SPOF — компонент, отказ которого роняет всю систему. Лечится резервированием (репликация, несколько инстансов, мульти-AZ) и failover — автоматическим переключением на резерв.
Подробно:
- Поиск SPOF: одиночный сервер, одна БД без реплик, один балансировщик, одна зона доступности, единственный брокер, общий кэш без репликации.
- Устранение:
- Репликация: копии данных/сервисов (БД primary+replicas, несколько инстансов приложения).
- Избыточность: N+1 инстансов, балансировщик распределяет; сам балансировщик тоже дублируют.
- Failover: при отказе primary автоматически промоутится реплика (с health-check и кворумом, чтобы избежать split-brain).
- Multi-AZ / multi-region для географической устойчивости.
- Метрики: RTO (за сколько восстановимся) и RPO (сколько данных можем потерять).
⚠️ Ловушка: «у нас две реплики» бесполезно без авто-failover и регулярного теста переключения. Непроверенный failover часто не срабатывает в реальный инцидент. И берегись split-brain — нужен кворум/арбитр.
21Circuit breaker, retries, timeouts, bulkhead?
senior
Короткий ответ: Паттерны защиты от каскадных сбоев. Timeout — не ждать вечно. Retry с backoff+jitter — повторять с умом. Circuit breaker — перестать долбить мёртвый сервис. Bulkhead — изолировать ресурсы, чтобы сбой одной части не утопил всё.
Подробно:
- Timeouts: на каждый внешний вызов обязателен таймаут. Без него зависший downstream держит потоки/соединения caller'а → исчерпание пула → падение. Таймауты должны убывать вниз по цепочке.
- Retries + exponential backoff + jitter:
- повторять только идемпотентные/безопасные операции и только при ретраибельных ошибках (5xx, таймаут), не при 4xx;
- exponential backoff: интервалы растут (1с, 2с, 4с…), чтобы не добивать сервис;
- jitter: случайная добавка к интервалу, чтобы клиенты не ретраили синхронно (thundering herd / retry storm);
- ограничить число попыток.
- Circuit breaker: считает ошибки; при превышении порога «размыкается» (Open) — мгновенно фейлит запросы, не нагружая мёртвый сервис; через паузу переходит в Half-Open (пробный запрос) → Closed при успехе. Даёт downstream восстановиться и быстро отдаёт fallback.
- Bulkhead: изоляция ресурсов (отдельные пулы потоков/соединений на разные зависимости), как переборки в корабле. Сбой/тормоза одной зависимости не исчерпывают общий пул и не топят остальные функции.
⚠️ Ловушка: retry без backoff+jitter и без circuit breaker превращает мелкий сбой в retry storm, который добивает уже шатающийся сервис (каскадный отказ). Ретраи без идемпотентности → дубли. Комбинируй: timeout + ограниченный retry с jitter + circuit breaker.
22Graceful degradation и backpressure?
middle
Короткий ответ: Graceful degradation — при сбое части системы продолжать работать с урезанной функциональностью, а не падать целиком. Backpressure — механизм, которым перегруженный потребитель сигналит источнику замедлиться.
Подробно:
- Graceful degradation: если рекомендации недоступны — показать дефолтный список; если кэш упал — идти в БД медленнее; если платёжный провайдер лежит — принять заказ в статусе pending. Fallback-значения, feature flags, отключение второстепенного под нагрузкой (load shedding).
- Backpressure: потребитель не справляется → должен замедлить/остановить производителя, а не копить бесконечно (что приведёт к OOM). Механизмы: ограниченные очереди и буферы, блокировка/отказ при заполнении, reactive streams (request(n)), HTTP 429/503, TCP flow control. В очередях — естественный буфер + лимиты + DLQ.
⚠️ Ловушка: неограниченные очереди/буферы — иллюзия отказоустойчивости: вместо отказа сервис копит память и падает по OOM (или раздувает латентность). Лучше явный отказ/деградация (fail fast / shed load), чем тихое накопление.
2312-factor app (кратко)?
middle
Короткий ответ: Набор из 12 практик для создания cloud-native приложений: легко деплоить, масштабировать горизонтально и эксплуатировать.
Подробно — ключевые факторы (на собеседование):
- Codebase — один кодбейс в VCS, много деплоев.
- Dependencies — явно объявлены и изолированы.
- Config — в переменных окружения, не в коде.
- Backing services — БД/кэш/брокер как присоединяемые ресурсы (по URL/конфигу).
- Build, release, run — строго разделённые стадии.
- Processes — приложение как stateless процессы (состояние — во внешних сервисах). ← основа масштабирования.
- Port binding — сервис сам экспортирует HTTP, self-contained.
- Concurrency — масштаб через процессы (горизонтально).
- Disposability — быстрый старт и graceful shutdown (важно для rolling-деплоя/автоскейла).
- Dev/prod parity — окружения максимально похожи.
- Logs — как поток событий в stdout, агрегируются снаружи.
- Admin processes — разовые задачи (миграции) как one-off процессы.
⚠️ Ловушка: самые часто нарушаемые — конфиг в коде (фактор 3) и состояние в процессе (фактор 6). Именно они мешают горизонтальному масштабированию и контейнеризации.
24Observability: логи, метрики, трейсинг?
middle
Короткий ответ: Три кита наблюдаемости — логи (что произошло), метрики (агрегированные числа во времени), трейсинг (путь запроса через сервисы). Связывает их correlation/trace ID.
Подробно:
- Логи (logs): дискретные события, желательно структурированные (JSON), агрегируются (ELK/Loki). Для деталей конкретного события/ошибки.
- Метрики (metrics): числовые ряды (RPS, латентность p50/p95/p99, error rate, насыщение ресурсов). Дёшевы, агрегируемы, для дашбордов и алертов (Prometheus/Grafana). Метод: RED (Rate, Errors, Duration) и USE (Utilization, Saturation, Errors).
- Трейсинг (distributed tracing): один запрос проходит через много сервисов; trace = дерево spans с таймингами. Видно, где в цепочке узкое место. OpenTelemetry, Jaeger, Zipkin.
- Correlation ID / trace ID: уникальный идентификатор, присваиваемый запросу на входе (API gateway) и пробрасываемый через все сервисы/логи/события. Позволяет собрать всю картину одного запроса в распределённой системе.
💡 Логи и метрики говорят, что сломалось; трейсинг — где в цепочке.
⚠️ Ловушка: без correlation id логи распределённой системы бесполезны — невозможно связать события одного запроса между сервисами. Прокидывай trace id с самого начала, включая асинхронные сообщения в очередях.
25Blue-green / canary деплой?
middle
Короткий ответ: Стратегии безопасного выката. Blue-green — два полных окружения, мгновенное переключение трафика. Canary — новая версия получает сначала маленькую долю трафика, постепенно растущую.
Подробно:
- Blue-green: blue (текущая) и green (новая) работают параллельно; после проверки green балансировщик переключает 100% трафика на неё. Мгновенный откат (переключить обратно), нулевой downtime. Минус — двойные ресурсы и сложности с миграциями БД.
- Canary: новую версию выкатывают на 1–5% трафика, следят за метриками (ошибки, латентность); при успехе постепенно повышают до 100%, иначе откатывают. Минимизирует радиус поражения. Требует хорошего мониторинга и маршрутизации по долям.
- Rolling update: инстансы обновляются по очереди (дефолт в k8s) — без двойных ресурсов, но откат медленнее и временно сосуществуют версии.
⚠️ Ловушка: при любой стратегии миграции БД должны быть обратно совместимы — старая и новая версии кода какое-то время работают с одной схемой одновременно. Используй expand-and-contract (сначала добавить, потом удалить колонку в отдельном релизе).
26Зачем очередь, если можно вызвать сервис напрямую?
concept
Короткий ответ: Прямой вызов синхронен и связывает сервисы во времени; очередь даёт асинхронность, развязку, буферизацию пиков и надёжность.
Подробно: при прямом вызове caller ждёт, оба должны быть живы одновременно, всплеск трафика бьёт прямо в downstream, а если downstream лежит — операция теряется или падает. Очередь: ответ мгновенный, downstream может быть временно недоступен (сообщение подождёт), пик сглаживается буфером, доставка надёжна (ack/retry/DLQ), консьюмеры масштабируются независимо. Цена — eventual consistency и требование идемпотентности. Очередь нужна, когда результат не обязан вернуться немедленно и важна устойчивость; прямой вызов — когда нужен ответ здесь и сейчас.
27Почему stateless важно для масштабирования?
concept
Короткий ответ: Потому что взаимозаменяемые инстансы можно свободно добавлять, убирать и перезапускать, а любой запрос обрабатывать на любом узле.
Подробно: если инстанс хранит состояние (сессию, кэш, файлы) в себе, клиента приходится «приклеивать» (sticky sessions), а падение узла теряет данные и ломает балансировку. Stateless выносит состояние в общие хранилища (Redis, S3, БД) → балансировщик кидает запрос куда угодно, rolling-деплой и автоскейл проходят без потерь, горизонтальное масштабирование становится тривиальным «добавь узлов». Это прямо отражено в 12-factor (фактор 6: processes).
28Микросервисы — когда это ошибка?
concept
Короткий ответ: Когда нет зрелой инфраструктуры/команды, когда домен не понят, или когда выгоды не перевешивают распределённую сложность — особенно на старте продукта.
Подробно: микросервисы — ошибка, если: маленькая команда (накладные расходы съедят пользу); нет CI/CD, мониторинга, оркестрации (операционный ад); границы доменов ещё не ясны (придётся постоянно перекраивать сервисы — дорого через сеть); система требует кросс-сервисных транзакций повсюду; результат — distributed monolith (связанные сервисы с общей БД и совместным деплоем — минусы обоих миров). Правильный путь: начать с модульного монолита, выделять сервисы только когда появилась реальная причина (разная нагрузка/релизный темп/команды).
29Что отвечать на «как масштабировать этот сервис»?
concept
Короткий ответ: Двигаться по слоям: измерь узкое место → сделай stateless → масштабируй горизонтально за балансировщиком → кэшируй → асинхронь тяжёлое через очереди → масштабируй данные (реплики, потом шардинг) → добавь отказоустойчивость и observability.
Подробно — структура ответа:
- Сначала измерь: найди bottleneck (CPU, БД, IO, внешний API) метриками — не оптимизируй вслепую.
- Stateless: вынеси сессии/состояние/файлы наружу (Redis/S3), чтобы можно было масштабировать горизонтально.
- Горизонтально + LB: N инстансов за L7-балансировщиком с health checks.
- Кэширование: браузер/CDN/reverse proxy/Redis — снять нагрузку чтения.
- Асинхронность: тяжёлые операции — в очередь к воркерам (Celery/брокер), сгладить пики.
- Данные: read replicas + разделение чтения/записи; индексы; при упоре в запись/объём — шардирование с продуманным ключом.
- Устойчивость: убрать SPOF, timeouts + retries(backoff+jitter) + circuit breaker + bulkhead, graceful degradation, autoscaling.
- Observability: метрики/логи/трейсинг с correlation id, чтобы видеть эффект и новые узкие места.
💡 Ключевая мысль: масштабирование — итеративный процесс по данным метрик, а не разовое «впихнуть Kafka и Kubernetes».
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.