Системный дизайн. Интервью по System Design

Table of Contents

Полезные материалы

GitHub:

Статьи:

YouTube:

YouTube English:

YouTube System Design по темам:

YouTube System Design по темам English:

Общий план по решению задач системного дизайна

Основные этапы интервью (фреймворк) из статьи https://apolomodov.medium.com/how-to-prepare-for-and-pass-the-system-design-interview-78b820589e8

  1. Получение задания и контекста — интервьюер даёт название системы и краткое описание.
  2. Формализация требований — кандидат задаёт вопросы, уточняет функциональные и нефункциональные требования (например, масштабируемость, доступность, согласованность).
  3. Определение границ системы и API — фиксация внешних точек входа, интерфейсов и контрактов.
  4. Итеративное проектирование архитектуры — проработка основных потоков (happy path) и исключительных сценариев (exceptional flows).
  5. Концептуальная схема — сведение компонентов в общую архитектуру.
  6. Выбор технологий и оценка размеров системы (sizing) — обсуждение конкретных инструментов и сколько ресурсов потребуется под нагрузку.
  7. Дополнительные вопросы и расширения — optional этап, где обсуждаются дополнительные требования и возможные улучшения.

1) Понимание / уточнение задачи

  • Что делать:
    • Спокойно прочитайте задачу.
    • Задавайте вопросы интервьюеру о масштабе, ролях пользователей, сценариях и ограничениях.
  • Цель:
    • Выяснить функциональные (что нужно системе делать) и нефункциональные требования (производительность, масштабируемость, отказоустойчивость).

2) Определение ключевых характеристик

  • Что требуется выбрать:
    • SCALABILITY (масштабируемость)
    • AVAILABILITY (доступность)
    • CONSISTENCY (согласованность)
    • LATENCY (задержка отклика)
  • Как спросить:
    • “Сколько запросов в секунду ожидается?”,
    • “Насколько важна точность данных?”,
    • “Нужно ли работать офлайн?”

3) High-Level Design (Верхнеуровневая архитектура)

  • Что делать:
    • Нарисовать базовую схему: Clients → Load Balancer → API Gates → Services → Storage.
    • Определить основные компоненты системы.
  • Важно:
    • Покажите, как запросы проходят через систему, какие модули есть и как они связаны между собой.

4) Детализация компонентов

  • Проработайте:
    • Какие базы данных (SQL/NoSQL) и почему.
    • Где ставить кеши (Redis/Memcached).
    • Как будут работать очереди сообщений (Kafka, RabbitMQ) для асинхронных задач.
    • Как обеспечить отказоустойчивость (репликация, резервирование).
  • Почему это важно:
    • Показывает понимание trade-offs (например, скорость чтения vs. согласованность).

5) Обсуждение сценариев нагрузки

  • Примеры вопросов:
    • Что будет при пиковых нагрузках?
    • Как система масштабируется?
    • Чем заменить узкое место?
  • Покажите:
    • Как добавить шардирование, их репликацию, горизонтальное масштабирование и оптимизации запросов.

6) Trade-offs и ограничения

  • Обсудите:
    • Преимущества и недостатки выбранной архитектуры.
    • Почему выбран тип базы, взаимодействие компонентов и протоколы.
  • Цель:
    • Демонстрировать рассуждения — интервьюер смотрит не на правильный ответ, а на логику и аргументацию.

7) Заключение и возможные улучшения

  • Что делать:
    • Уточнить, что можно улучшить.
    • Обсудить мониторинг, логирование, безопасность, расширяемость.
  • Важно:
    • Показать стратегическое понимание системы за рамками минимального решения.

Короткая формула для интервью

Clarify → High-Level → Deep Dive → Bottlenecks → Trade-offs → Wrap-up

Вертикальное vs горизонтальное масштабирование

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

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

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

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

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

Stateful vs stateless сервисы

В мире распределённых систем различие между stateful и stateless сервисами оказывает огромное влияние на архитектуру, масштабируемость и отказоустойчивость.

Stateless-сервисы — это наиболее простой и предсказуемый тип сервисов. Они не хранят никакого состояния между запросами, и каждый запрос обрабатывается так, будто он первый. Сервис не полагается на память о предыдущих взаимодействиях и не требует сохранения пользовательских данных локально. Он получает входные данные, выполняет вычисление и возвращает результат — всё. Именно эта независимость от истории делает такие сервисы чрезвычайно удобными для масштабирования: любую копию сервиса можно добавить или удалить безо всяких последствий, а балансировщик запросов свободно распределяет нагрузку между экземплярами. Фактически stateless-архитектура превращает сервисы в вычислительные «ячейки», которые можно размножать, заменять, обновлять и уничтожать без риска потерять данные или нарушить целостность работы системы.

Stateful-сервисы живут совсем по другим законам. Они хранят состояние — пользовательские сессии, данные, транзакции, контекст взаимодействия, внутренние буферы или кэш, необходимый для корректной работы. Это означает, что один и тот же клиентский запрос должен прийти на тот же узел, который держит состояние, либо состояние должно быть доступно всем узлам через внешний механизм — например, через распределённое хранилище. Система становится более связанной и менее гибкой: невозможно просто взять и создать ещё одну копию сервиса без обеспечения согласованности состояния, так же как невозможно безболезненно выключить существующий узел, пока данные не будут корректно перенесены. Stateful-архитектуры требуют репликации, механизмов консенсуса, мониторинга здоровья узлов и сложных алгоритмов распределения нагрузки. Именно состояние делает систему более “живой”, но одновременно и более хрупкой, более чувствительной к сбоям и сетевым задержкам.

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

И всё же фундаментальный принцип остаётся неизменным: stateless-сервисы дают гибкость, простоту и практически линейную масштабируемость, в то время как stateful-сервисы дают возможность работать с реальными данными, но требуют гораздо более сложной инфраструктуры и становятся естественной точкой роста сложности всей системы. Понимание различий между ними лежит в основе любого серьёзного проектирования — от API-сервисов до распределённых баз данных — и определяет, каким образом система будет расти, выдерживать нагрузку и восстанавливаться после сбоев.

Где необходим Stateful подход

Во-первых, это базы данных и хранилища. Любая СУБД по своей сути stateful: она хранит данные, индексы, кэши, журналы транзакций и метаданные. Попытка сделать базу stateless лишена смысла, потому что её ценность — именно в сохранении и управлении состоянием. Здесь statefulness компенсируется репликацией, шардингом и механизмами восстановления.

Во-вторых, in-memory системы с состоянием, такие как Redis, Memcached, Kafka, стриминговые движки и очереди сообщений. Они держат состояние либо в памяти, либо в логах, чтобы обеспечить низкую задержку и высокую пропускную способность. Например, Kafka хранит offset’ы, порядок событий и данные топиков; Flink хранит состояние операторов; Redis — кэш и структуры данных. Вынесение этого состояния «куда-то ещё» разрушило бы их основную функцию.

Третья важная категория — долгоживущие соединения и real-time системы. WebSocket-серверы, игровые серверы, чаты, видеостриминг и push-уведомления требуют постоянного контекста соединения. Здесь состояние связано не только с данными, но и с самим фактом открытого канала связи. Каждый клиент «привязан» к конкретному соединению, и это невозможно сделать полностью stateless без потери функциональности или резкого усложнения архитектуры.

Четвёртая область — стриминговая и event-driven обработка данных. В системах вроде Flink или Spark Streaming состояние необходимо для оконных агрегаций, подсчётов, дедупликации и работы с event time. Без хранения промежуточного состояния невозможно корректно обрабатывать потоки событий. Здесь stateful-подход — не компромисс, а обязательное условие корректности.

Пятая категория — сессии и пользовательский контекст, когда объём состояния велик или часто изменяется. Например, сложные бизнес-процессы, оркестрация саг, workflow-движки, системы бронирования или финансовые процессы. Передавать весь контекст в каждом запросе было бы дорого и небезопасно, поэтому состояние хранится централизованно и обновляется постепенно.

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

Шардинг vs Репликация

В распределённых хранилищах шардинг и репликация выполняют две разные роли, хотя на практике почти всегда работают вместе.

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

Шардинг решает совершенно другую задачу. Если объём информации растёт, а одна база данных перестаёт помещаться на один сервер, приходится дробить набор данных на отдельные сегменты — шарды. Каждый шард хранит только свою часть информации, и вместе эти части образуют целостное хранилище. Это увеличивает вместимость системы практически без предела: можно добавлять новые узлы и переносить на них отдельные части данных, обеспечивая горизонтальную масштабируемость. Но вместе с этим приходит сложность маршрутизации: система должна знать, на какой узел отправить запрос; данные становятся распределёнными, и объединить их в рамках одного запроса может быть гораздо сложнее. Шардинг усложняет операции поиска, агрегирования и транзакций — то, что на одном сервере выполнялось просто, в распределённой среде требует дополнительной логики и координации.

Если репликация защищает данные, но не делает систему “больше”, то шардинг делает систему “больше”, но не защищает её сам по себе. В реальной инфраструктуре редко выбирают между ними — чаще строят комбинацию: каждый шард реплицируется на несколько узлов, чтобы обеспечить и масштабируемость, и отказоустойчивость. Таким образом, хранилище растёт по горизонтали, но при этом любой сервер внутри шарда может выйти из строя без потери данных.

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

Синхронные vs Асинхронные коммуникации

Взаимодействие сервисов в распределённой системе может строиться на синхронных или асинхронных коммуникациях, и выбор между ними определяет характер всей архитектуры.

Синхронная модель предполагает, что один сервис отправляет запрос другому и ждёт ответа, прежде чем продолжить работу. Она проста и естественна: запрос связан с ответом, логика понятна, а поток выполнения легко проследить от начала до конца. Такой подход даёт ощущение прямого диалога между системами — пока один говорит, другой слушает и отвечает. Но эта же простота становится источником ограничений. Один медленный сервис способен заблокировать цепочку зависящих от него вызовов. Если где-то возникает задержка или временная недоступность, всё дерево запросов начинает сбоить, а нагрузка растёт каскадом. Система с активным использованием синхронных вызовов становится тесно связанной: здоровье каждого узла напрямую влияет на остальных, и архитектура в целом становится менее устойчивой к непредсказуемости реального мира.

Асинхронные коммуникации предлагают совершенно иной способ взаимодействия. Вместо прямого запроса и ожидания ответа сервис только отправляет сообщение — чаще всего в очередь, шину событий или брокер сообщений — и продолжает работу, не дожидаясь реакции. Это создаёт естественную буферизацию между компонентами: отправитель не знает и не обязан знать, кто обработает сообщение, как скоро это произойдёт и какой объём нагрузки сейчас испытывает получатель. Каждый сервис работает в собственном темпе, а система становится более устойчивой к всплескам нагрузки и временной недоступности отдельных элементов. Потеря скорости одним узлом не приводит к немедленному эффекту домино: сообщения аккуратно накапливаются в очереди, а потребители обрабатывают их по мере готовности.

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

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

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

HTTP, сети и протоколы

HTTP

HTTP — это фундамент современных интернет-сервисов, и его эволюция показывает, как интернет постепенно менялся, подстраиваясь под рост потребностей.

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

HTTP/2 решает эту проблему принципиально иначе: один TCP-коннект превращается в многопоточный канал, где десятки запросов и ответов идут параллельно, перемешиваясь между собой. Появляется бинарный формат, сжатие заголовков и серверные push-запросы. Но HTTP/2 всё равно опирается на TCP, а значит страдает от «головной блокировки строки»: потеря одного пакета может заморозить весь поток.

HTTP/3 делает шаг ещё дальше и строится поверх QUIC — протокола на базе UDP, который обеспечивает поточность на уровне приложения. Потеря пакета больше не замораживает весь трафик, только конкретный поток, и система становится заметно быстрее в реальных сетях с непостоянным качеством соединения, например в мобильных сетях.

WebSockets и long polling

Рядом с HTTP стоит другая важная модель взаимодействия: WebSockets и long polling. Обычный HTTP предполагает короткий запрос и короткий ответ, но многие приложения — чат, игры, торговые терминалы — требуют постоянного двустороннего соединения.

Long polling пытается имитировать реальную подписку: клиент делает запрос, сервер держит его открытым и отвечает только тогда, когда есть новое событие. Это работает, но создаёт дополнительную нагрузку.

WebSocket же устанавливает постоянный канал поверх HTTP-upgrade и превращает клиент и сервер в равноправных участников, способных посылать сообщения в любой момент. Такой канал позволяет строить системы реального времени без постоянных открытий и закрытий соединений.

TCP и UDP

На ещё более низком уровне находится различие между TCP и UDP — двумя фундаментальными транспортными протоколами.

TCP создаёт надёжный поток данных: пакеты приходят в правильном порядке, пропавшие переотправляются, а канал гарантирует доставку. Это идеально подходит для веб-страниц, API, финансовых транзакций.

UDP работает иначе: он не пытается контролировать порядок доставки или гарантировать успех. Но именно благодаря этому UDP быстрее — и становится основой для мультимедиа, игр, стриминга и современных протоколов вроде QUIC. В системном дизайне важно понимать эту разницу: когда важнее скорость, а когда — надёжность.

Свойство TCP UDP
Гарантия доставки Да Нет
Порядок пакетов Гарантирован Не гарантирован
Скорость Медленнее Быстрее
Установление соединения Да Нет
Используется для HTTP, API, файлы Игры, звонки, стриминг
Потеря пакетов Исправляется Игнорируется

CDN

И наконец — CDN, тихий герой высокопроизводительного интернета. Контент-дистрибуционные сети выносят копии статических ресурсов ближе к пользователям, сокращая путь, который проходит запрос. Это не просто «кэширование на периферии», это целая географически распределённая сеть узлов, способная взять на себя огромную часть нагрузки и сделать систему более устойчивой к всплескам трафика. CDN особенно важна в глобальных сервисах, где пользователи находятся на разных континентах, а даже небольшое уменьшение задержки может сильно улучшить пользовательский опыт.

Данные и хранение

SQL vs NoSQL

Одним из первых выборов, который приходится делать при проектировании системы, становится выбор между SQL и NoSQL.

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

NoSQL появился как ответ на другие требования: гибкость схемы, огромные объёмы данных и горизонтальная масштабируемость. Документные базы и key-value системы не навязывают структуру, легко растут по горизонтали и прекрасно работают там, где данные имеют иерархическую природу или где система должна выдерживать колоссальную нагрузку на чтение. Выбор между SQL и NoSQL никогда не сводится к моде — он всегда определяется моделями доступа, структурой процессов и требованиями к согласованности.

Индексы

Работа с данными неизбежно приводит к вопросу индексов — того механизма, который делает операции поиска не просто возможными, но эффективными.

В основе большинства индексов в реляционных базах лежат B-tree структуры, оптимизированные под дисковые операции. Они позволяют находить строки по ключу или по диапазону за логарифмическое время, что делает их универсальным инструментом для большинства запросов.

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

Транзакционная изоляция

Даже в относительно простых системах важно понимать основы транзакционной изоляции — те уровни, которые определяют, какие аномалии допускаются при одновременных операциях. Read Uncommitted почти не используется, Read Committed обеспечивает базовую защиту от грязных чтений, Repeatable Read предотвращает непредсказуемые изменения данных в пределах одной транзакции, а Serializable создаёт иллюзию последовательного выполнения операций. На практике полный Serializable слишком дорог, и большинство систем выбирают компромисс — изоляцию, которая устраняет самые опасные аномалии, но не мешает масштабированию.

API Gateway vs. Load Balancer

Load Balancer (Балансировщик нагрузки) — это система, отвечающая за распределение входящего трафика между несколькими серверами, скрытыми за одной точкой входа. Его можно представить как регулировщика движения, который направляет автомобили (запросы) по параллельным полосам (серверам), не допуская образования заторов в сети.

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

Цель балансировщика нагрузки — оптимизировать использование ресурсов, снизить задержки и устранить единые точки отказа. Балансировщики могут работать на разных уровнях модели OSI — от уровня 4 (транспортного) до уровня 7 (прикладного).

На уровне 4 балансировщики опираются на базовую сетевую информацию, такую как IP-адреса и порты, и принимают решения о распределении трафика на основе простых правил. Балансировщики уровня 7 работают на уровне приложения, анализируя HTTP-заголовки, cookies и другие данные запросов, чтобы более осмысленно распределять трафик.

В реальных системах широко используются такие инструменты, как Nginx, HAProxy, а также механизмы балансировки нагрузки, предоставляемые публичными облачными провайдерами. Балансировщики особенно важны в средах с высоким трафиком, например на e-commerce-платформах, где они помогают гарантировать, что ни один сервер не будет перегружен запросами, особенно в периоды пиковых нагрузок — во время флеш-распродаж или сезонов активных покупок.

API Gateway — это центральная точка управления, которая контролирует и направляет API-трафик между клиентами и микросервисами, обеспечивая эффективное и безопасное взаимодействие. В отличие от балансировщика нагрузки, который лишь распределяет запросы, API Gateway интеллектуально оркестрирует API-вызовы, маршрутизирует запросы к соответствующим backend-сервисам и позволяет применять единые политики управления API. Вместе API Gateway и продвинутый подход к API-менеджменту позволяют объединить микросервисы за единым интерфейсом.

API Gateway также предоставляет расширенные возможности, такие как ограничение скорости запросов (rate limiting), управление аутентификацией и авторизацией, трансформацию запросов, кеширование, аналитику и многое другое. В микросервисных средах внедрение gateway снижает сложность системы за счёт централизации сквозных задач и упрощения управления. Вместо того чтобы реализовывать эти механизмы в каждом микросервисе по отдельности, gateway выступает в роли единой точки входа, упрощая эксплуатацию и повышая сопровождаемость системы.

В типичном сценарии клиент отправляет запрос в API Gateway. Gateway анализирует запрос, проверяет токены безопасности, при необходимости переписывает или трансформирует данные и затем маршрутизирует запрос в соответствующий микросервис. Когда микросервис возвращает ответ, gateway может преобразовать полезную нагрузку обратно для клиента или применить логику кеширования для повышения производительности. Таким образом, API Gateway — это не просто инструмент для равномерного распределения запросов, а компонент, который контролирует и посредничает во всём взаимодействии между клиентами и backend-сервисами.

Во многих зрелых микросервисных экосистемах API Gateway часто используется совместно с решениями для API-менеджмента, чтобы упростить управление, анализ использования и применение политик.

Аспект Load Balancer API Gateway
Основная роль Распределяет входящий трафик между серверами/инстансами для равномерной нагрузки и отказоустойчивости. Центральная точка управления API-вызовами между клиентами и микросервисами, с возможностью политики и обработки запросов.
Что делает Перенаправляет запросы на свободные узлы, балансирует нагрузку. Анализирует запросы, маршрутизует, проверяет безопасность, трансформирует данные.
Безопасность Может выполнять SSL/TLS termination, базовое SSL шифрование. Поддерживает аутентификацию, авторизацию, rate limiting, WAF-правила.
Границы видимости В основном невидим для клиента, работает «за кулисами». Выступает официальной клиентской точкой входа, видной внешнему миру.
Уровень OSI Часто слой 4 (TCP/UDP), может быть и Layer 7 (HTTP маршрутизация). В основном Layer 7, работает с API-уровнем (HTTP, WebSocket, иногда gRPC).
Обработка запросов Простейшие правила распределения, иногда по URL/заголовкам. Гибкое роутирование по пути, заголовкам, версиям API, преобразование запросов.
Мониторинг и аналитика Базовый health-check, статистика распределения. Глубокие метрики API: latency, использование, ошибки.
Сложность и стоимость Проще, обычно дешевле. Сложнее, богаче функционально, может быть дороже.
Расширяемость Масштабирует распределение нагрузки. Масштабирует API-контроль, политики, безопасность.

Когда выбирать Load Balancer:

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

Когда выбирать API Gateway:

Если вы строите микросервисную архитектуру и хотите единый интерфейс для всех API, централизовать безопасность, авторизацию, throttle/rate limiting, логирование, трансформацию запросов или предоставлять разные версии API, то API Gateway — более подходящее решение. Он выступает «консьержем API», скрывая сложность микросервисов от клиентов и обеспечивая единообразное управление трафиком и политиками.

Когда использовать оба вместе:

В больших продуктах часто применяют сочетание API Gateway + Load Balancer. Например, Load Balancer распределяет трафик по нескольким экземплярам API Gateway для отказоустойчивости и масштабирования, а сам Gateway выполняет логические проверки и маршрутизацию к микросервисам. В других архитектурах Gateway сначала обрабатывает запрос, а затем передаёт его на Load Balancer внутри кластера микросервисов.

Rate limiting / throttling

Rate limiting / throttling — это механизм контроля количества запросов, которые клиент или группа клиентов может отправить в систему за определённый промежуток времени. Его цель — защитить сервисы от перегрузки, злоупотреблений и неравномерного использования ресурсов, сохранив стабильность и предсказуемость работы системы.

В основе rate limiting лежит простая идея: любые вычислительные ресурсы конечны, и если не ограничивать входящий поток запросов, один пользователь, бот или ошибка в коде могут исчерпать эти ресурсы и повлиять на всех остальных. Throttling позволяет системе вежливо, но жёстко сказать: «Ты уже отправил достаточно запросов, подожди немного».

С точки зрения поведения системы rate limiting проявляется в том, что при превышении лимита новые запросы либо отклоняются с ошибкой вроде HTTP 429 Too Many Requests, либо обрабатываются с задержкой. Это создаёт предсказуемый потолок нагрузки и позволяет сервису продолжать работу даже в условиях пикового трафика или атаки.

В распределённых системах rate limiting часто применяется на границе системы — в API Gateway, балансировщике нагрузки или edge-инфраструктуре. Это позволяет отсекать избыточный трафик ещё до того, как он достигнет внутренних сервисов и баз данных. Например, публичное API может разрешать 100 запросов в минуту на один API-ключ, чтобы ни один клиент не мог монополизировать систему.

Существует несколько распространённых алгоритмов rate limiting. Fixed window ограничивает количество запросов в жёстких временных окнах, но может допускать всплески на границе окон. Sliding window сглаживает эти эффекты, учитывая реальное распределение запросов во времени. Token bucket и leaky bucket моделируют систему как сосуд, в который поступают «токены» с фиксированной скоростью, позволяя временные пики, но контролируя среднюю нагрузку. Эти модели широко используются в реальных системах, потому что они хорошо балансируют гибкость и контроль.

Важно понимать разницу между rate limiting и throttling. Rate limiting обычно означает жёсткое ограничение — запросы сверх лимита просто отклоняются. Throttling чаще подразумевает мягкое управление скоростью — система может замедлять ответы, ставить запросы в очередь или постепенно снижать пропускную способность. На практике эти термины часто используются вместе, потому что решают одну и ту же задачу — защиту системы от перегрузки.

В реальных продуктах rate limiting применяется повсеместно. Stripe ограничивает частоту запросов к платёжным API, GitHub — к своим публичным endpoint’ам, а облачные провайдеры используют throttling для защиты инфраструктуры от внезапных всплесков нагрузки. Даже внутри микросервисных архитектур rate limiting важен: он предотвращает ситуации, когда один сервис начинает агрессивно дергать другой и провоцирует каскадные отказы.

Kafka и RabbitMQ

Kafka и RabbitMQ решают похожую задачу — передачу сообщений между компонентами системы, — но делают это принципиально по-разному. Понимание этой разницы важно для системного дизайна, потому что выбор между ними влияет на масштабируемость, модель обработки данных и даже на то, как система будет эволюционировать со временем.

RabbitMQ — это классический брокер сообщений. Его модель ближе всего к очереди задач: продюсер отправляет сообщение, брокер гарантирует его доставку, а консьюмер забирает сообщение и подтверждает обработку. После подтверждения сообщение исчезает. RabbitMQ активно управляет сообщениями: он знает, кто их получил, кто подтвердил, кому нужно сделать retry, а кому отправить сообщение в dead-letter очередь. Это делает RabbitMQ отличным выбором для систем, где важна надёжная доставка каждой задачи и сложная логика маршрутизации. Например, фоновые задачи вроде отправки email, обработки платежей, генерации отчётов или работы с внешними API часто реализуются через RabbitMQ. Здесь критично, чтобы каждое сообщение было обработано ровно один раз или, как минимум, не потерялось.

Kafka работает по другой философии. Это не столько очередь, сколько распределённый лог событий. Сообщения в Kafka не удаляются после чтения — они сохраняются в топике в течение заданного времени или до достижения лимита размера. Консьюмеры сами отслеживают, что они прочитали, управляя своим offset. Это позволяет нескольким независимым сервисам читать одни и те же данные, каждый в своём темпе, не мешая друг другу. Kafka отлично подходит для систем, где важен поток событий, высокая пропускная способность и возможность повторного воспроизведения данных. LinkedIn, Uber и Netflix используют Kafka как основу для аналитики, построения фидов, логирования и event-driven архитектур.

С точки зрения производительности Kafka рассчитана на очень большие объёмы данных и высокую скорость записи — сотни тысяч или миллионы сообщений в секунду. Она масштабируется горизонтально за счёт партиций и распределения нагрузки между брокерами. RabbitMQ тоже масштабируется, но его сильная сторона не в throughput, а в гибкости: сложные схемы routing, topic-exchange, приоритеты сообщений, подтверждения и ретраи.

Есть и различие в характере гарантий доставки. RabbitMQ ориентирован на надёжность обработки каждой задачи и богатую семантику доставки. Kafka чаще используется в модели «at-least once» или «exactly once» (при определённой настройке), но с акцентом на потоковую обработку, а не на индивидуальные задания. В Kafka допустима идея, что сообщение может быть обработано повторно, и система должна быть к этому готова.

Если упростить, RabbitMQ — это инструмент для команд и задач, Kafka — инструмент для событий и потоков данных. Если нужно гарантированно выполнить конкретное действие — например, списать деньги или отправить письмо — чаще выбирают RabbitMQ. Если же нужно зафиксировать факт события и позволить многим сервисам независимо на него реагировать — например, «пользователь сделал заказ» или «поездка завершена» — Kafka становится естественным выбором.

В реальных архитектурах эти системы нередко используются вместе. Kafka может служить основным «хребтом» событийной архитектуры, а RabbitMQ — обслуживать операционные задачи и фоновые процессы. Понимание их различий позволяет осознанно выбирать инструмент под конкретную задачу, а не поддаваться моде или привычке.

Критерий Kafka RabbitMQ
Основная идея Распределённый лог событий Классический брокер сообщений
Тип данных Поток событий (event stream) Очереди сообщений (tasks/commands)
Модель обработки Consumer сам управляет offset Broker управляет доставкой
Удаление сообщений Хранятся заданное время Удаляются после ack
Повторное чтение Да (replay) Нет (по умолчанию)
Throughput Очень высокий (100k+ msg/s) Ниже, но стабильный
Latency Чуть выше (ms) Ниже (sub-ms – ms)
Масштабирование Горизонтальное (partitioning) Ограниченное, сложнее
Гарантии доставки At-least-once, exactly-once At-least-once, at-most-once
Routing Минимальный Очень гибкий (exchanges)
Message ordering Внутри партиции Внутри очереди
Fan-out (1 → many) Нативно (consumer groups) Через exchanges
Retention По времени / размеру Нет retention
DLQ (dead letter queue) Через отдельные топики Встроено
Использование памяти Disk-first Memory-first
Типичные данные Логи, события, метрики Задачи, команды
Сложность эксплуатации Выше Ниже
Типичный стек Kafka + ZooKeeper/KRaft RabbitMQ cluster
Примеры компаний LinkedIn, Uber, Netflix GitHub, Instagram, Airbnb

Когда выбирать Kafka

Kafka подходит, если:

  • нужен event-driven подход
  • требуется очень высокий throughput
  • несколько сервисов должны читать одни и те же события
  • нужна возможность переигрывать события
  • строится аналитика или стриминг
  • данные — это «история событий»

Примеры:

  • аналитика пользовательских действий
  • feed generation
  • логирование
  • real-time metrics
  • event sourcing

Когда выбирать RabbitMQ

RabbitMQ подходит, если:

  • нужна гарантированная обработка каждой задачи
  • важна низкая задержка
  • требуется сложная маршрутизация
  • есть retry, DLQ, приоритеты
  • задачи короткие и независимые

Примеры:

  • отправка email/SMS
  • фоновые job’ы
  • обработка заказов
  • интеграции с внешними API
  • task queues (Celery)

Fan-out и Fan-in

Fan-out и Fan-in — это базовые паттерны распределённых систем, которые описывают, как запросы или события расходятся и сходятся между компонентами системы. Эти понятия часто встречаются в системном дизайне, очередях сообщений, микросервисах и стриминговых архитектурах.

  • Fan-out: один источник — много получателей.
  • Fan-in: много источников — один агрегатор.

Fan-out

Fan-out — это ситуация, когда одно событие или запрос «размножается» и отправляется сразу нескольким получателям. Источник генерирует одно сообщение, а система доставки гарантирует, что его получат все заинтересованные сервисы. В этом паттерне отправитель не знает и не должен знать, сколько получателей существует и кто они именно.

Fan-out используется, когда один факт в системе должен вызвать несколько независимых реакций. Например, пользователь зарегистрировался. Это одно событие, но на него могут реагировать разные сервисы: один отправляет приветственное письмо, другой создаёт профиль, третий обновляет аналитику, четвёртый инициирует рекомендации. Все эти действия должны происходить параллельно и не блокировать друг друга.

В Kafka fan-out реализуется естественным образом через consumer groups: одно и то же событие читается разными группами консьюмеров, каждая из которых обрабатывает его по-своему. В RabbitMQ fan-out достигается с помощью exchange, который копирует сообщение во множество очередей. В HTTP-мире fan-out встречается, когда API Gateway делает несколько внутренних вызовов, собирая данные из разных сервисов.

Fan-in

Fan-in — это обратный процесс: несколько источников данных сходятся в одну точку обработки. Система получает события или результаты от множества сервисов и агрегирует их, объединяет или синхронизирует.

Fan-in применяется, когда нужно собрать данные из разных частей системы и получить единый результат. Например, при формировании news feed сервис может получать посты от сервиса подписок, рекламы, рекомендаций и трендов, а затем объединять их в одну ленту. В аналитике fan-in используется для агрегации логов, метрик и событий со множества узлов в один поток для обработки или хранения.

В Kafka fan-in выглядит как множество продюсеров, пишущих в один топик. В RabbitMQ — как несколько продюсеров, отправляющих сообщения в одну очередь. В микросервисах fan-in часто реализуется на уровне сервиса-агрегатора или API Gateway, который собирает ответы от нескольких backend-сервисов и возвращает клиенту единый результат.

Caching (Кеширование)

Кеширование — это один из ключевых инструментов системного дизайна, позволяющий снизить задержки, уменьшить нагрузку на базу данных и повысить устойчивость системы под высокой нагрузкой. В основе кеширования лежит идея временного хранения часто запрашиваемых или дорогих в вычислении данных ближе к месту использования. Однако важно не просто «добавить кеш», а выбрать правильный паттерн, потому что каждый из них по-разному влияет на согласованность данных, сложность системы и сценарии отказов.

Самый распространённый и интуитивно понятный подход — Cache Aside. В этом паттерне ответственность за работу с кешем полностью лежит на приложении. Когда приходит запрос, приложение сначала проверяет кеш. Если данные там есть, они сразу возвращаются клиенту. Если данных нет, приложение идёт в базу данных, получает результат и кладёт его в кеш, чтобы последующие запросы были быстрее. При записи или обновлении данных приложение сначала пишет в базу, а затем инвалидирует или обновляет кеш. Этот подход прост, прозрачен и хорошо контролируем, поэтому он используется в большинстве веб-приложений. Его слабое место — риск временной неконсистентности и cache stampede, когда множество запросов одновременно промахиваются мимо кеша.

Read-Through Cache переносит логику загрузки данных в сам кеш. Приложение всегда обращается только к кешу, а при промахе кеш сам идёт в базу данных, загружает данные и возвращает их приложению. Для разработчика это выглядит как доступ к одному источнику данных, что упрощает код. Такой подход часто реализуется в managed-решениях или библиотеках, тесно интегрированных с хранилищем. Однако он усложняет инфраструктуру и снижает прозрачность: приложение меньше контролирует, когда и как происходят обращения к базе.

Write-Through Cache ориентирован на согласованность данных при записи. Когда приложение сохраняет данные, оно сначала пишет их в кеш, а кеш синхронно записывает изменения в базу данных. Благодаря этому чтения всегда получают актуальные данные из кеша, а риск рассинхронизации минимален. Цена за это — более высокая задержка на запись, так как операция считается завершённой только после записи в базу. Такой паттерн подходит для систем, где корректность данных важнее скорости записи.

Write-Behind (или Write-Back) делает шаг в сторону производительности. Запись сначала происходит только в кеш, а в базу данные сохраняются асинхронно, с задержкой или батчами. Это резко ускоряет операции записи и хорошо работает под высокой нагрузкой. Но взамен система становится более сложной и менее надёжной: при падении кеша есть риск потери данных, а база данных временно содержит устаревшее состояние. Этот подход применяют там, где допустима eventual consistency и где кеш можно сделать надёжным, например с журналированием или репликацией.

Materialized Views выходят за рамки классического кеша, но решают похожую задачу — ускорение чтения. Вместо того чтобы каждый раз выполнять сложные JOIN’ы или агрегации, система заранее вычисляет результат запроса и хранит его в виде отдельной таблицы или представления. При чтении данные отдаются мгновенно, но за это приходится платить сложностью обновления: при изменении исходных данных materialized view нужно пересчитывать полностью или инкрементально. Такой подход часто используется в аналитических системах, отчетах и read-heavy сценариях.

Event Sourcing — это более фундаментальный архитектурный паттерн, в котором состояние системы не хранится напрямую, а восстанавливается из последовательности событий. Сами события являются единственным источником истины, а текущее состояние и производные представления могут кешироваться или храниться как materialized views. В этом контексте кеширование становится способом ускорить восстановление состояния и чтение, а не источником данных. Event sourcing даёт отличную масштабируемость и трассируемость изменений, но значительно усложняет систему и требует зрелого подхода к консистентности и миграциям.

В реальных системах эти подходы редко используются поодиночке. Чаще всего Cache Aside применяется для простых CRUD-операций, write-through или write-behind — для горячих данных с высокой нагрузкой на запись, materialized views — для сложных запросов, а event sourcing — для доменных ядер, где важна история изменений.

Кеширующие системы, которые должен знать каждый разработчик

Данные кешируются повсюду — от клиентской стороны до backend-систем. Рассмотрим основные уровни кеширования, которые используются для оптимизации производительности.

Слои кеширования

Клиентские приложения. Браузеры кешируют HTTP-ответы. Серверные ответы содержат директивы кеширования в заголовках. При последующих запросах браузер может отдать данные из кеша, если они всё ещё считаются актуальными.

Сети доставки контента (CDN). CDN кешируют статический контент — изображения, стили, JavaScript-файлы. Контент обслуживается с серверов, расположенных ближе к пользователям, что снижает задержки и ускоряет загрузку страниц.

Балансировщики нагрузки. Некоторые балансировщики способны кешировать часто запрашиваемые данные. Это позволяет отдавать ответы без обращения к backend-серверам, снижая нагрузку и уменьшая время отклика.

Брокеры сообщений. Системы вроде Kafka кешируют сообщения на диске в соответствии с политикой хранения (retention). Потребители затем читают сообщения в удобном для них темпе.

Сервисы. Отдельные сервисы часто используют кеширование для ускорения доступа к данным, сначала проверяя in-memory кеш перед обращением к базе данных. Также может применяться дисковое кеширование для больших объёмов данных.

Распределённые кеши. Системы вроде Redis кешируют пары ключ–значение и используются несколькими сервисами одновременно, обеспечивая более быстрые операции чтения и записи по сравнению с традиционными базами данных.

Полнотекстовые поисковые движки. Платформы вроде Elasticsearch индексируют данные для быстрого текстового поиска. Такой индекс по сути является формой кеша, оптимизированного под быстрый поиск по тексту.

Базы данных. Внутри СУБД существуют специализированные механизмы повышения производительности, многие из которых используют идеи кеширования.

Механизмы кеширования в базах данных

Buffer Pool. Это внутренний кеш базы данных, который хранит копии страниц данных в памяти. Он позволяет быстро читать и записывать данные во временное хранилище в RAM, снижая необходимость обращаться к диску.

Materialized Views. Материализованные представления похожи на кеши тем, что хранят результаты вычислительно дорогих запросов. База данных может быстро вернуть заранее вычисленные результаты вместо того, чтобы пересчитывать их каждый раз.

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

Примеры систем кеширования

На уровне приложений чаще всего используются специализированные in-memory хранилища, такие как Redis и Memcached. Они работают как отдельные сервисы, хранящие данные в оперативной памяти и предоставляющие доступ к ним по сети с минимальной задержкой. Приложение при чтении сначала обращается к кешу и только при промахе идёт в основное хранилище. Redis, в отличие от Memcached, поддерживает сложные структуры данных, персистентность, репликацию и механизмы отказоустойчивости, поэтому его часто используют не только как кеш, но и как вспомогательное хранилище состояния. Memcached проще и быстрее в эксплуатации, но ограничен моделью ключ-значение и не сохраняет данные при перезапуске.

Следующий важный уровень — CDN (Content Delivery Network), такие как Cloudflare или AWS CloudFront. CDN кеширует статический и полу-статический контент — изображения, стили, скрипты, видео — на edge-серверах, физически близких к пользователю. Когда клиент запрашивает ресурс, запрос не доходит до основного сервера, а обслуживается ближайшей CDN-нодой. Это резко снижает latency и снимает нагрузку с backend’а. Управление таким кешем обычно осуществляется через HTTP-заголовки Cache-Control, Expires и ETag, которые определяют, сколько времени контент считается валидным.

На стороне пользователя работает кеш браузера, который хранит ресурсы локально и повторно использует их без сетевого запроса. Этот уровень кеширования полностью прозрачен для backend’а, но оказывает огромное влияние на производительность и восприятие скорости приложения. Грамотно настроенный браузерный кеш позволяет загружать страницы практически мгновенно, но требует аккуратной стратегии инвалидирования, особенно при деплое новых версий фронтенда.

Часто кеширование реализуется и на уровне самого приложения — в виде локального in-process кеша. Такие кеши живут в памяти процесса и дают минимальную задержку, но плохо масштабируются и не подходят для распределённых систем без дополнительной синхронизации. Их используют для небольших, редко меняющихся данных или как дополнительный слой поверх Redis для самых горячих запросов.

Отдельный класс — кеши на уровне базы данных и операционной системы. Современные СУБД активно кешируют данные в памяти, используя page cache, buffer pool и другие внутренние механизмы. Операционная система также кеширует файловые операции, снижая количество реальных обращений к диску. Эти кеши управляются автоматически и обычно не контролируются напрямую разработчиком, но они существенно влияют на производительность и должны учитываться при проектировании.

Общим механизмом управления всеми уровнями кеширования являются политики времени жизни данных — TTL (time to live), eviction-алгоритмы вроде LRU или LFU и стратегии инвалидирования. TTL ограничивает срок актуальности данных и предотвращает использование слишком устаревших значений. Eviction-алгоритмы определяют, какие данные будут удалены первыми при нехватке памяти. Инвалидация гарантирует, что изменения в основном хранилище рано или поздно отразятся в кеше.

Stream Processing

Коротко:

  • Flink — для сложного stateful streaming с сильными гарантиями,
  • Spark Streaming — для аналитического стриминга с упором на batch-модель,
  • Storm — для экстремально низкой задержки ценой сложности и ручного управления состоянием.

Apache Flink

Apache Flink — это мощный движок для потоковой обработки данных с сильными гарантиями по состоянию (state) и обработке событий. При этом Flink — скорее фреймворк, чем готовое прикладное решение. Это означает, что почти всю доменную логику разработчик реализует сам: агрегации, оконные функции, управление состоянием, очистку устаревших данных и интеграцию пайплайнов с другими системами.

С точки зрения системного дизайна Flink ценен тем, что он ориентирован на true streaming — обработку событий по мере их поступления, а не батчами. Он поддерживает состояние операторов, таймеры, event-time processing и гарантии exactly-once. Для хранения состояния часто используется RocksDB, что позволяет работать с большими объёмами данных и делать инкрементальные чекпоинты. Однако само управление жизненным циклом состояния — например, TTL для пользовательских профилей или удаление данных после периода неактивности — требует явной реализации со стороны разработчика.

Масштабирование Flink — нетривиальная задача. Добавление или удаление узлов обычно требует сохранения состояния (savepoint) и перезапуска джоба. Начиная с версии 1.13 появился Reactive Mode, который позволяет полуавтоматически масштабировать систему за счёт перезапуска с новыми ресурсами, но настоящее zero-downtime масштабирование пока недоступно. Поэтому Flink хорошо подходит для долгоживущих стриминговых задач с чётко определённой логикой и высокой ценностью состояния, но требует зрелой эксплуатации.

Apache Spark Streaming

Apache Spark Streaming, особенно в режиме Structured Streaming, реализует потоковую обработку через концепцию микробатчей. Вместо обработки каждого события отдельно Spark группирует события в небольшие временные партии и обрабатывает их как мини-батчи. Это упрощает модель исполнения и повторно использует батчевую инфраструктуру Spark, но накладывает ограничения на задержку.

С точки зрения system design ключевой компромисс Spark Streaming — это latency vs simplicity. Микробатчи означают, что минимальная задержка измеряется секундами, а не миллисекундами. Для задач, где требуется реакция «здесь и сейчас», такой подход не подходит. Зато Spark отлично справляется с тяжёлыми агрегациями, оконными вычислениями и интеграцией с аналитическим стеком.

Как и Flink, Spark Streaming — это фреймворк, а не готовое решение. Разработчику необходимо самостоятельно описывать окна, счётчики, агрегации и заботиться о том, как результаты стриминга будут использоваться дальше — например, в обучении моделей или аналитике. Spark хорошо подходит для систем, где стриминг — это продолжение batch-аналитики, а не основа real-time взаимодействия с пользователем.

Apache Storm

Apache Storm создавался с прицелом на ультранизкую задержку. Он обрабатывает события по одному, без микробатчей, что делает его одним из самых быстрых инструментов для настоящей real-time обработки. Это сильное преимущество в сценариях, где важны миллисекунды.

Однако из коробки Storm — stateless-система. Он не хранит контекст между событиями, и если требуется агрегация, подсчёты или оконные метрики, разработчику нужно самостоятельно реализовать хранение состояния — например, во внешней базе данных. Это сильно усложняет архитектуру и повышает связность компонентов.

Storm также не предоставляет богатого набора агрегатных операторов. Любые счётчики, окна, метрики и сложные вычисления нужно писать вручную. Слой Trident добавляет поддержку состояния и частично вводит микробатчи, смягчая ограничения, но даже с Trident Storm остаётся более низкоуровневым и менее удобным, чем современные стриминговые движки. В результате Storm чаще рассматривают как специализированный инструмент для узких real-time задач, а не универсальную стриминговую платформу.

Что важно знать про streaming для system design

С точки зрения системного дизайна стриминговые системы — это всегда баланс между задержкой, состоянием и сложностью эксплуатации. True streaming (как во Flink или Storm) даёт минимальную latency и точный контроль над event-time, но усложняет масштабирование и управление состоянием. Микробатчевый подход (Spark Streaming) проще в эксплуатации и интеграции с аналитикой, но не подходит для интерактивных сценариев.

Важно также понимать, что стриминговые движки редко работают в одиночку. Обычно они строятся поверх брокеров событий вроде Kafka, используют внешние хранилища для долговременного состояния и формируют materialized views для быстрого чтения. В интервью ценится не знание API конкретного инструмента, а понимание, когда нужен streaming вообще, почему нельзя обойтись batch-обработкой и какие компромиссы система делает ради низкой задержки или высокой надёжности.

Графовые базы данных (Graph DB)

Графовые базы данных (Graph DB) — это специализированные системы хранения данных, оптимизированные для работы со связями между сущностями. В отличие от реляционных баз, где связи реализуются через JOIN’ы, в графовых БД связи являются первоклассными объектами и хранятся напрямую. Это делает операции обхода связей, поиска путей и анализа графов на порядки эффективнее и концептуально проще.

В основе графовой модели лежат вершины (nodes), рёбра (edges) и свойства (properties). Вершины представляют сущности — пользователей, товары, документы. Рёбра описывают отношения между ними — «друг», «купил», «подписан». Свойства дополняют и вершины, и рёбра атрибутами. Такая модель особенно хорошо подходит для доменов, где ключевую роль играют связи, а не табличные агрегаты: социальные сети, рекомендательные системы, antifraud, knowledge graph, IAM и сетевые топологии.

Рынок графовых баз данных сформировался вокруг нескольких зрелых решений. Neo4j остаётся безусловным лидером и фактическим стандартом де-факто. Он предлагает зрелую экосистему, язык запросов Cypher, богатые инструменты визуализации и оптимизирован для OLTP-нагрузок с большим количеством обходов графа. Neo4j часто выбирают в enterprise-проектах, где важна стабильность, документация и опыт эксплуатации.

Amazon Neptune — облачно-ориентированная графовая база от AWS, ориентированная на managed-подход. Она поддерживает модели property graph и RDF, а также языки Gremlin и SPARQL. Neptune хорошо вписывается в экосистему AWS, обеспечивает автоматическое масштабирование и высокую доступность, но при этом уступает Neo4j в гибкости и выразительности запросов. Это типичный выбор для компаний, которые уже глубоко сидят в AWS и хотят минимизировать операционные издержки.

ArangoDB представляет собой мультимодельную базу данных, сочетающую в себе документную, ключ-значение и графовую модели. Такой подход удобен для систем, где граф — лишь часть общей архитектуры, а не единственный способ доступа к данным. ArangoDB позволяет работать с разными типами данных в рамках одной СУБД, что снижает сложность инфраструктуры, но может быть менее специализированным по сравнению с «чистыми» графовыми решениями.

Dgraph ориентирован на распределённость и масштабирование «из коробки». Он изначально проектировался как distributed graph database и тесно интегрирован с GraphQL. Это делает его привлекательным для cloud-native и API-ориентированных систем, где важны горизонтальное масштабирование и простой доступ к данным через GraphQL-интерфейсы. При этом Dgraph требует более серьёзного понимания внутренних механизмов и пока менее распространён в enterprise-сегменте.

TigerGraph специализируется на глубокой аналитике связей и обработке очень больших графов. Его сильная сторона — сложные многопереходные запросы, графовая аналитика и сценарии, где нужно анализировать большие объёмы взаимосвязанных данных за минимальное время. TigerGraph часто используется в антифроде, телеком-аналитике и финансовых системах, где глубина обхода графа имеет критическое значение.

Помимо зрелых решений, рынок активно развивается за счёт новых подходов. PuppyGraph предлагает интересную концепцию — выполнять графовые запросы поверх уже существующих хранилищ, без необходимости миграции данных в отдельную графовую БД. Это снижает барьер входа и упрощает интеграцию. RelationalAI, в свою очередь, объединяет реляционную модель с логическими и AI-ориентированными подходами, расширяя традиционные базы возможностями вывода и анализа сложных зависимостей.

С точки зрения системного дизайна графовые базы данных выбирают тогда, когда основная ценность системы — в связях, а не в отдельных записях. Они плохо подходят для простого CRUD или тяжёлых агрегатов, но незаменимы для задач поиска связей, рекомендаций, обнаружения аномалий и анализа сложных структур. В реальных архитектурах Graph DB часто используются вместе с реляционными и документными базами, выполняя роль специализированного компонента для работы с отношениями.

Метрики производительности системы

Ваш API работает медленно. Но насколько именно медленно? Нужны числа. Реальные метрики, которые показывают, что именно сломалось и где это нужно чинить.

Вот четыре ключевые метрики, которые должен понимать каждый инженер при анализе производительности системы:

  • Queries Per Second (QPS) — количество входящих запросов, которые система обрабатывает за одну секунду. Если сервер получает 1000 запросов за секунду, значит у него 1000 QPS. Звучит просто, пока не становится ясно, что большинство систем не способны долго выдерживать пиковый QPS без начала деградации.
  • Transactions Per Second (TPS) — количество завершённых транзакций, которые система обрабатывает за секунду. Транзакция включает полный цикл обработки: запрос отправляется, доходит до базы данных и возвращается с ответом.
    TPS показывает фактически выполненную работу, а не просто принятые запросы. Именно на эту метрику чаще всего ориентируется бизнес.
  • Concurrency (параллелизм) — количество одновременно активных запросов, которые система обрабатывает в конкретный момент времени. Например, система может получать 100 запросов в секунду, но если каждый запрос выполняется 5 секунд, то одновременно в работе находится 500 запросов.
    Высокий уровень параллелизма означает, что системе требуется больше ресурсов, эффективный пул соединений и грамотное управление потоками.
  • Response Time (RT) — время, прошедшее с момента начала обработки запроса до получения ответа. Измеряется как на стороне клиента, так и на стороне сервера.

Все эти метрики связывает простое соотношение:

QPS = Concurrency ÷ Среднее время ответа

Больше параллелизма или меньшее время ответа означает более высокую пропускную способность системы.

Метрики производительности в highload системах

В highload-системах метрики производительности нужны не «для графиков», а для ответа на конкретные вопросы: где узкое место, что сломается следующим и почему пользователи чувствуют деградацию. Обычно их рассматривают по уровням системы — от клиента до железа — потому что высокая нагрузка почти всегда проявляется каскадно.

Клиентский уровень (Client-Side Performance)

Этот слой отвечает за то, как пользователь ощущает систему, даже если backend формально «жив».

Ключевые метрики здесь — время установления TCP-соединения, время загрузки HTML, CSS, JavaScript и изображений, а также общее HTTP response time и статус ответа. В highload-сценариях проблемы на backend’е быстро проявляются именно здесь: растёт latency, увеличивается число таймаутов, появляются 5xx-ошибки.

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

Сетевой уровень (Network Metrics)

Сеть — это кровеносная система highload-архитектуры. На этом уровне смотрят, выдерживает ли инфраструктура объём соединений и трафика.

Основные показатели — количество установленных TCP-соединений, число TCP-сегментов в секунду, количество reset’ов и неудачных попыток соединения. В условиях высокой нагрузки резкий рост TCP resets или connection failures почти всегда указывает на перегрузку балансировщика, exhaustion портов, проблемы с NAT или неправильные таймауты.

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

Уровень веб-сервера (Web Server Metrics)

Этот слой отвечает за приём и первичную обработку запросов. Именно здесь highload чаще всего проявляется в виде очередей и отказов.

Критичны метрики очередей запросов, транзакций в секунду, объёма переданных данных, потребления памяти и cache hit ratio. Рост request queue — один из самых опасных симптомов: он означает, что система уже не справляется с входящим потоком.

Cache hit ratio здесь играет ключевую роль: при высокой нагрузке даже небольшое падение процента попаданий в кеш может лавинообразно увеличить нагрузку на backend и базу данных.

Уровень application-сервера

Это сердце бизнес-логики, и в highload-системах именно здесь чаще всего «горит».

Здесь смотрят на время установления соединений, ожидание соединений (connection wait time), использование памяти, количество потоков, активные и приостановленные транзакции, таймауты и rollback’и.

Рост connection wait time означает, что пул соединений исчерпан. Увеличение active transactions без роста TPS — сигнал о том, что запросы «залипают» внутри системы. Таймауты и rollback’и часто указывают на перегрузку downstream-сервисов или базы данных.

Связь метрик между собой

В highload-системах метрики нельзя анализировать изолированно. Например:

  • рост response time почти всегда ведёт к росту concurrency;
  • рост concurrency увеличивает потребление памяти и количество открытых соединений;
  • перегруженные application-серверы создают очереди на веб-сервере;
  • очереди увеличивают клиентскую latency и таймауты.

Поэтому инженеры часто используют базовое соотношение:

Throughput = Concurrency ÷ Response Time

Оно помогает понять, что именно ограничивает систему — скорость обработки или количество одновременно обрабатываемых запросов.

Что важно на собеседовании

В контексте system design от тебя ждут не перечисления всех метрик, а понимания:

  • какие метрики сигнализируют о начале деградации;
  • какие из них указывают на CPU-bound, IO-bound или network-bound систему;
  • как метрики разных уровней влияют друг на друга.

Обзор паттернов

Архитектурные паттерны

  • Микросервисы — Разбиение системы на независимые сервисы, каждый из которых выполняет свою небольшую задачу. Используют Netflix, Uber, Amazon.
  • Монолит — Единое приложение, внутри которого всё тесно связано. Простой старт, сложное масштабирование.
  • SOA (Service-Oriented Architecture) — Предшественник микросервисов — крупные сервисы с чётко определёнными контрактами.

Чем отличается SOA (Service-Oriented Architecture) от микросервисной архитектуры

SOA (Service-Oriented Architecture) и микросервисная архитектура основаны на одной идее — разбиении системы на сервисы, — но различаются по масштабу, степени связанности и подходу к разработке и эксплуатации.

SOA возникла как enterprise-подход к интеграции крупных корпоративных систем. Сервисы в SOA обычно достаточно крупные, охватывают значимые бизнес-домены и часто разделяют общую инфраструктуру: базы данных, ESB (Enterprise Service Bus), механизмы безопасности и оркестрации. Взаимодействие между сервисами в SOA нередко строится через централизованный слой интеграции, который отвечает за маршрутизацию, трансформацию сообщений и применение политик. Это упрощает управление и стандартизацию, но повышает связанность системы и создаёт единые точки отказа.

Микросервисная архитектура, напротив, делает акцент на максимальную автономность сервисов. Каждый микросервис обычно небольшой, отвечает за один узкий бизнес-контекст и владеет своими данными. Сервисы взаимодействуют напрямую, чаще всего по лёгким протоколам вроде HTTP или через асинхронные события, без тяжёлого централизованного посредника. Это снижает связанность и позволяет независимо разрабатывать, деплоить и масштабировать компоненты системы.

С точки зрения данных различие особенно заметно. В SOA допускается совместное использование баз данных и схем, что упрощает консистентность, но усложняет эволюцию. В микросервисах принцип «database per service» считается базовым, а согласованность достигается через события и eventual consistency.

Отличается и эксплуатационная модель. SOA ориентирована на стабильные, долго живущие сервисы с редкими изменениями и централизованным управлением. Микросервисы проектируются под частые деплои, автоматизацию, CI/CD, контейнеризацию и горизонтальное масштабирование. Это повышает гибкость, но увеличивает операционную сложность.

Паттерны масштабирования и доступности

  • Load Balancer — Распределяет нагрузку между узлами. Классическая схема — NGINX + несколько backend-сервисов.
  • Horizontal Scaling — Добавляем больше узлов вместо усиления одного.
  • Sharding — Разделение данных по ключу или диапазону между несколькими нодами.
  • Replication — Создание копий данных для отказоустойчивости и быстрого чтения.
  • Federation / Partitioning — Разделение больших сервисов или БД на тематические домены.

Паттерны взаимодействия между сервисами

  • Request/Response — Типичный REST/gRPC вызов.
  • Event-Driven Architecture — Сервисы общаются через события, как в Uber, LinkedIn.
  • Pub/Sub — Публикация событий в брокер (Kafka, Pulsar), подписчики их потребляют.
  • CQRS (Command Query Responsibility Segregation) — Разделение операций записи и чтения. Полезно в high-load системах.
  • Saga — Управление распределёнными транзакциями при помощи цепочки локальных операций и компенсационных действий.
  • Circuit Breaker — Предотвращение каскадных падений: если сервис долго не отвечает, вызовы отключаются.
  • Retry / Exponential Backoff — Повторная отправка запросов с увеличивающимся тайм-аутом.
  • Bulkhead — Изоляция компонентов, чтобы сбой одного не уронил всю систему.

Паттерны данных и кеширования

  • Cache Aside — Приложение сначала проверяет кеш, затем БД. Самый популярный паттерн.
  • Read-Through Cache — Кеш сам ходит в БД при промахе.
  • Write-Through Cache — Запись идёт сначала в кеш, потом в базу.
  • Write-Behind — Запись в БД происходит асинхронно из кеша.
  • Materialized Views — Предварительно пересчитанные данные для быстрых запросов.
  • Event Sourcing — Состояние системы хранится как поток событий.

Паттерны потоковой обработки

  • Stream Processing — Система обрабатывает данные непрерывным потоком. Kafka Streams, Flink.
  • Lambda Architecture — Комбинация batch-обработки и stream-обработки.
  • Kappa Architecture — Только потоковая обработка, без batch-а.

Паттерны отказоустойчивости

  • Leader Election — Выбор ведущего узла (ZooKeeper, Etcd, Consul).
  • Failover — Переход на резервную ноду при отказе основной.
  • Redundancy — Дублирование критичных узлов или подсистем.

Паттерны API и интеграции

  • API Gateway — Единая точка входа в систему (Netflix Zuul, Kong). Добавляет авторизацию, rate limiting, кеш.
  • Backend for Frontend (BFF) — Отдельный backend для каждого типа клиента — мобильного, веба и т. п.
  • Service Mesh — Автоматизированная сетка сервисных коммуникаций: Envoy, Istio.

Паттерны управления состоянием

  • Stateless — Сервисы без состояния легко масштабируются.
  • Stateful — Сервисы, хранящие состояние, требуют репликации или sticky-сессий.
  • Sticky Sessions — Привязка клиента к конкретному серверу при работе со stateful-компонентами.

Паттерны безопасности

  • OAuth2 / JWT — Стандартные механизмы авторизации.
  • Zero Trust — Каждый запрос проверяется, даже внутри частной сети.
  • Rate Limiting / Throttling — Ограничение запросов, чтобы защитить сервисы. Статья

Глоссарий / Термины

Общие термины подготовки к интервью

  • System Design — проектирование крупномасштабных распределённых систем и архитектурных решений для сложных приложений.
  • Coding — подготовка к собеседованиям по алгоритмам и программированию, включая техники решения задач.
  • Behavioral Interview — оценка мягких навыков и коммуникативных качеств кандидата.
  • Tech Interview — общий термин для всех технических собеседований: coding, system design, behavioral.
  • FAANG — аббревиатура ведущих технологических компаний (Facebook/Meta, Apple, Amazon, Netflix, Google), стандарт отрасли.
  • STAR Method — структурированный метод ответа на поведенческие вопросы (Situation, Task, Action, Result).
  • Mock Interviews — практические имитации интервью для отработки формата и получения обратной связи.
  • Interview Roadmap — структурированный план подготовки: coding, design, behavioral.
  • Interview Bootcamp — интенсивная программа подготовки с практическими заданиями и наставниками.
  • Resume Review — услуга оценки и улучшения резюме для технических ролей.
  • AI-Assisted Development / Vibe Coding — использование AI-инструментов (например, ChatGPT) для улучшения процесса написания кода.

System Design: фундаментальные концепции

  • System Design Fundamentals — базовый набор концепций и архитектурных паттернов для проектирования систем.
  • High-Level Design (HLD) — общая архитектурная картина, компоненты и их связи.
  • Low-Level Design (LLD) — детальная проработка компонентов, структуры данных, классы и модули.
  • Scalability — способность системы обрабатывать рост нагрузки.
  • Performance vs Scalability — производительность для одного пользователя против эффективности при росте нагрузки.
  • Latency — время отклика системы.
  • Throughput — количество обработанных операций за единицу времени.

Масштабирование

  • Вертикальное масштабирование — увеличение мощности одного сервера (CPU, RAM).
  • Горизонтальное масштабирование — добавление новых узлов для распределения нагрузки.
  • Sharding — горизонтальное разделение данных на части для масштабирования.
  • Partitioning — логическое деление наборов данных для параллельной обработки.
  • Replication — копирование данных на несколько узлов для отказоустойчивости и доступности.
  • Replication factor — число копий данных в распределённой системе.

Consistency и модели согласованности

  • CAP Theorem — компромисс между Consistency, Availability и Partition tolerance.
  • Weak consistency — отсутствие немедленной согласованности после записи.
  • Eventual consistency — реплики данных со временем сходятся к одному состоянию.
  • Strong consistency — все чтения после записи видят обновлённые данные.
  • Idempotency — повторный вызов операции даёт одинаковый результат без побочных эффектов.

Отказоустойчивость и доступность

  • Fail-over — переключение на резервный узел при сбое.
  • Active-passive — один узел активен, другой в standby.
  • Active-active — оба узла обслуживают трафик одновременно.
  • Circuit breaker — блокировка вызовов к проблемному сервису для отказоустойчивости.

Сетевые коммуникации

  • HTTP — протокол прикладного уровня для веб-сервисов.
  • REST — архитектурный стиль API поверх HTTP.
  • WebSockets — двунаправленный протокол для постоянного соединения.
  • Long polling — клиент держит открытый запрос до появления новых данных.
  • TCP — надёжный транспортный протокол.
  • UDP — лёгкий транспортный протокол без гарантии доставки.
  • TLS/HTTPS — шифрование сетевого трафика.
  • Authentication — подтверждение личности пользователя/сервиса.
  • Authorization — проверка прав доступа.
  • JWT (JSON Web Token) — стандарт аутентификации и передачи утверждений.
  • RPC — удалённый вызов процедур.
  • gRPC — высокопроизводительный RPC-фреймворк.

DNS и балансировка нагрузки

  • DNS — система доменных имён.
  • A record — связывает доменное имя с IP-адресом.
  • CNAME — каноническое имя, указывающее один домен на другой.
  • NS record — указывает авторитетные DNS-серверы домена.
  • MX record — указывает почтовый сервер домена.
  • Load Balancer — распределение трафика между серверами.
  • Layer 4 load balancing — балансировка на транспортном уровне.
  • Layer 7 load balancing — балансировка на прикладном уровне.
  • Reverse proxy — прокси, принимающий запросы от клиентов и перенаправляющий на внутренние сервисы.
  • Forward proxy — прокси между клиентом и внешним интернетом.
  • Service mesh — инфраструктурный слой для управления взаимодействием микросервисов.

Базы данных и хранение

  • RDBMS — реляционная база данных с ACID-транзакциями.
  • ACID — атомарность, согласованность, изоляция, долговечность транзакций.
  • Master-slave replication — одна база для записи, несколько реплик для чтения.
  • Master-master replication — несколько узлов принимают чтение и запись с синхронизацией.
  • Federation — разделение базы на функциональные части.
  • Denormalization — копирование данных для ускорения чтения.
  • SQL tuning — оптимизация запросов и индексов.
  • NoSQL — ключ-значение, документные, колонкоориентированные, графовые базы.
  • Key-value store — хранилище пар «ключ-значение».
  • Document store — база данных, хранящая документы.
  • Wide-column store — колонкоориентированная база данных.
  • Graph Database — база для хранения графов сущностей и связей.
  • Indexes — структуры для ускорения поиска.
  • Transactional isolation — уровень согласованности параллельных транзакций.

Кэширование

  • Cache — временное хранение данных для ускорения доступа.
  • Client caching — кэш на стороне клиента.
  • CDN caching — кэш на edge-серверах CDN.
  • Web server caching — кэш в веб-сервере.
  • Database caching — кэш в базе данных.
  • Application caching — кэш в приложении (Redis, Memcached).
  • Cache-aside — приложение управляет кэшем самостоятельно.
  • Write-through — запись в кэш и основной store одновременно.
  • Write-behind — запись сначала в кэш, потом в основной store.
  • Refresh-ahead — обновление записи до истечения TTL.
  • Eviction policy — правило удаления устаревших данных из кэша.
  • TTL (Time To Live) — время жизни записи в кэше.
  • Cache hit — запрос найден в кэше.
  • Cache miss — запрос не найден в кэше, обращение к основному источнику.

Асинхронность и очереди

  • Message Queue — очередь сообщений для обмена между сервисами.
  • Task Queue — очередь фоновых задач.
  • Producer — отправитель сообщений.
  • Consumer — получатель сообщений.
  • Topic / Partition — логические каналы и партиции в Kafka для параллелизма.
  • Retention policy — правила хранения сообщений в очереди/топике.
  • Back pressure — защита системы от перегрузки.
  • Fan-out / Fan-in — распределение сообщений и агрегирование результатов.
  • Rate limiting — ограничение частоты запросов/операций.
  • Backoff strategy — уменьшение частоты повторных попыток при ошибках.
  • Windowing — обработка событий во временные окна.
  • Exactly-once semantics — гарантия однократной обработки события.

Полезные практические ресурсы

Разбор задач по системному дизайну

Материалы

URL Shortener (аналог bit.ly)

Классика: генерация коротких ссылок, редиректы, TTL, лимиты.

Сервис сокращения URL: архитектурный разбор

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

Основная функциональность и требования

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

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

Генерация коротких идентификаторов

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

Альтернативный подход основан на хешировании длинного URL. Он не требует глобального состояния и может выполняться на любом узле. Однако применение чистого хеша чревато коллизиями. Поэтому обычно выбирают комбинацию хеша и проверки хранилища. Если коллизия обнаружена, система генерирует новый идентификатор, используя добавочные данные или случайность. В системах с огромным объёмом данных вероятность коллизий становится заметным фактором, поэтому практикуют укороченный хеш с дополнительной энтропией.

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

Хранилище

В основе сервиса лежит структура вида ключ-значение. Ключом является короткий идентификатор, значением — исходный URL и метаданные. Применение реляционной базы обычно неоправданно. Такие данные масштабируются горизонтально гораздо проще, если использовать распределённые key-value хранилища или базы класса NoSQL.

Зная, что операции чтения преобладают, критичной становится скорость доступа. Поэтому архитектура почти всегда включает слой кеширования. Ближайший к пользователю региональный кеш Redis или аналог снижает задержку и уменьшает нагрузку на основное хранилище. Важно понимать, что кеш должен обновляться при изменении TTL, удалении ссылок или продлении срока жизни. Ошибки в синхронизации иногда приводят к выдаче истёкших ссылок, поэтому метаданные TTL хранятся как в кеше, так и в первичном хранилище.

Обработка редиректов

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

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

TTL, истечение и очистка

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

Ограничения и защита от злоупотреблений

Защита от чрезмерного использования сервиса чаще всего реализуется через rate-limiting. Ограничения могут быть наложены как на создание ссылок, так и на переходы. Для глобальных публичных сервисов применяется многоуровневая архитектура, где лимиты проверяются на уровне CDN, API-шлюза и непосредственно в сервисе.

Масштабирование

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

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

Возможные схемы

Rate Limiter

Token bucket / leaky bucket, распределённый rate limiting.

Этап 1. Постановка задачи и исходный контекст

Интервьюер формулирует задачу: требуется спроектировать распределённую систему rate limiting, способную ограничивать количество запросов, исходящих от клиента, сервиса или пользователя, в единицу времени. Ограничения должны быть гибкими, управляемыми и устойчивыми к злоупотреблениям. Важно обеспечить предсказуемую нагрузку на backend-части систем, а также защиту от несанкционированных попыток обойти лимиты. Задача должна включать модели token bucket или leaky bucket, а также уметь работать в распределённой среде, что исключает выполнение rate limiting полностью локально.

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

После постановки вопроса интервьюер завершает вводную часть и передаёт инициативу кандидату.

Этап 2. Формализация требований и уточняющие вопросы

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

Параллельно формулируются нефункциональные цели. Высокая доступность для rate limiter является критичной, поскольку ошибки в этом компоненте могут привести к деградации или отказу всего API. Консистентность требует аккуратного подхода: строгая консистентность повышает задержки, а eventual consistency может допускать временные превышения лимита. Выбор зависит от характера сервиса. Пропускная способность должна быть высокой, так как проверка лимита выполняется на каждом запросе. Масштабируемость важна для горизонтального расширения без разделения контекста лимитов или появления горячих ключей. Аудитируемость может стать обязательной в корпоративных системах, где требуется отслеживание нарушений.

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

Этап 3. Границы системы и публичное API

Теперь требуется определить публичный интерфейс. Rate limiter по сути предоставляет одну основную операцию: проверку и обновление лимита пользователем. В рамках неё клиент передаёт идентификатор субъекта лимитирования и параметры правила (если они не заранее зафиксированы). Результат представляет собой разрешение или запрет действия, дополненный информацией о текущем состоянии бакета или очереди.

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

Таким образом граница системы формируется вокруг простой операции check-and-update, являющейся атомарной по смыслу, пусть и распределённой по технической реализации.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

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

В распределённой системе появляется вопрос синхронизации состояния бакета между узлами. Первым компонентом становится хранилище счётчиков. Оно должно обеспечивать атомарные операции инкремента, декремента, условной записи или хранения временных отметок. Система может использовать in-memory хранилище с распределёнными блокировками, например Redis с Lua-скриптами, либо специализированные распределённые базы данных, предоставляющие операции CAS. Логика rate limiting должна быть максимально компактной, чтобы обработка происходила за десятки микросекунд.

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

В случае отказов необходимо предусмотреть graceful degradation. Например, при временной недоступности центрального хранилища система может работать в режиме fail-open или fail-closed. Выбор стратегии зависит от характера сервиса: критичные API обычно предпочитают fail-open, чтобы не блокировать клиентов, но строго внутренние системы могут работать в fail-closed для защиты от перегрузки.

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

Этап 5. Концептуальная архитектура

Завершив развитие сценариев, можно представить целостную схему. API получает запрос на проверку лимита и передаёт его в компонент rate limiter. Этот компонент обращается к распределённому хранилищу счётчиков, выполняя атомарную транзакцию: расчёт восстановленных токенов, проверку лимита и обновление состояния. Результат возвращается клиенту. Для высокой производительности система использует локальные кеши, в которых хранит метаданные лимитов и параметры восстановления. Хранилище выполняет роль единственного источника истины, обеспечивая согласованность в распределённой среде. В архитектуре может быть предусмотрен вспомогательный сервис для централизованной конфигурации правил.

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

Этап 6. Выбор технологий и оценка размера системы

На этом этапе проводится обсуждение конкретных инструментов. Если требуется минимальная задержка, применяется Redis Cluster, позволяющий выполнять атомарные операции через Lua. Для более строгой консистентности можно использовать CockroachDB или DynamoDB, обеспечивающие линейно масштабируемые операции обновления. В случае экстремальных нагрузок возможно применение in-memory систем, работающих полностью распределённо, например Aerospike.

Оценка размера зависит от количества субъектов лимитирования и числа запросов. Если система обрабатывает сотни тысяч запросов в секунду, хранилище должно поддерживать низкую задержку при высокой конкурентности. Выбор region-aware конфигурации Redis или использование шардированных распределённых атомарных счётчиков становится важной частью sizing. Для глобальных систем можно организовать региональные rate-limiters, выполняющие часть работы локально, а глобальная консистентность достигается распределённым накоплением токенов.

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

Этап 7. Дополнительные вопросы и расширения

Наконец, можно рассмотреть расширение задачи. Например, реализацию rate limiting на уровне CDN, распределённые токены, синхронизацию лимитов между дата-центрами, защиту от случайных всплесков нагрузки, реализацию soft-limits и mode-switching при деградации. Можно обсудить многоуровневые лимиты, построенные по принципу периметр → API → метод. Ещё одна интересная тема — rate limiting в системах с микросервисной архитектурой, где каждый сервис может выступать одновременно и потребителем, и контролирующим компонентом.

Эти дополнительные рассуждения демонстрируют широту знаний кандидата и умение видеть систему за пределами узкого требования.

Design a Cache System (Redis-based)

Кеширование профилей, стратегий eviction, борьба со штампедом.

Этап 1. Постановка задачи и контекст

Сервис должен кэшировать профильные данные пользователей для обеспечения низкой латентности чтения и снижения нагрузки на хранилище первичных данных. Профиль включает небольшой набор «горячих» полей (имя, аватар, статус, флаги приватности), а также ряд «холодных» или редко меняющихся полей (история, настройки). Основные функциональные требования: быстрые чтения (миллисекунды), согласованность в рамках разумных ограничений, возможность инвалидации при обновлениях профиля, поддержка TTL для устаревших данных и защита от наводнений запросов (stampede). Нефункциональные требования: высокая доступность, горизонтальная масштабируемость, контролируемое использование памяти, мониторинг и способность обслуживать пики нагрузки.

Этап 2. Формализация требований и ключевые архитектурные характеристики

Необходимо уточнить несколько практических допущений, которые влияют на дизайн: допустимо ли eventual consistency между кэшем и базой; частота и критичность обновлений профиля; цель по hit-rate (например ≥95%); ожидаемый QPS чтений и записей; средний размер профиля. Из нефункциональных свойств важно зафиксировать приоритеты: низкая латентность чтения — главный приоритет; консистентность сильнее важна для операций конфиденциальности и удаления, чем для отображения аватара; доступность должна быть высокой, система не должна становиться «узким местом». На основе этого определяем свойства: оптимизация под high throughput (чтения), допустимость eventual consistency для обычных полей, необходимость auditability для операций обновления критичных атрибутов.

Этап 3. Границы системы и публичный API

Граница системы — уровень кеша, видимый для прикладных сервисов. Набор операций минимален: GetProfile(userId, options) возвращает профиль (или его часть) и метаинформацию; UpdateProfile(userId, delta) обновляет профиль и обеспечивает инвалидацию/обновление кеша; InvalidateProfile(userId) принудительно удаляет/обновляет запись в кеше; BulkWarm(keys) — опционально для прогрева. GetProfile должен поддерживать параметры частичного чтения (hot fields vs full profile). Ответ должен содержать маркер источника («cache» или «db») и, при необходимости, TTL/версию. API должен быть лёгким и атомарным с точки зрения прикладного кода: вызов GetProfile не обязан вручную реализовывать логику кеширования.

Этап 4. Проектирование: happy path и exceptional flows, компоненты и потоки данных

Happy path для чтения: приложению вызывают GetProfile(userId). Клиентский SDK (или middleware на уровне сервиса) проверяет локальный near-cache (опционально), затем Redis. При попадании в кеш (hit) профиль возвращается. При промахе (miss) сервис читает профиль из первичной БД, записывает его в Redis (cache-aside) с нужным TTL/метаданными и возвращает результат. Для ускорения и экономии сети подход cache-aside с централизованной логикой чаще всего лучше, чем write-through, поскольку запись происходит реже.

Обновления: при поступлении UpdateProfile система выполняет запись в первичную БД и затем инвалидиует ключ в Redis. Варианты поведения: синхронная инвалидация после успешной транзакции записи, либо write-through (сначала в кеш, затем в БД) при необходимости строгой консистентности. Для частичных обновлений полезно хранить версию/число смен (version number) в значении кеша — это упрощает детекцию устаревших значений и гонок.

Борьба со штампедом (cache stampede): при большом количестве одновременных запросов на недостающий в кеше ключ нужно предотвратить лавину чтений в БД. Практические техники:

  • Singleflight / mutex per key: первый запрос ставит «замок» (локальный или в Redis с SETNX), остальные ждут или получают ответ с опцией «fallback»; после загрузки из БД и записи в кеш замок снимается. Для распределённой среды применяют Redis с небольшим TTL на замок и гарантированным восстановлением.
  • Request coalescing на уровне edge: агрегировать запросы внутри процесса или на API-шлюзе.
  • Probabilistic early recompute: при приближении TTL популярного ключа его предварительно «перепекают» (background refresh) по вероятностному алгоритму, чтобы избежать одновременной просадки.
  • Negative caching и Bloom filter: для отсутствующих ключей возвращать отрицательный кеш (короткий TTL) и использовать Bloom filter, чтобы фильтровать запросы к БД для несуществующих пользователей.
  • Lazy locking + timeout: выдержать предел ожидания, а при ошибке чтения возвращать ошибку контролируемо.

Hot keys и «горячие» профили: если конкретный профиль получает несоизмеримо много трафика, имеет смысл хранить его в специальной hot-shard или использовать client-side near-cache с TTL и LFU-недельной политикой, либо применить rate limiting на уровне потребителей для этого ключа.

Eviction и стратегии: Redis предлагает политики evictions: LRU, LFU, TTL-based и их комбинации. Для профилей лучше избегать простого allkeys-lru без учета семантики, иначе горячие профили будут вытеснять набор «активных» пользователей. Практические подходы: разделение ключей по namespace (hot vs cold) с разными maxmemory и политиками; хранение самых важных полей отдельно (hot fields) с более длинным TTL; использование LFU для адаптивного удержания часто запрашиваемых профилей. Также можно применять size-aware eviction: учитывать размер значения при принятии решения об удалении (если профили имеют разный размер).

Последовательность для отказов компонента кеша: при недоступности Redis система должна graceful degrade — либо читать напрямую из БД (fail-open), либо использовать локальные копии (near-cache), либо, при критичности защиты БД, temporarily reject с контролируемым backoff (fail-closed). Чаще выбирают fail-open для пользовательского опыта и мониторинг для отслеживания нагрузки на базу.

Этап 5. Концептуальная архитектура и целостный обзор

В центре — прикладной API слой с middleware, реализующим логику кеширования. Redis Cluster выступает в качестве распределённого in-memory хранилища. Рядом располагаются вспомогательные сервисы: сервис конфигурации TTL/политик, фоновые воркеры для прогрева/рефреша, очередь событий для инвалидаций (pub/sub или Kafka), мониторинг/alerting. Поток чтения: application → local near-cache → Redis → DB. Поток записи: application → DB → (инвалидация через pub/sub) → Redis (delete) или обновление значения. Для борьбы со штампедом дополнительный слой — lock service (реализуемый через Redis SETNX + Lua) и Bloom filter для non-existent keys.

Этап 6. Выбор технологий и sizing

Технологический стек: Redis Cluster для шардирования и масштабирования памяти, с replica-репликами на каждом шардe для отказоустойчивости; Redis Sentinel или встроенные механизмы кластера для failover; использование Redis Modules (RedisJSON) если нужно частичное чтение/запись полей профиля без передачи всего объекта; клиентские библиотеки с поддержкой singleflight/near-cache.

Sizing. Исходим из предположений: N активных пользователей, средний профиль P килобайт, ожидаемый hit-rate H, peak read QPS_r и write QPS_w. Память = N * P * (1 / hit_factor) плюс overhead шардов и metadata; добавляем репликацию (factor 2 или 3). Количество узлов = ceil(memory_total / instance_memory). Для QPS учитываем пропускную способность каждого экземпляра Redis (обычно десятки тысяч ops/s на современном железе), но при большом числе небольших операций важна сетевая задержка и CPU (Lua-скрипты и сериализация). Рекомендуется планировать запас 2–3× по операции и памяти, использовать мониторинг (hit ratio, evictions, latency) и авто-scaling/resharding при росте.

Параметры отказоустойчивости: реплики на каждом шарде, RPO/RTO зависят от выбора persistence (AOF vs RDB) — для кеша обычно persistence выключен или минимален; если важно сохранить кеш между рестартами, включают RDB snapshotting с приемлемыми интервалами.

Этап 7. Дополнительные расширения и эксплуатационные темы

Можно рассмотреть следующие улучшения: partial caching с RedisJSON для снижения трафика при обновлениях; adaptive TTL на основе частоты доступа; per-field versioning для минимизации инвалидаций при частичных обновлениях; CDN/edge caching для контента профиля, не требующего приватности; использование managed Redis (AWS ElastiCache, Azure Redis) для снижения операционной нагрузки. Для наблюдаемости — метрики: hit/miss ratio, evictions/sec, memory usage, slowlog; tracing запросов для выявления hot keys. Наконец, политика безопасности: шифрование транспортного уровня, ACL в Redis, ограничение доступа через VPC.

Messenger/Chat (WhatsApp / Telegram Lite)

Сохранение сообщений, онлайн-статусы, доставляемость, fan-out.

Этап 1. Постановка задачи и исходный контекст

Интервьюеру даётся задача: спроектировать сервис обмена сообщениями — лёгкий мессенджер, обеспечивающий отправку и хранение сообщений, онлайн-статусы, доставляемость и масштабируемый фан-аут как для личных чатов, так и для групповых. Система должна поддерживать множество устройств на один аккаунт, обеспечивать быстрый UX (низкая задержка доставки и отображения новых сообщений), сохранять историю и при необходимости доставлять сообщения оффлайн-пользователям через push. Нефункциональные требования включают высокую доступность, устойчивость к пиковому трафику, масштабирование до миллионов активных пользователей, обработку медиа (attachments) и обеспечение порядка сообщений в пределах чата. Дополнительные пожелания — эффективная борьба с дублированием, поддержка синхронизации между устройствами и возможность расширения (например, голосовые сообщения, шифрование).

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

Этап 2. Формализация требований и ключевые архитектурные характеристики

Кандидат задаёт уточняющие вопросы и формализует поведение системы. Первое — требуемые гарантии доставки: допустима ли at-least-once или требуется exactly-once? В мессенджерах обычно достаточно at-least-once с идемпотентной обработкой на клиенте (удаление дубликатов по message-id) и семантикой «sent → delivered → read». Второе — требования к порядку: строгий порядок обеспечивает удобство в личных чатах; в группах часто достаточно порядка в пределах одного отправителя или условного causality (переменная важность строгого глобального порядка). Третье — моделирование оффлайн-клиентов: сообщения должны сохраняться на сервере до доставки всем активным девайсам или до истечения TTL, а также синхронизироваться при подключении нового устройства. Четвёртое — масштабируемость фан-аута: одновременная доставка в группы с миллионами подписчиков невозможна простым broadcast-режимом; нужно выбирать между eager fan-out (write-time fan-out) и lazy fan-out (read-time fan-out) с гибридными подходами. Пятое — требования к конфиденциальности и безопасности: будет ли требоваться end-to-end шифрование (E2EE) или достаточно транспорта и хранения в зашифрованном виде на сервере. Нефункционально фиксируется высокая доступность, низкая задержка (мс-уровень для онлайн-доставки), масштабируемость и мониторируемость.

На базе этих ответов фиксируются ключевые архитектурные характеристики: больше внимания — latency и availability; консистентность — умеренная (с акцентом на локальный порядок в чатах); throughput — высокий для читателей и средний/низкий для записей; auditability и долговременное хранение — опционально, зависят от политики ретеншна.

Этап 3. Проектирование границ системы и публичного API

Границей считается набор API, которым оперируют клиенты и вспомогательные сервисы. Минимальный публичный контракт включает операции аутентификации/привязки устройства, отправки сообщения, получения сообщений (с пагинацией и синхронизацией), отметки доставленных/прочитанных сообщений, получения и публикации presence, загрузки и получения медиа, управление подписками на групповые события.

Примеры API (HTTP/HTTP+WebSocket/гетерогенный протокол):

POST /v1/send
body: { fromUserId, fromDeviceId, conversationId, clientMessageId, payload, timestamp, attachmentsMeta }

WS: SUBSCRIBE /v1/stream?userId=…
messages stream: server -> client pushes new messages, presence updates, acks

GET /v1/sync?userId&sinceToken
returns: ordered messages, device sync cursors

POST /v1/ack
body: { conversationId, serverMessageId, ackType: delivered|read, deviceId, timestamp }

POST /v1/presence
body: { userId, deviceId, status: online|offline|idle, lastActiveAt }

API должен возвращать достаточную метаинформацию: серверные идентификаторы, пер-сообщение sequence / lamportTimestamp для порядка, курсоры синхронизации и указание устройств, на которые сообщение было доставлено или нет. Публичный контракт отделяет клиентскую видимость от внутренней реализации (например, internal push-gateway, message-broker, storage).

Этап 4. Проектирование системы: happy path и exceptional flows; компоненты и потоки данных

Основной happy path: пользователь A отправляет сообщение в чат с пользователем B или в группу. Клиент формирует клиентский идентификатор clientMessageId и локальную метку времени; сообщение отправляется на ближайший frontend (API / WebSocket gateway). Gateway выполняет базовую валидацию и передаёт сообщение в контроллер доставок (dispatcher). Dispatcher назначает серверный идентификатор messageId и sequence/ordering metadata, записывает сообщение в durable log (например, partitioned Kafka topic или аналогичный commit-log) и публикует событие в очередь доставки. Затем dispatcher инициирует фан-аут: для личного чата это список устройств B; для группы — список участников (который может храниться в sharded group-service). Для каждого целевого девайса формируется delivery task, который отправляется в ряды delivery-workers.

Delivery-worker пытается доставить сообщение: если получено активное соединение WebSocket/MQTT, worker шлёт push и ждёт acknowledgement от клиента. При подтверждении отправки (device-level ack) worker помечает устройство как доставленное и, при необходимости, обновляет статус в message-store. Если устройство оффлайн, worker генерирует push-notification через APNs/FCM (через push-gateway) и сохраняет сообщение как pending в per-device queue для будущей доставки. После доставки на все устройства обновляется статус «delivered» для конкретного получателя или всех получателей. При прочтении клиент шлёт read-ack, который обновляет read-state и генерирует уведомление отправителю.

На этом пути формируются ключевые компоненты: front-end gateways (HTTP + WebSocket), dispatcher (assigns ids, writes to commit-log), durable commit-log (Kafka-like), delivery workers (stateless, масштабируемые), presence service (tracking active connections and device mapping), group service (manages membership), message store (persisting messages for retrieval and history), per-device pending queues, push-gateway, media-store / CDN для attachments, sync service (reconciliation and history pagination), and monitoring & metrics.

Exceptional flows: дублирование сообщений при повторной отправке со стороны клиента (network retry) обрабатывается идемпотентностью по clientMessageId; потеря ordering при кросс-шардовых операциях решается назначением per-conversation sequence (dispatcher даёт монотонный sequence в пределах conversation partition). Отказ durable log или delivery-workers компенсируется репликацией commit-log и повторной обработкой событий; в случае недоступности push-gateway система может временно накапливать pending-notifications и применять backoff. Для скоростных всплесков применима backpressure на gateway и rate limiting на уровне sender.

Порядок сообщений. В личных чатах важно сохранить строгий порядок; это достигается partitioning commit-log по conversationId — все сообщения одного разговора попадают в один партишин, получают последовательный offset и обрабатываются в порядке поступления. В группах с большим количеством участников схему с single partition может стать узким местом; тогда применяют per-conversation партиционирование + sharding группы по conversationId и, при необходимости, логические sequence от каждого отправителя (per-sender ordering) с merge-стратегией на клиента.

Хранение истории. Сообщения сохраняются в message-store — выбор между wide-column store (Cassandra/DynamoDB) или append-only blob storage с индексом зависит от требований по latency и доступности. Для пользовательского опыта последние N сообщений размещают также в in-memory cache (Redis) для ускорения sync и initial load. Медиа-файлы сохраняются в object storage (S3) и раздаются через CDN; в базе хранятся ссылки и метаданные. Политика retention управляется на уровне сервиса: удаление по истечению TTL, возможность удаления пользователем и аудит.

Синхронизация между устройствами реализуется через sync cursors. Клиент периодически вызывает /sync?sinceToken и получает все новые события. При привязке нового устройства проводится full-sync (последние N сообщений плюс paginated history). Sequence и cursor гарантируют, что клиент получит все события в нужном порядке.

Этап 5. Концептуальная схема и целостный обзор

Система представляется в виде многоуровневой архитектуры. На периферии расположены Gateways, принимающие клиентские соединения. Gateway взаимодействует с Presence Service для определения активных устройств получателя и с Group Service для получения списка участников. Dispatcher делает durable write в commit-log, который является единственным источником правды для событий. Delivery-workers читают из commit-log или получают события через pub/sub и выполняют доставку, сохраняя статусы доставки в Message Store. Message Store даёт возможности чтения истории и согласования при повторной доставке. Media-поток обрабатывается отдельно: uploader сохраняет в object storage, возвращает ссылку, которую включают в сообщение. Push gateway интегрирован с внешними сервисами для уведомлений на мобильные устройства. Синхронная инвалидация/ack flow и метрики обеспечивают поддержку SLA. Такая архитектура обеспечивает надёжную, масштабируемую обработку сообщений и даёт четкие точки расширения.

Этап 6. Выбор технологий и sizing

Выбор технологий ориентируется на требования: Kafka-подобный durable log для commit-log; Cassandra или DynamoDB для message-store при требовании write-heavy и линейного масштабирования; PostgreSQL/Spanner — при необходимости транзакционной логики (редко требуется для чатов). Redis применяется для presence, per-device queues и кеширования последних сообщений. WebSocket / MQTT брокеры (NGINX + WebSocket, EMQX, Mosquitto или propietary gateway) используются для удержания постоянных соединений и низкой латентности пуша. Push-gateway интегрируется с APNs/FCM. Object storage (S3) и CDN для медиа.

Sizing делается на основе входных параметров: число активных пользователей (MAU/DAU), среднее число одновременных подключений, средний QPS отправки сообщений, средний размер сообщения и среднее число получателей. Формула для пропускной способности коммита в durable log: required_throughput = messages_per_sec * replication_factor. Для хранения: daily_storage ≈ messages_per_day * avg_message_size. Примерная численная иллюстрация: при 10M DAU, 20% concurrent, средний отправляемых сообщений на пользователя в день 50, avg_size 1KB, получателей в среднем 1.5, потребуется пропускная способность commit-log порядка 10M50/86400 ≈ 5.8k msg/s (при этом с репликацией и overhead ориентируемся на ≈ 20k ops/s). Message-store размер в день: 10M501KB1.5 ≈ 750GB; с репликацией и overhead под 2TB в день; retention 30 дней — порядка 60TB. Эти оценки показывают необходимость shard- и tiered-storage (горячие последние дни в Cassandra / SSD, архивы в object storage). Количество WebSocket gateway’ев рассчитывается исходя из допустимого числа соединений на ноду и пикового трафика; современные инстансы поддерживают десятки тысяч persistent connections.

Выбор по гарантиям: если достаточно at-least-once, Kafka+consumer-groups + idempotent writes на клиенте — простой путь. Для stronger semantics применяют exactly-once processing с эффектацией последовательных обновлений в message-store с помощью transactional writes или conditional writes.

Этап 7. Дополнительные вопросы, расширения и эксплуатационные аспекты

Система допускает множество дополнений. End-to-End шифрование требует изменения контрактов: сервер перестаёт иметь доступ к расшифрованным payload’ам, хранения метаданных и доставки осуществляются по зашифрованным blob’ам; sync и поиск усложняются. Масштабирование больших групп оптимизируется через hybrid fan-out: для небольших групп — eager fan-out (разовая запись в per-recipient queues), для очень больших групп — lazy fan-out (recipient pulls recent offsets), а для мега-групп — use of multicast-like delivery через push-to-topic + client-side filtering. Hot-group и hot-user detection помогают выделять горячие участки нагрузки в отдельные шарды. Для борьбы с spam — комбинация rate limiting на стороне sender, content-moderation pipelines и machine-learning фильтров.

Операционно важны мониторинг и alerting: delivery latency, ack-rates, queue-latency, commit-log lag, offline message queue sizes, push-failure rates и hotspot detection. Тестирование отказов и chaos engineering критичны для гарантирования SLA; регулярные drills для push-gateway’а и durable-log failover обязательны. Политика бэкапов для message-store зависит от требований к RPO/RTO; для быстрого recovery используются snapshot’ы и репликация.

Наконец, UX-аспекты: клиентская логика должна обрабатывать дубли, обеспечивать локальную видимость отправки (optimistic UI), корректно показывать статус доставлено/прочитано с учётом нескольких устройств, уметь синхронизироваться после долгого offline периода и уважать privacy settings пользователя.

News Feed

Fan-in/fan-out, push vs pull, хранение ленты.

Этап 1. Постановка задачи и исходный контекст

Интервьюер формулирует задачу: спроектировать систему формирования и доставки ленты новостей (news feed) уровня соцсети. Система должна принимать события (посты, репоcты, лайки, комментарии), агрегировать их в персональные ленты пользователей и обеспечивать быструю доставку и прокрутку (infinite scroll). Дополнительные требования: персонализация ранжирования, свежесть контента, масштабирование до миллионов активных пользователей, поддержка как текстовых сообщений, так и медиа, гарантия приемлемой латентности для первой страницы и бесшовной подгрузки истории. Нефункциональные требования включают высокую доступность, устойчивость к пиковому трафику, предсказуемое время отклика, возможность A/B тестирования ранжировщиков и экономное использование ресурсов при больших фан-аутах (множество подписчиков у одного автора).

Интервьюер на этом этапе заканчивает ввод, инициатива переходит к кандидату.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат задаёт вопросы, чтобы определить границы и приоритеты. Что важнее для продукта: абсолютная свежесть или релевантность ранжирования? Нужно ли показывать «activities» от друзей в строгом хронологическом порядке или применять персонифицированный рейтинг? Какая допустимая задержка от создания события до его появления в ленте: сотни миллисекунд, секунды или минуты? Какова типичная структура социальной графа: большинство пользователей имеют небольшое число подписок или есть много «звёзд» с миллионами подписчиков? Будут ли активные push-уведомления для новых постов или достаточно pull/refresh по требованию? Нужны ли глобальные агрегаты («тренды», «топ за день»), и какова политика ретеншна для истории?

Формализуя нефункциональные характеристики, фиксируют цели: низкая латентность загрузки первой страницы, высокая пропускная способность для фан-аута, приемлемая eventual consistency между источником и пользовательской лентой, гибкость в обновлении ранжировщика, а также возможность поведения при отказах (graceful degradation). Эти уточнения формируют основу для выбора архитектурных паттернов.

Этап 3. Границы системы и публичное API

Граница системы — сервис формирования и выдачи ленты. Публичное API включает операции: publishEvent(sourceId, event), getFeed(userId, cursor, policy) и éventuellement subscribeToRealtime(userId) для WebSocket/streaming. API возвращает упорядоченный, постраничный набор feed-items с указанием источника, метаданных ранжирования и курсором для следующей страницы. При публикации события вызывающий компонент может передавать минимальные метаданные (тип события, timestamp, «вес»), а более тяжёлые данные (медиа) хранятся отдельно и инлайнятся ссылками.

Важно надёжно отделить публичный контракт от внутренней реализации: клиент не должен знать, была ли запись сформирована при записи (push fan-out) или собрана при чтении (pull). API должен позволять переключаться между стратегиями без изменения клиентского кода.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Сердце задачи — выбор модели fan-in/fan-out и стратегия push vs pull. При публикации событие поступает в ingest-пайплайн, проходит валидацию и поступает в durable commit-log. Отсюда есть два принципиально разных подхода к формированию персональных лент.

В модели fan-out-on-write (push) система сразу распространяет событие во все персональные ленты подписчиков: для каждого follower создаётся запись в per-user store (materialized feed). Это делает чтение чрезвычайно быстрым — getFeed просто читает precomputed список. Но при высоком числе подписчиков одного источника (celebrity) write-амплификация становится дорогой: публикация одного поста генерирует огромный объём записей. Решение — гибрид: выполнять eager fan-out для авторов с небольшим числом подписчиков, а для «звёзд» переключаться на read-time сборку.

В модели fan-out-on-read (pull) при запросе ленты система собирает события с источников, которых читатель подписан, и ранжирует их на лету. Это экономит записи при публикации, но увеличивает задержку чтения и вычислительную нагрузку на read-path, особенно при большом числе подписок. Практически всегда используют гибрид: материализованные feeds для подавляющего большинства пользователей и lazy fetch для аккаунтов с огромной аудиторией.

Ранжирование делится на два этапа. Offline-пайплайн (batch) формирует базовые сигналы и обученные модели рекомендаций, предвычисляет candidate set для пользователей или для сегментов. Online-компонент (real-time scorer) применяет скоринг с учётом свежих сигналов (взаимодействия в последних минутах, временные boost’ы, демографические фильтры) и ранжирует кандидатов в момент выдачи. Компонент realtime scoring должен быть быстрым и легковесным; тяжёлые ML-модели выполняются в background и результаты кешируются.

Хранение ленты может быть организовано как per-user materialized view (row per user with ordered list), либо как inverted index в key-value сторе, где ключ = userId, value = списочный указатель на feed-items; вместо хранения полного payload’а хранятся ссылки на события в центральном event-store и на медиа в object storage. Такой подход упрощает инвалидации и сжатие; при удалении контента проще пометить событие как удалённое, не удаляя все зеркала.

Для борьбы с дублированием и order-issues применяют per-user cursors и deterministic ids (eventId, sequence per source). Параллелизм и шардинг социал-графа выполняют по userId, а для равномерного распределения нагрузки используют согласованное хеширование и переназначение шардов при росте.

Realtime delivery опирается на subscription layer: фронт для WebSocket/HTTP-streaming получает события push из materialized feed (или через pub/sub) и отправляет их подключённым клиентам. Для push-уведомлений мобильным устройствам используется отдельный push-gateway. Для offline-пользователей события помещаются в per-device queue до момента доставки.

При больших всплесках нагрузки система должна применять backpressure: throttling на publish (rate limiting для источников), batching публикаций, а также адаптивную деградацию ранжировщика (упрощённый ранжировщик при пиковой нагрузке).

Этап 5. Концептуальная архитектура и целостный обзор

Целостная схема включает следующие зоны: ingest-пайплайн (API gateway → validation → commit-log), materialization layer (fan-out workers и per-user feed storage), ranking layer (offline feature computation + online scoring), storage (event-store, per-user feed store, media storage), realtime delivery (subscription gateways, push-gateway), и monitoring/analytics. Commit-log (Kafka или аналог) служит источником правды и даёт возможность повторной переработки событий для переиндексации лент при изменении ранжировщика. Materialization layer читает из commit-log и актуализирует per-user feeds; он же выполняет hybrid-решение: eager fan-out для «малых» авторов, флаг lazy для «горячих» аккаунтов.

Выдача ленты выглядит просто для клиента — чтение materialized view с последующей дополнительной онлайновой переоценкой топ-N кандидатов. Для мультиязых/мультирегионональных развертываний materialized feeds хранятся в региональных кластерах, а commit-log реплицируется либо через geo-replication, либо через региональные пайплайны с согласованным eventual consistency.

Этап 6. Выбор технологий и оценка размера системы

Для commit-log логичным выбором будет Kafka-подобное решение: обеспечивает высокую пропускную способность, retention и возможность переиграть события. Для materialized per-user feeds подходят хранилища, которые эффективно работают с append/prepend и range-reads: wide-column базы (Cassandra, Scylla), key-value сторы с поддержкой списков (Redis Streams для горячих данных, но при большом retention лучше Cassandra). Для медиа — object storage + CDN. Для realtime scoring и низкой латентности используют in-memory сервисы и feature caches (Redis/KeyDB), а для heavy ML — онлайн feature store и fast API для получения признаков (Feast-подобные решения).

Sizing начинается с расчёта: число публикаций в секунду, средний размер события, среднее число фоловеров источника и retention feed-элементов на пользователя. Общий объём записей при eager fan-out приближается к публикациям × average_followers, поэтому для сервисов с большой долей «звёзд» hybrid-подход существенно снижает нагрузку. Хранилище per-user feed должно выдерживать QPS чтений пиковых часов; для первой страницы важно обеспечить латентность мс-уровня, поэтому hot feeds держат в памяти или на SSD. Репликация, резервирование и мониторинг latency, lag в materialization workers и consumer lag в commit-log критичны для sizing.

Важный практический паттерн — стадирование: хранить только N последних элементов в materialized feed (sliding window), старую историю перемещать в холодное хранилище и доставлять при demand-пагинации. Это экономит оперативную память и упрощает инвалидации.

Этап 7. Дополнительные вопросы, расширения и эксплуатационные аспекты

Система дает возможности для множества улучшений. Персонализация может эволюционировать от простых heuristics (recency, popularity, social proximity) к сложным ML-рестраферным моделям с bandit/A-B testing. Обеспечение fairness и diversity — важные требования продукта, которые вводят дополнительные сигналы в ранжировщик. Конфиденциальность и модерация — фильтрация контента и «take down» операции требуют быстрых инвализаций materialized feeds по всему кластеру; здесь commit-log с быстрым набором worker-ов и topic для moderation-events помогает.

Для борьбы с накрутками и спамом добавляют throttling на create-event, signal-based detection и adaptive penalties. Для повышения UX применяют optimistic updates на клиенте и клиентскую агрегацию (например, показывать «X новых постов», не пытаясь сразу подтянуть всё).

Операционная сторона включает мониторинг tail-latency, consumer lag, number of hot keys, memory pressure на feed-store, size of per-user feeds и ratio eager-vs-lazy fan-out. Chaos engineering и регулярные проверки корректности ранжирования при обновлении ML-моделей обязательны. Архитектура должна позволять безболезненно «переиграть» события через commit-log для ретроактуализации ранжирования.

Design a Logging System

Централизованный сбор логов (ELK / ClickHouse), ingestion pipeline.

Этап 1. Постановка задачи и исходный контекст

Интервьюер ставит задачу: проектировать централизованный лог-ингест и хранение для больших систем. Система должна принимать логи и события от тысяч/миллионов источников (приложения, контейнеры, сетевые устройства, облачные сервисы), обеспечивать надёжный сбор, парсинг и обогащение, давать быстрый полнотекстовый поиск и агрегации в реальном времени, поддерживать аналитические запросы по историческим данным, давать возможности для алертинга и интеграции с downstream-pipelines. Требования включают: устойчивая ингерсия при пиковых нагрузках, гарантии доставки (минимум at-least-once), масштабируемое хранение с управлением ретеншном, низкая латентность для «первого поиска» недавно пришедших логов, дешёвое холодное хранение старых данных и обеспечение безопасности/аудита. После объявления контекста интервьюер обычно молчит; кандидат уточняет границы и характеристики.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат задаёт набор уточняющих вопросов, определяющих архитектурный вектор. Нужны ли в логах структурированные поля (JSON) или свободный текст; какие объёмы ожидаются (EPS и средний размер записи); требования по retention для разных типов логов (audit — месяцы/годы, app debug — дни); что важнее — полнотекстовый поиск или аналитические агрегации на больших объёмах; требуется ли realtime-alerting с задержкой <1–5 секунд; какова допустимая стоимость хранения; какова политика соответствия (GDPR, HIPAA). От ответа на эти вопросы зависит выбор движков: Elasticsearch/Opensearch хорош для полнотекстовых поисков и интерактивного анализа, ClickHouse эффективен для высокоскоростных аналитических агрегаций и хранения, object-storage (S3) — для холодного архива и сырого лога. Не менее важно уточнить требования по SLA/латентности для поиска свежих логов, и следует ли поддерживать reprocessing (переиграть ингерс-лог) при изменении парсинга или ранжирования.

Ключевые нефункциональные характеристики фиксируются так: высокая доступность ingestion-пайплайна, устойчивость к всплескам с приёмом значительной доли пиков, масштабируемость хранилища и возможности дешёвой архивации, предсказуемая стоимость и наблюдаемость (метрики lag, drop, processing time).

Этап 3. Границы системы и публичный API

Границы системы — входные точки для продюсеров логов и внешнего потребителя (search/analytics/alerting). Публичный контракт включает ingestion API (HTTP/S endpoint, syslog/UDP/TCP, beats/forwarder, gRPC), схему для batch-пушей и streaming (безопасный endpoint и возможности ретраев). Для downstream-потребителей публичный API включает query API (search by time/span/fields, aggregation queries, scroll/pagination), subscription API для реального времени (websocket/push on match), и управление retention/reindex/ILM. Также нужны API для schema registry (если используем Avro/Protobuf для структурированных логов), а также endpoints для health/metrics и для административных действий (удаление, экспорт).

API должен выражать, что ингерс — асинхронен: ответ на ingestion даёт подтверждение приёма (accepted/queued) и cursor/offset, а не мгновенную индексацию. Это позволяет упаковать ожидания по латентности.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Сердце системы — ingestion pipeline, построенный вокруг устойчивого commit-log (обычно Kafka или совместимый). На периферии находятся collectors/agents (Beats, Fluentd, Vector, Filebeat, syslog-ng), которые аггрегируют, буферизуют и шипят данные в ingress brokers. Сюда же входят cloud-native shippers (CloudWatch→Kinesis→Kafka), sidecar’ы в контейнерах и сетевые лог-прокси. Этот слой ответственен за backpressure и локальные ретраи — при падении центрального брокера агенты буферизуют локально.

Далее поток идёт в парсеры/normalizers: stream processors (Logstash, Fluent Bit, Kafka Streams, Apache Flink, or a Beam pipeline) выполняют parsing (JSON парсинг, grok), enrichment (geo-IP, service metadata, kubernetes/pod labels), фильтрацию (redaction PII) и категоризацию. Парсинг может быть тяжёлым — поэтому лучше делать его в распределённом стрим-слое, где можно горизонтально масштабироваться и переигрывать события при обновлении схем.

После нормализации данные идут в две основные ветви хранения: fast-search store и analytics store. Fast-search (hot path) — это полнотекстовый индекс (Elasticsearch / OpenSearch), оптимизированный на быстрый поиск по времени и полям. Analytics store (OLAP) — ClickHouse или Parquet/Delta Lake на S3, оптимизированный на массовые агрегации и дешёвое хранение больших объёмов. При этом сырые события и парсинг-оригиналы также отправляются в объектное хранилище (S3) для долговременного архива и возможности переобработки.

Реалтайм-алертинг реализуется на стрим-слое: CEP/streaming engine смотрит за паттернами и пишет alert-events в алерт-систему (AlertManager, PagerDuty) и/или в ES для fast-query. Для исследований и интерактивных дашбордов используется связка Kibana/Grafana, при этом heavy analytics выполняются в ClickHouse (поддерживает быстрые group-by по большим объёмам).

Ключевые инженерные вопросы на этом этапе: partitioning (по source-id, tenant, time), ordering (в пределах partition), idempotency (message-id для дедупа), schema evolution (registry + versioning), задержки (SLA по приёму→доступности в search), гарантия доставки (at-least-once + dedup), и backpressure (agents + brokers + circuit breakers).

Stampede/пики решаются буферизацией на нескольких уровнях: agent-side disk buffer, Kafka topic с retention и многими consumer groups, и масштабирование парсинг/ingest-workers. Если тема — cost, часть данных можно напрямую шорт-листить: критичные логи в ES, менее критичные в ClickHouse или только в S3.

Этап 5. Концептуальная архитектура и целостный обзор

В целостном виде архитектура выглядит как многоступенчатый конвейер: источники → collectors/agents (buffer, backpressure) → ingress broker (Kafka) → stream processors (parse, enrich, filter) → fast index (Elasticsearch) + analytics store (ClickHouse) + cold archive (S3 / HDFS). Над этим слоем находятся realtime alerting services, query/visualization layer (Kibana/Grafana/Custom UI), и админ-панель для управления policies/ILM/schema. Commit-log (Kafka) служит одним источником правды и даёт возможность переиграть события для исправления парсинга, регенерации индексных представлений или репроцессинга аналитики.

Для безопасности и мульти-тенантности вписываются шлюзы аутентификации/authorization, TLS, per-tenant topics/indices и RBAC. Для наблюдаемости system emits metrics: ingest-lag, consumer-lag, parsing-errors, index-rate, disk-usage, query-latency.

Этап 6. Выбор технологий и sizing (с примером расчёта)

Технологические выборы ориентируются на требования. Для collectors используют Beats/Vector/Fluent Bit; для брокера — Apache Kafka или managed Kafka (Confluent, MSK); для stream processing — Flink/Beam/Flink SQL или Kafka Streams; для быстрого поиска — Elasticsearch/OpenSearch; для аналитики — ClickHouse; для cold storage — S3 + Parquet/ORC; для alerting — Prometheus Alertmanager + custom rule engine; для schema registry — Confluent Schema Registry (Avro/Protobuf). Менеджмент и автоматизация — Kubernetes/Helm и оператор для ES/ClickHouse/Kafka, CI для schema changes.

Чтобы показать подход к sizing, приведу пример: предположим 100k событий в секунду, средний размер события 1 KB. Посчитаем поток сырых данных в сутки и оценим индексную нагрузку.

Шаги расчёта (цифры приведены детерминировано):

EPS = 100 000 событий/с.

Средний размер = 1 000 байт.

Байтов в секунду = EPS × размер = 100 000 × 1 000 = 100 000 000 байт/с.

Байтов в сутки = 100 000 000 × 86 400 = 8 640 000 000 000 байт.

Это 8.64 TB в сутки (десятичные ТБ, 1 TB = 10^12 байт).

Для индексирования в Elasticsearch обычно учитывают фактор overhead для inverted-index / replicas / metadata — практическая оценка 2–4× от сырых данных в зависимости от количества полей, анализаторов и репликации. При 3× overhead ежедневный объём индексируемых данных = 8.64 TB × 3 = 25.92 TB/day.

Отсюда выводы по sizing:

Hot storage (ES) растёт очень быстро; для 30-дневного retention потребуется ≈ 25.92 × 30 ≈ 777.6 TB индекса (без учета реплик и свободного запаса) — это повод держать в ES только последние N дней и делать tiered storage: горячие данные в ES (например последние 7 дней), тёплые в ClickHouse/SSD, холодные в S3 (Parquet).

Kafka throughput должен выдерживать ~100 MB/s входа и репликацию; планирование partition-count и disk throughput критично.

ClickHouse подходит для дешёвых агрегаций: компактное хранение Parquet и эффективная компрессия сильно уменьшают объём долгосрочного хранения.

Количество узлов ES зависит от target IOPS, heap sizing правил (для ES — минимизировать heap, больше RAM для filesystem cache) и репликации; расчёт узлов делается на основе 1) ожидаемой индексации/sec, 2) сред. размера сегментов, 3) retention и 4) оперативной памяти для caching.

Эти численные примеры показывают, почему для больших объёмов логов комбинируют движки: ES для fast-search последних данных, ClickHouse для аналитики и S3 для дешёвого архива.

Этап 7. Дополнительные расширения, эксплуатация и безопасность

Наконец, обсуждаем эксплуатационные и расширяющие аспекты. Schema evolution: использовать schema registry, версионировать парсеры и хранить raw-payload для переобработки. Deduplication: предусмотреть message-id и логику дедупа в стрим-слое. TL;DR по отказам: agents с disk buffer + Kafka с репликами + idempotent consumers + возможность переиграть темы. Для защиты приватных данных — redaction pipeline (PII masking) выполняется до записи в индекс; хранение raw-логов в зашифрованном виде и контроль доступа к ним. Monitoring: метрики ingest-latency, parsing-errors, consumer-lag, index-failure-rate, disk/IOPS; логирование самой лог-инфраструктуры отдельно и выделенно. Disaster recovery: snapshot ES/ClickHouse + репликации Kafka topics + объектные snapshot’ы на S3.

Операционные практики включают: ILM/curation для ES (rollover, shrink, delete), автоматический переход индексов между hot/warm/cold tiers, lifecycle для ClickHouse/Parquet архива, регулярные тесты переигрывания событий и проверка возможности reindex, автоматическое масштабирование consumer groups, и security audits. Для multi-tenant deployments — строгое изоляция данных по tenant-id (topics, indices, access control), rate-limiting per-tenant и мониторинг затрат.

Search Autocomplete

Система подсказок с высокой скоростью отклика.

Этап 1. Постановка задачи и контекст

Интервьюер ставит задачу: спроектировать службу автодополнения для поисковой строки, которая возвращает набор подсказок за очень низкое время отклика (целевые SLO — десятки миллисекунд для p95/p99). Система должна обрабатывать сотни тысяч или миллионы запросов в секунду, обеспечивать релевантность подсказок (popularity, personalization, recency), корректно работать при опечатках и частичных вводах, уважать фильтры безопасности (фильтрация матерных/запрещённых выражений) и позволять быструю инвалидацию/обновление подсказок при поступлении нового контента. Дополнительно важно хранение телеметрии (CTR подсказок, последующий поисковый запрос), A/B тестирование ранжировщиков и GDPR-совместимость при персонализации. После постановки интервьюер замолкает; кандидат переходит к формализации.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат формализует функциональные и нефункциональные требования и задаёт уточнения, которые определяют архитектуру. Вопросы касаются допустимой латентности на p50/p95/p99; объёма QPS; допускаемых trade-offs между свежестью и скоростью; уровня персонализации (анонимная подсказка vs per-user); требуемой устойчивости к опечаткам; политики по обновлению словаря и частоте добавления новых фраз; требований к многокультурности и мульти-язычности; и допустимой стоимости (in-memory решение дорогé, но быстро). На основании ответов формируются целевые характеристики: ultra-low latency на read-path, eventual or near-real-time freshness (seconds→minutes), high throughput, per-query ranking с быстрым ранжированием и малой вычислительной нагрузкой.

Ключевое архитектурное решение уже на этом этапе: разделение на быстрый read-path (latency-critical)—использующий пред-вычисленные структуры и кэш, и менее критичный write-path для обновления словаря и сигналов ранжирования. Это диктует выбор data structures и потоков обновления.

Этап 3. Границы системы и публичный API

Граница системы — HTTP/gRPC интерфейс для клиентов (web, mobile, backend), плюс административные API для управления словарём и метриками.

Примеры контрактов:

GET /v1/autocomplete?q=nyc%20wea&userId=123&locale=en-US&max=10
Response:
{
«query»: «nyc wea»,
«suggestions»: [
{«text»:»nyc weather», «type»:»query», «score»: 98.3, «source»:»global_popular»},
{«text»:»nyc waterfront restaurants», «type»:»entity», «score»: 87.1, «source»:»local_index»}
],
«meta»: {«served_from»:»edge_cache»,»latency_ms»:6}
}

POST /v1/suggests/bulk_update
body: {updates: […]} // admin API for adding/removing phrases

POST /v1/telemetry
body: {userId, query, suggestionText, clicked}

Get-операция должна быть синхронной и укладываться в заданный SLO; админ-операции асинхронны — они пускают изменения в ingestion pipeline.

Граница системы также подразумевает ответственность за нормализацию входа (lowercase, unicode normalization, tokenization), за rate limiting и за базовую фильтрацию. Клиент не должен знать, использовался ли trie, FST, или search engine внутри; контракт остаётся стабильным.

Этап 4. Проектирование: happy path и exceptional flows; компоненты и потоки данных

Happy path для запроса простой: пользователь вводит префикс, клиент посылает /autocomplete, система нормализует префикс, проверяет edge/region cache (в память CDN/edge), затем обращается к локальному suggestion service. Suggestion service читает пред-вычисленные структуры (например FST/finite-state transducer, trie-индексы или inmemory priority lists), получает candidate set и применяет lightweight re-scoring (weighting by popularity, personalization signals, recency boosts, business rules). Результат кэшируется на лепестковом уровне (edge CDN, per-node LRU) и возвращается клиенту.

Основные компоненты:

• Frontend gateways (terminate TLS, auth, basic throttling) и edge caches, сокращающие путь для «горячих» префиксов.
• Suggestion service, реализующий latency-critical read-path: in-memory data structures (FST, prefix trie, or compressed tries stored in memory-mapped files), per-shard caches и fast scorer.
• Offline ingestion pipeline: прием обновлений (popularity events, new phrases, deletions) через commit-log (Kafka), батчевые генераторы FST/indices и incremental updaters (near-real-time).
• Realtime updater/nearline buffer: для немедленной видимости новых фраз поддерживается small in-memory overlay (log-structured memtable) или write-through to fast index; периодическая сшивка overlay → main FST.
• Persistance: backing store for phrase metadata (counts, timestamps, signals) — key-value store или OLAP store.
• Telemetry/analytics: сбор CTR, abandonment, latencies; pipeline feeding ML models for ranking.
• Admin services: profanity lists, blacklists, synonym management.

Выбор структуры данных определяет эффективность. Для дешёвых стоимостных reads эффективен FST (Lucene’s FST), который компактно хранит множество строк и позволяет быстро делать prefix lookup и буферизированную итерацию кандидатов. Для случаев, когда требуется выдать не только exact-prefix, но и fuzzy/typo-tolerant suggestions, используют сочетание FST + n-gram индекс, либо implement Levenshtein automata on the FST, либо предгенерируют n-grams/edge-ngrams в индексе (на чтение это будет быстрый lookup).

Ранжирование: важно держать основной скоринг лёгким: score = α·popularity + β·personalization + γ·recency + δ·business_boost. Сигналы popularity/recency можно инкрементировать в streaming pipeline; personalization сигналы запрашиваются из per-user store, но их извлечение должно быть быстрым (кэши, precomputed user preferences). Тяжёлые ML-модели применяют офлайн или в online-re-rank только на top-K кандидатов (K small, например 50→re-rank→return top 10).

Опечатки и fuzzy matching: варианты реализации разных степеней сложности. Простая, быстрая техника — edge-ngrams: хранить для каждой фразы ее префиксные n-грамы (tri/bi-grams) и при вводе искать по ним. Более точные варианты — Levenshtein automata intersected with FST (поддерживается в Lucene) или использование BK-trees для spell correction. Компромисс: либо дать очень быстрые prefix suggestions с limited fuzzy, либо тратить CPU/latency на глубокий fuzzy-lookup; обычно практикуют hybrid: prefix strict matching + lightweight fuzzy fallback.

Обновление данных и freshness: ingestion pipeline собирает имплицитные сигналы (search logs, clicks) → events → commit-log → streaming counter updates (increment popularity in real-time DB) → periodic rebuild of on-disk/mmapped structures (FST) или incremental update via small delta-FSTs merged regularly. Для immediacy поддерживают in-memory delta layer: new phrases go to memtable visible to reads, and background process compacts memtable into main FST every N seconds/minutes. Это даёт near-real-time видимость без перегенерации всей структуры.

Exceptional flows: сетевые задержки, кеш-мисс, перегрузка backends. При недоступности suggestion-service отвечают из edge cache или возвращают пустой набор с контролируемым fallthrough. Для защиты от широких атак и «helloworld»-пиков — rate limiting и per-key hot-key protection: если префикс слишком «горяч», используем precomputed top suggestions и ограничиваем expensive fuzzy attempts.

Этап 5. Концептуальная схема и целостный обзор

В целом система делится на два логических слоя: latency-critical read layer и asynchronous write/ingest layer. Read layer состоит из front gateways, edge caches (CDN / per-region cache), suggestion nodes (sharded, in-memory structures), и per-node caches. Write layer — telemetry collectors, event stream (Kafka), stream processors (real-time counters), offline batch jobs (rebuild FSTs, train ranking models), и admin pipelines (blacklist/synonym updates). Persistance включает columnar/kv store для metadata и object storage для backup of indices.

При таком дизайне путь для типичного запроса минимален: client → edge cache → suggestion node (in-mem FST) → scorer → return. Обновления проходят через stream → counters update → memtable overlay → periodic compaction. Это обеспечивает быстрое чтение и приемлемую свежесть.

Этап 6. Выбор технологий и оценка размера системы (sizing)

Технологии, часто применимые в продакшне: Lucene/Solr/Elasticsearch (completion suggester, FST), OpenSearch, Redis (for ultra-low-latency small-cache), RocksDB/LMDB as backing store, Kafka для ingestion, Flink/Kafka Streams for counters, and CDN/edge caches.

Как подходить к sizing без конкретных цифр: исходят из QPS автодополнения и целевых latency. Если система должна выдерживать 1M QPS, то чтение полностью из памяти критично; каждому suggestion-шарду ставим ограничение по числу подключений и throughput. Для FST, хранимого в памяти, важна оценка memory footprint: FST эффективен компрессией; если общий словарь из M фраз (например 100M фраз), и средний cost per phrase в FST ~ few tens bytes (зависит от overlaps and shared prefixes), общая память может быть порядка десятков гигабайт до сотен гигабайт. Для масштабируемости делим словарь на шард-ключи (hash by normalized prefix range or hash(query) with replica routing). Реплики для availability: обычно 2–3 копии sharded data.

Примерный расчёт метрики latency/throughput: допустим p95 latency target = 20ms сетевое + сервисное; suggestion node должна обслуживать lookup + scoring в пределах 10 ms. Это диктует in-memory structures и ограничение на количество вспомогательных запросов (minimize external calls). Если per-query re-rank использует heavy ML, то нужно выполнять re-rank только на top-K, к тому же re-rank model делает inference в специализированных serving nodes (GPU/CPU) с заранее закешированными фичами.

Ключевые практические правила sizing: поддерживать запас на 2–3× пиков для latencies; использовать региональные deployment для снижения сетевого RTT; хранить hot part of dictionary на RAM, warm part — on fast mmapped files; считать память на replica factor; проектировать auto-scaling для suggestion nodes по CPU/latency.

Этап 7. Дополнительные вопросы и расширения

Система предлагает много дополнительных функций и сложностей: персонализация подсказок под пользователя (history-based boosting, user segments), A/B тестирование ранжировщиков, поддержка многоязычности и локалей (separate indices or locale-aware normalization), context-aware suggestions (query + current page context), voice/ASR spelling corrections, privacy-aware ranking (обход персонализации по запросу) и safe-search filtering.

Операционные аспекты: мониторинг p50/p95/p99 latency, suggestion CTR, abandonment rate, error rate, hot-prefix heatmap; alerting on cache-evictions and high rebuild times; capability to replay logs to rebuild signals; procedures for blacklist/whitelist and rapid take-down; continuous evaluation pipeline for ranking models; chaos testing for partial failures and cold start.

Безопасность и модерация критичны: real-time profanity/PII filtering in ingestion, rate limiting to prevent abuse, and telemetry retention policies to satisfy privacy regulations.

File Storage Service (Dropbox / Google Drive Lite)

Хранение файлов, версионирование, синхронизация, шардинг.

Этап 1. Постановка задачи и контекст

Задача: спроектировать сервис для хранения пользовательских файлов с поддержкой версионирования, синхронизации между устройствами и масштабируемым хранением. Сервис должен позволять пользователям загружать и скачивать файлы разного размера, синхронизировать изменения между несколькими устройствами в реальном времени или в режиме фоновой синхронизации, предоставлять историю версий, позволять совместное использование (sharing) и обеспечивать разумную защиту данных и приватность. Нефункциональные требования включают: высокая доступность и надёжность (данные не теряются), масштабируемость по объёму и по числу операций, эффективное использование сети и диска (делта-обновления), гарантия целостности данных и возможность восстановления предыдущих версий, низкая латентность при доступе к «горячим» файлам, и экономичное долговременное хранение больших объёмов. Интерфейс должен поддерживать как веб/мобильный доступ, так и фоновые «sync» клиенты.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет поведение и приоритеты. Насколько строгими должны быть гарантии консистентности между устройствами: нужен ли строгий сериализуемый порядок или достаточно eventual consistency с механизмами разрешения конфликтов? Ожидается ли поддержка больших файлов (десятки гигабайт) и потоковой передачи (streaming)? Как часто изменяются файлы: превалируют небольшие изменения в документах или крупные медиа-файлы? Какая политика версионирования: хранить все версии бесконечно или лимитировать по количеству/времени? Нужны ли блокировки на уровне файлов (advisory locks) или optimistic concurrency с merge-стратегиями? Будет ли служба предоставлять совместное редактирование в реальном времени или достаточно синхронизации версий? Ответы на эти вопросы задают направление по выбору архитектуры: если важна низкая вероятность конфликтов и сильная согласованность, потребуется более сложная координация; если важна пропускная способность и масштаб, стоит ориентироваться на eventually consistent store с семантикой «последнее сохранение выигрывает» и поддержкой версионирования для отмен и слияния.

Ключевые нефункциональные требования фиксируются так: устойчивость и долговечность хранения (RPO≈0), горизонтальная масштабируемость по объёму и по числу клиентов, экономичное холодное хранение, оптимизация сетевого трафика при синхронизации (делта-обновления, chunking), и поддержка офлайн-клиентов с последующей репликацией.

Этап 3. Границы системы и публичный API

Границы — сервис хранения и синхронизации, видимый клиентам и администраторам. Публичный API должен покрывать основные сценарии: загрузка (upload), скачивание (download), получение метаданных, создание/удаление/переименование объектов, управление версиями (listVersions, restoreVersion), операции синхронизации (sync cursors, long-poll / push notifications), управление доступом и шаринг (grant/revoke), а также административные операции (lifecycle, retention, audit logs).

Примеры контрактов: PUT /files/{user}/{path}?uploadId=… для chunked multipart upload; GET /files/{user}/{path}?version=… для получения конкретной версии; POST /sync/push {changes, clientCursor, deviceId} возвращает serverCursor и конфликтные файлы; GET /sync/pull?since=cursor возвращает дифф изменений. API должен быть идемпотентен по возможности: клиент при повторной загрузке с тем же uploadId продолжит процесс, при повторном push изменений сервер должен обрабатывать отдельно существующие идентификаторы изменения.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Проектирование начинается с happy path: пользователь создаёт или изменяет файл на устройстве. Клиент определяет изменения и передаёт дельту в сервис. Для эффективного использования сети и диска применяется разбиение на блоки (chunking) и адресуемое по содержанию хранение чанков (content-addressable storage, CAS). Клиент делит файл на фиксированные или переменные по контенту чанки, вычисляет их хеши, запрашивает у сервера какие чанки уже есть (dedup), загружает отсутствующие чанки, затем отправляет метаданные (manifest) с указанием порядка чанков и метаданных файла. Сервер, получив manifest, создаёт или обновляет объект файла, сохраняет ссылку на набор чанков, создаёт новую версию и обновляет метаданные пользователя.

Синхронизация между устройствами основана на механизме cursors или change-log: клиент периодически запрашивает изменения с момента своей последней синхронизации или получает push-уведомление. При приходе изменений клиент запрашивает нужные версии/чанки и применяет их локально. При одновременных модификациях применяется стратегия разрешения конфликтов: оптимистичная стратегия с версионными метками и автоматическим merge для текстовых файлов (via operational transforms или CRDT для real-time), либо сохранение обеих версий и уведомление пользователя о конфликте. Для многих приложений выбран pragmatic подход: если файл не является документом для real-time совместной работы, сохранять обе версии (conflict copy) и позволять пользователю/клиенту разрешить конфликт.

Компоненты системы складываются из нескольких групп. Persistence layer включает chunk-storage (объектное хранилище: S3 или его эквиваленты) и metadata store (ключ-значение или wide-column DB для маппинга файлов→манифестов, прав доступа, индексов). Chunk-store хранит неизменяемые чанки, оптимизированы по throughput и долговечности. Metadata store хранит и индексирует древовидную структуру каталогов, ACL, версии и cursors. Sync service отвечает за ingestion client-изменений, запись в write-ahead log (commit-log) и генерацию уведомлений для подписанных устройств. Background services выполняют сборку версий, очистку orphanChunks (гc), lifecycle и tiering (перемещение старых версий в холодный архив). Authentication/authorization и audit trail — отдельные сервисы. Кроме того, требуется notification service для push-уведомлений, CDN для отдачи больших файлов и streaming service для медиа.

Exceptional flows: потеря соединения при upload → поддержка resumable uploads (uploadId + chunkId), недоступность chunk-store → agents буферизуют с помощью local cache или retry-queue, конфликт версий → автогенерация conflict-file и уведомление пользователя, попытки злоупотреблений → rate-limiting и quota checks. Издержки масштабирования: горячие файлы (часто запрашиваемые) требуют кешей (edge CDN, Redis), холодные — перенос в дешёвую долговременную память.

Шардирование и балансировка. Metadata store шардируется по userId или по namespace, давая силуэшную изоляцию пользователей. Chunk-store шардируется по хешу чанка; хеш-ориентированное распределение даёт естественный баланс и позволяет эффективно дедуплицировать повторяющиеся данные у разных пользователей. Для масштабируемости каталоги больших аккаунтов (с миллионами объектов) необходимо делать paging и ленивую инициализацию каталогов, а также оптимизировать операции list с помощью precomputed index shards.

Версионирование. Версии реализуются как неизменяемые manifest-объекты, указывающие на список чанков и метаданные. При изменении создаётся новый manifest и ссылка на него сохраняется в metadata store. Политика хранения версий управляется lifecycle: хранить последние N версий или все версии за последние T дней; старые версии могут быть перемещены в холодный архив и/или удалены по политике.

Безопасность и целостность. Все чанки и manifest подписываются хешами; при восстановлении/передаче клиент может проверить целостность. Для защиты приватности применяют шифрование на стороне сервера (server-side encryption) либо end-to-end (client-side encryption) по требованию. Access control реализуется через токены и ACL, а аудит операций логируется в отдельную систему логов.

Этап 5. Концептуальная схема и целостный обзор

В целостном виде система выглядит как набор взаимосвязанных слоёв. На входе находятся клиенты, которые через API/SDK общаются с front-end gateway. Gateway выполняет аутентификацию, базовую валидацию и маршрутизацию в sync-service. Sync-service пишет операции в commit-log и обращается к metadata-store и chunk-API. Chunk-API взаимодействует с объектным хранилищем для записи и чтения чанков и с кешами для ускорения отдачи. Metadata-store хранит файловую структуру и версии; он шардирован по userId для масштабирования. Background workers читают commit-log и выполняют асинхронные задачи: сбор garbage (удаление неиспользуемых чанков), tiering, репликация и генерацию уведомлений. Notification-service доставляет события на устройства и триггерит pull. CDN/edge ноды обеспечивают быструю доставку больших файлов. Monitoring и alerting покрывают все слои: latency upload/download, storage utilization, gc-lag, error-rates. Такая архитектура разделяет долговременное хранение неизменяемых чанков и динамическую метаинформацию, что упрощает дедупликацию, версионирование и масштабирование.

Этап 6. Выбор технологий и оценка размера системы (sizing)

Технологии выбирают с учётом требований: объектное хранилище S3 (или S3-совместимое) как основа chunk-store для долговечности и cheap cold storage; для горячего слоя — SSD-backed storage или специализированные распределённые хранилища (Ceph, MinIO, Google Cloud Storage) с шардированием по хешу. Metadata store — выбор между DynamoDB/Cassandra/Spanner для горизонтальной масштабируемости и низкой латентности; PostgreSQL/MySQL подходят для меньших конфигураций, но масштабирование сложнее. Commit-log — Kafka или управляемые аналоги для гарантий доставки и переигрывания изменений. Для индексов и поиска (по имени, тегам) применим Elasticsearch или dedicated search service. Для edge-каша и распределённого lock-менеджмента используют Redis (для short-lived locks, rate-limits, sessions). Для push-уведомлений — APNs/FCM и internal push gateway.

Sizing: начнём с входных параметров: число пользователей, среднее число файлов на пользователя, средний размер файла, процент активных пользователей, QPS операций. Приведу примерный расчёт: 10M пользователей, среднее файлов на аккаунт 200, средний размер файла 5 MB. Объём данных ≈ 10M * 200 * 5MB = 10M * 1GB = 10 PB. Учитывая репликацию, overhead метаданных и index, итоговый raw storage может быть в районе 20–30 PB. Для такого объёма S3-стратегия с tiered storage необходима: горячие последние изменения держать на SSD/fast object storage, холодные перемещать в Glacier/Archive. По IOPS: если 1% файлов запросы в сутки, это 100k * 200 = 20M file accesses/day ≈ 230 ops/s — требует большого кеширования на edge. Для throughput при параллельных загрузках оцените пиковую QPS и планируйте трансферную пропускную способность и количество воркеров.

Dedup и chunking существенно сокращают объём: при высокой доле повторяющихся данных (например, резервные копии ОС или медиа) дедупликация может снизить объём хранимых данных в разы. Chunk-size trade-off: маленькие чанки дают лучшую дедупликацию и параллелизм, но больше метаданных; большие — меньше метаданных, но хуже дедупликация. Часто выбирают переменные по контенту чанки (content-defined chunking, CDC) с средней величиной 8–64 KB для хорошего компромисса.

Операционные параметры: поддерживать запас по throughput ~2–3× пиков для обеспечения устойчивости при пиковых бёрстах; мониторить gc latency (удаление старых версий) и неизменно иметь процедуры восстановления. RPO/RTO зависят от SLA — для критичных данных делают гео-репликацию и snapshot-ы.

Этап 7. Дополнительные вопросы и расширения

Наконец, варианты расширений и усложнений. Поддержка совместного редактирования в реальном времени требует реализации OT/CRDT и более сложной модели хранения изменений; это значительно меняет архитектуру, так как сервер должен поддерживать fine-grained ops и трансформации. End-to-end шифрование (E2EE) на клиенте существенно усложняет дедупликацию и серверные операции (сервер не видит содержимого, значит не может дедуплицировать или индексировать), но обеспечивает высокую приватность; практическая компромиссная модель — client-side encryption с optional server-side metadata indexing. Multi-tenant enterprise features (audit, compliance, retention policies, legal hold) требуют расширений metadata-store и интеграции с SIEM.

Другие полезные темы для продакшн-решения: версии хранения для efficient snapshotting (дедуплицированные manifests), квоты и billing, экспорты/миграции, интеграция с файловыми протоколами (WebDAV, SMB), CDN-integrated streaming для больших медиа, интеграция с CDN+signed-URLs для безопасного доступа, и тонкая моделирование прав доступа для совместного использования. Операционные практики: end-to-end тестирование restore-процессов, регулярные drill’ы по отказу региона, мониторинг tail-latency, и автоматизация lifecycle (tiering, cleanup) с отчетностью об экономике хранения.

Video Streaming Platform (YouTube Lite)

CDN, encoding pipeline, рекомендации (поверхностно).

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать видеоплатформу, которая принимает пользовательские видео, хранит и транскодирует их в форматы, подходящие для воспроизведения в браузере и на мобильных устройствах, раздаёт контент с низкой задержкой глобальной аудитории через CDN, обеспечивает адаптивную потоковую доставку (ABR), ведёт хранение и метаданные, а также поддерживает базовую подсказку рекомендаций. Нефункциональные требования включают возможность масштабирования до миллионов загрузок и просмотров, минимизацию задержки старта воспроизведения (startup latency), эффективное использование сети и диска, экономную долговременную архивацию исходников, надёжность и устойчивость к пиковым нагрузкам (выпуски, вирусный контент). Платформа должна обеспечивать безопасность контента и правообладательский контроль, а также базовую телеметрию (view counts, watch-time, QoS metrics).

Интервьюер завершает ввод, кандидат переходит к уточнению требований и проекту.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат формализует функциональность и определяет важные нефункциональные характеристики. Нужно понять ожидаемые SLO: p95 startup latency (например <2 s), допустимую задержку между загрузкой и доступностью в основном качестве (minutes→tens of minutes), поддерживаемые устройства и сети (mobile 3G/4G/Wi-Fi), требования к DRM или платному доступу, допустимые форматы исходников, объёмы хранения и ретеншн исходников, а также глубину рекомендаций (simple collaborative filtering vs сложная ML-пайплайн). Следует уточнить требования по live-streaming: требуется ли live или только VOD; в данном упрощённом кейсе сосредотачиваемся на VOD, упомянув отличия для live.

Нефункционально фиксируется приоритет: минимальная задержка старта и стабильная entrega для зрителя; масштабируемость отдачи и дешёвое cold-хранение исходников. Консистентность метрик и надёжность данных о просмотрах и QoS важны для биллинга и рекомендаций.

Этап 3. Границы системы и публичное API

Границы — сервисы загрузки/обработки/хранения/доставки видео, а также API для клиентов и админов. Публичный контракт включает загрузку (multipart / resumable upload), запрос метаданных и статусов обработки, запрос манифеста потоков HLS/DASH, получение статических ресурсов (thumbnails, captions), просмотр счётчиков и отправка телеметрии (player events). API для бек-офиса предоставляет операции для модерации, управления encoding profiles, lifecycle policies и получения аналитики.

Ключевая инвариантность API: клиент не зависит от внутренней реализации транскодирования или CDN; он получает m3u8/DASH manifest и готов к ABR-плееру. Для загрузки поддерживается resumable uploads с uploadId — это упрощает обработку нестабильных сетей.

Этап 4. Проектирование: happy path, exceptional flows и основные компоненты

Happy path загрузки: пользователь отправляет исходный файл на Upload Gateway. Gateway аутентифицирует, проверяет квоты и инициирует chunked/streaming загрузку в объектное хранилище (S3-like), возвращая uploadId. После загрузки manifest (metadata) помещается в job-queue для encoding pipeline. Worker подхватывает задачу, извлекает исходник, выполняет транскодирование в набор битрейтов и разрешений, генерирует сегменты и manifest’ы для HLS и/или DASH, создаёт thumbnails и субтитры (если требуется), а затем выгружает результаты в объектное хранилище, обновляет metadata store и помечает video «готово».

Воспроизведение: клиент запрашивает manifest и начинает получать сегменты через CDN. Плеер поддерживает ABR: на основе скорости сети и буфера выбирается подходящий битрейт. Для минимизации startup latency применяется быстрый первоначальный профиль (low-res first) и HTTP/2/3 prefetching. CDN обслуживает горячие части контента; origin хранится в S3 и используется при cache miss.

Ключевые компоненты формируются естественным образом: Upload Gateway (auth, quota), Object Storage (raw + encoded assets), Encoding Pipeline (job queue, workers, transcoders), Manifest/Segment Store (обычно same object storage with structured paths), CDN (edge nodes, cache policies), Metadata Service (video metadata, status, thumbnails, captions, access control), Playback Service (tokenized URLs, signed URLs, DRM gateway), Telemetry Pipeline (player events → ingestion → analytics), Moderation Service (automated checks: copyright/visual detectors + human review), and Recommendation Service (lightweight for YouTube Lite).

Exceptional flows: транскодер падает/ошибается → job возвращается в очередь и выполняется retry с backoff; исходник повреждён → пометить failed и уведомить uploader; spike загрузок → elastic scaling workers; CDN cache miss при первом просмотре → origin-read cost и потенциальное latency bump; нарушение прав → take-down через admin API и invalidation в CDN и storage.

Особенности с учётом live vs VOD: live требует низкой end-to-end latency, incremental chunking, специализированных transcoders (实时分发) и обычно отдельного pipeline; в этом разборе лишь отмечаем, что live меняет требования к pipeline и CDN (chunk-oriented low-latency delivery, WebRTC/LL-HLS).

Этап 5. Концептуальная архитектура и целостный обзор

В центре архитектуры лежит объектное хранилище для исходников и артефактов транскодирования, job-queue (Kafka/SQS) и серия worker-кластов для CPU/GPU-транскодинга. Upload Gateway принимает файлы и публикует задачи; Encoding Workers выполняют транскод, упаковку в сегменты и генерацию manifest’ов; результаты хранится в объектном хранилище по структуре виде/ resolution/bitrate/segmentIndex; Metadata Service хранит ссылки на manifest’ы и служит единой точкой запроса для клиента; CDN кэширует сегменты и manifest’ы, снижая нагрузку на origin; Telemetry собирает player events и feed’ит аналитические и recommendation pipelines.

Cache-invalidation для take-down/DMCA осуществляется через CDN purge API и пометки в Metadata Service. Для доставки защищённого контента используется signed URL + short TTL и, при необходимости, DRM gateway, выдающий лицензию.

Важный деталь: манифесты и сегменты должны быть организованы таким образом, чтобы позволять частичную замену/обновление (например, обновить один bitrate не затрагивая другие). Также хранение исходников и encoded assets разделяются lifecycle политикой: исходник можно держать в hot хранении некоторое время, затем архивировать.

Этап 6. Технологии и sizing

Технологический стек обычно включает: объектное хранилище (S3 или S3-compatible) для стабильного долговременного хранения; очередь заданий (Kafka, SQS) для coordinate encoding jobs; контейнеризированные encoding workers (FFmpeg-основа, hardware accel via NVENC/VideoCore/TPU) управляемые автоскейлингом; CDN (Cloudflare, Fastly, AWS CloudFront или свой CDN) для глобальной доставки; metadata store (NoSQL — DynamoDB/Cassandra или RDBMS для транзакционных требований); telemetry/analytics (Kafka→Flink/Beam→ClickHouse/BigQuery); signer/service for secure URLs; и опционально DRM providers.

Sizing делается через входные метрики: ожидаемая частота загрузок, средний размер исходника, среднее число просмотров в сутки, медианная длительность просмотра, процент пиковых событий. Примерный расчёт: при 1000 загрузок/сутки и среднем исходнике 500 MB объём исходников ≈ 0.5 TB/сутки. Транскодирование требует CPU/GPU ресурса, пропорционального aggregate encode time. Если средний encode на одном worker занимает 30 минут (CPU-bound), и требуется обрабатывать 1000 задач/сутки равномерно, нужен пул из порядка 30 workers одновременно работающих (30 min per job → 48 jobs/day per worker → ~21 workers; запас 2×→~42). Для пикового поведения важен autoscaling. Хранение сегментированных файлов требует учитывать мультибитрейты: если исходник 500 MB, encoded outputs могут суммарно быть 50–150% от исходника в зависимости от профилей и сегментации; нужно оценивать replication и CDN cache hit ratio.

ABR и плеер: выбирать HLS (в широкой поддержке) и/или MPEG-DASH; сегменты маленького размера (2–4 s) уменьшают startup latency и улучшают адаптации, но увеличивают overhead запросов; trade-off выбирается в зависимости от target devices.

Экономические соображения: CDN cost dominates delivery; origin bandwidth и storage — вторичные. TTL и cache-control играют роль: long cache headers для сегментов, если нет DRM или частых инсертных изменений; для платного/личного контента используют signed URLs с коротким TTL.

Этап 7. Расширения и эксплуатационные аспекты (рекомендации поверхностно)

Рекомендации и personalization. Для YouTube Lite достаточно простого рекомендационного слоя: candidate generation via popularity signals (recent views, trending), basic collaborative filtering (user history → nearest neighbours) и content-based boosting (same tags, creator). Pipeline: player events → ingestion → feature store → offline model training → online serving via feature cache + lightweight ranker. Тяжёлые модели выполняются офлайн и результаты кешируются в per-video / per-user buckets.

Мониторинг и SRE. Ключевые метрики: startup latency p50/p95/p99, time-to-first-byte for manifest and first segment, cache hit ratio at CDN, encode queue depth, failed encodes, storage growth, bandwidth cost, QoE metrics (rebuffer rate, average bitrate). Алерты на резкие изменения QoE и падение CDN hit rate. Chaos testing encoding pipeline и CDN purge/responsiveness.

Контент-безопасность и модерация. Автоматическая фильтрация (специализированные детекторы для аудио/видео), human review pipelines, takedown flows с атомарной инвалидацией manifest и purge CDN, и audit trail. Для правообладателей интеграция fingerprinting/ContentID.

Особые случаи. Для live streaming потребуются low-latency ingest (RTMP/WeBRTC), chunking с минимальными сегментами, специализированные packers; для VR/360 и больших разрешений нужны иные encoding профили и CDN-настройки (edge-caching с поддержкой byte-range).

Distributed Task Queue (как Celery / Kafka Consumer System)

Отложенные задачи, ретраи, DLQ.

Этап 1. Постановка задачи и контекст

Интервьюер ставит задачу: спроектировать распределённую систему для обработки асинхронных задач. Система должна принимать задачи от различных сервисов и пользователей, обрабатывать их с возможностью отложенного запуска и повторных попыток при сбоях, гарантировать доставку и порядок выполнения в рамках потребностей приложения, поддерживать DLQ (Dead Letter Queue) для задач, которые не удалось обработать, и обеспечивать наблюдаемость и мониторинг. Нефункциональные требования включают горизонтальную масштабируемость (обработка миллионов задач в сутки), высокую доступность и устойчивость к сбоям отдельных компонентов, возможность приоритизации задач, эффективность хранения и доставки сообщений, а также контроль количества повторных попыток и backoff стратегий. После объявления контекста интервьюер молчит, кандидат переходит к уточнению требований.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат задаёт уточняющие вопросы: какие гарантии доставки нужны — at-most-once, at-least-once или exactly-once; допустим ли reordering задач; требуется ли поддержка отложенных задач на миллисекундном или секундном уровне; сколько разных типов задач и их приоритетов; какова допустимая задержка между постановкой и выполнением задачи; нужен ли persistence задач на диске или допустим in-memory queue; допустим ли потерянный task при полном крахе системы; какая политика для DLQ — фиксированное количество ретраев или динамическая.

Нефункциональные характеристики формулируются так: высокая доступность брокеров/очередей, масштабируемость обработчиков (worker nodes), устойчивость к spike нагрузкам, предсказуемость задержки выполнения задач, auditability (ведём метрики по успехам/ошибкам/retries).

Этап 3. Границы системы и публичный API

Границы системы включают API для продюсеров задач и сервисов-обработчиков:

Producer API: enqueue_task(task_payload, type, priority, schedule_time, max_retries), поддерживает синхронный/асинхронный ответ с task_id.

Consumer API: pull_task(worker_id, batch_size), acknowledge_task(task_id, status), nack_task(task_id, reason).

Admin API: inspect queues, DLQ, retry/failure policy, purge tasks, metrics (queue length, task success/failures).

Основной контракт: producer создаёт задачу, worker забирает, выполняет и подтверждает результат; при сбое задача может быть повторена или отправлена в DLQ.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: клиент создаёт задачу → очередь (broker) ставит её в очередь → worker забирает задачу → выполняет → подтверждает успешное выполнение → broker удаляет задачу.

Retry: если обработка завершается ошибкой, задача возвращается в очередь или отправляется в delay queue с backoff.

DLQ: после превышения max_retries задача помещается в DLQ для ручного или автоматического анализа.

Основные компоненты:

Producer/Task Client — формирует задачу и публикует в broker, опционально может подписываться на статус выполнения.

Broker / Message Queue — хранит задачи, управляет delivery semantics, поддерживает persistence (Kafka, RabbitMQ, Redis Streams). Broker шардируется по типу задач или key hashing для балансировки нагрузки.

Worker Nodes — забирают задачи из очереди, выполняют их, используют concurrency/async для высокой throughput. При падении worker задача возвращается в очередь.

Scheduler / Delay Queue — управляет задачами с отложенным запуском, поддерживает таймеры и приоритетные очереди.

DLQ Handler — обрабатывает задачи, которые не удалось выполнить после заданного числа retries.

Exceptional flows: broker недоступен → producer буферизует локально или fallback; worker падает во время выполнения → задача переходит на повторное выполнение; spike нагрузка → auto-scaling worker nodes; блокировка очереди/падение persistence → задачи остаются в commit log.

Архитектурные решения: partitioning (по task type или hash), idempotency (task_id уникальный для повторных delivery), backoff strategy (fixed, exponential, jitter), visibility timeout (чтобы unacknowledged tasks возвращались), monitoring & alerting (queue depth, retry count, failed tasks).

Этап 5. Концептуальная схема и целостный обзор

Целостно система выглядит как три слоя:

Producers: микросервисы, веб-клиенты, cron jobs. Публикуют задачи в broker.

Broker Layer: message queue / streaming system (Kafka/RabbitMQ/Redis Streams). Поддерживает persistence, partitions, priority queues, delay queues.

Workers: масштабируемые группы обработчиков, забирают задачи, выполняют, подтверждают результат. Используется backoff, retry и DLQ.

Дополнительно: scheduler/cron для отложенных задач, monitoring/metrics collector, DLQ analyzer, admin tools для ручного вмешательства.

Этап 6. Выбор технологий и sizing

Технологии:

Broker: Kafka (для high throughput, log-based persistence), RabbitMQ (AMQP, priority queues), Redis Streams (low-latency, simple tasks).

Workers: Celery + Python, or custom Go/Java consumers, с автошардингом и concurrency.

Delay queue / scheduler: Redis sorted sets, Kafka + scheduled jobs, RabbitMQ delayed messages.

DLQ: отдельная очередь или топик в Kafka для failed tasks.

Sizing: предположим 1M задач/сутки, средний task payload 1 KB.

Объём данных в broker ≈ 1 GB/day.

Worker throughput: если average task = 100ms, 1 worker = 10 tasks/sec → для 1M tasks/day (~12 tasks/sec average), достаточно 2–3 workers с запасом. Для spike нагрузки используется autoscaling.

Storage: если broker log retention = 7 дней, Kafka топик ~7 GB для persistence.

Для большого масштаба: sharding по task type или key hashing, replication factor ≥2 для HA, мониторинг lag и unacknowledged tasks, alert на превышение retry threshold.

Этап 7. Расширения и эксплуатация

Расширения:

Prioritization: отдельные priority queues, worker pools для high-priority tasks.

Rate limiting: ограничение на количество задач от одного producer.

Exactly-once semantics: идемпотентные task_id + deduplication на worker side.

Observability: метрики по выполнению задач, retries, DLQ, latency distribution.

Fault tolerance: multi-region brokers, persistent queues, auto-retry.

Backpressure: если очередь растёт → throttling producers или динамическое масштабирование workers.

Эксплуатация: мониторинг lag, queue depth, DLQ growth, failed tasks, autoscaling workers, SLA по latency обработки, алерты на превышение retries.

Design a Web Crawler

Обход страниц, распределение нагрузки, хранение результатов.

Этап 1. Постановка задачи и контекст

Интервьюер формулирует задачу: спроектировать веб-краулер, который обходит страницы Интернета и сохраняет их содержимое для последующей индексации или анализа. Система должна масштабироваться на миллионы и миллиарды страниц, обеспечивать распределённый обход без перегрузки сайтов, поддерживать приоритетное сканирование, следовать правилам robots.txt, обрабатывать динамический контент (JS-rendered pages), и эффективно хранить результаты. Нефункциональные требования включают высокую производительность обхода, fault tolerance, управление очередями URL, дедупликацию, поддержание politeness (ограничение числа запросов к одному домену в единицу времени) и мониторинг. После постановки контекста интервьюер молчит, инициатива переходит к кандидату.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет: требуется ли full web (весь Интернет) или ограниченный crawl (например, news sites, заданный набор доменов); какая глубина обхода (BFS vs DFS); допустимая задержка между обнаружением страницы и её обработкой; как часто нужно обновлять уже обработанные страницы; формат хранения контента (HTML, текст, метаданные, ссылки, скриншоты); поддержка мультиязычных сайтов; политика обхода ошибок (HTTP 5xx, timeout); SLA по freshness.

Ключевые архитектурные характеристики: high throughput, scalability (горизонтальное добавление crawlers), politeness, data consistency для очередей URL и хранилища контента, fault tolerance и возможность восстановления после падений, мониторинг и auditability.

Этап 3. Границы системы и публичный API

Граница системы — набор сервисов, принимающих URLs, управляемых scheduler, хранение страниц, API для мониторинга и управления.

Публичный API:

enqueue_url(url, priority, metadata) — добавление нового URL в crawl queue.

fetch_next_urls(batch_size, domain_constraints) — worker забирает URLs для обхода с учётом politeness.

store_page(url, content, metadata) — сохранение результата обхода.

admin APIs — stats по crawler’ам, очередь, DLQ для неудачных fetch’ей, управление политиками (robots.txt, crawl delay).

Контракт обеспечивает идемпотентность: URL не должен обрабатываться повторно без нужды; результаты fetch’а должны сохраняться атомарно с метаданными.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: Scheduler выбирает URL → распределяет его на available worker → worker делает HTTP request → парсит HTML → извлекает ссылки и контент → сохраняет результат в хранилище → новые ссылки возвращает в scheduler → повторение.

Exceptional flows: HTTP ошибки/timeout → retry с backoff → после max_retries в DLQ; блокировка сайта (robots.txt) → skip; rate-limit на домен → delay queue; падение worker → task возвращается в очередь.

Компоненты:

URL Frontier / Scheduler — центральный компонент, управляющий очередями, politeness, приоритетами и дедупликацией. Может быть распределённым для горизонтальной масштабируемости.

Workers / Fetchers — HTTP-клиенты, забирающие страницы, парсеры, извлекают ссылки и метаданные. Worker pool масштабируется горизонтально.

Content Storage — распределённое хранилище HTML/текст/медиа (S3-like), с возможностью индексации и хранения метаданных (URL, crawl time, HTTP headers).

URL Deduplication / Bloom Filters — предотвращение повторного обхода одинаковых URL; хранится на fast-access storage (Redis, Cassandra).

Delay Queues / Politeness Enforcer — контролирует частоту обращений к одному домену; может реализовываться через per-domain timers.

Retry & DLQ — задачи с ошибками помещаются в DLQ после заданного числа попыток.

Monitoring / Metrics — очередь на обработку, latency fetch, success rate, error rate, politeness violations.

Обработка динамического контента: JS-rendered pages могут обрабатываться через headless браузеры (Puppeteer, Playwright) или lightweight JS engines; trade-off: высокая латентность, но больше coverage.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Scheduler / URL Frontier — распределённый, хранит очереди и приоритеты, управляет politeness и дедупликацией.

Worker Layer — масштабируемые fetchers, делают HTTP запросы, парсят страницы, извлекают ссылки, сохраняют content + metadata.

Storage Layer — распределённое хранение контента, метаданных, индексация для дальнейшего анализа или поиска.

Delay / Retry / DLQ Layer — управляет отложенными задачами и ошибками.

Monitoring / Analytics — метрики работы системы и качества crawl.

Поток данных: Scheduler → Worker → Storage → Scheduler (для новых ссылок). Для politeness Scheduler использует per-domain queues и timers; для масштабирования Frontier распределяется по шардированию доменов или хешированию URL.

Этап 6. Выбор технологий и sizing

Технологии:

Message Queue / Scheduler: Kafka, RabbitMQ, Redis Streams для распределённого распределения URL.

Workers: контейнеризированные fetchers на Go/Python/Java, headless browsers для JS.

Storage: S3/MinIO для raw content, Cassandra/HBase/Elasticsearch для метаданных и быстрых lookup.

Deduplication: Bloom Filter в Redis/Redis Cluster или Cassandra; scalable filters на уровне shards.

Retry & DLQ: очередь Redis/Kafka для failed fetches.

Sizing:

Допустим, 1B страниц в базе, средний HTML 50 KB → raw storage ~50 TB.

Для throughput 1M pages/day: ~11.5 pages/sec; при среднее fetch 500ms → ~6 workers/worker pool, с запасом 2× → 12 fetchers.

Politeness: max 1 request/sec/domain, Scheduler управляет rate-limit per domain.

Dedup: Bloom filter для 1B URL с false positive 1% → ~1.2 GB RAM; для распределения shard’ы.

Для больших масштабов: Frontier распределён по доменам, Workers горизонтально масштабируемы, Storage tiering (hot/cold), CDN для часто запрашиваемых страниц, batching fetch results для экономии IOPS.

Этап 7. Расширения и эксплуатационные аспекты

Возможные расширения:

Prioritization / Relevance: crawl pages based on importance, PageRank or domain weight.

Incremental crawl / freshness: повторное посещение страниц с заданной частотой.

Politeness policies: динамический crawl-delay per domain, adaptive backoff.

Dynamic content handling: headless browsers, AJAX crawling.

Content parsing: extract metadata, structured data (JSON-LD, microdata), media.

Monitoring / Alerting: latency, error rates, politeness violations, queue backlog.

Fault tolerance: multi-region Frontier, persistence for URL queues, checkpointing for resuming after crash.

Design a Payment System

Идемпотентность, транзакции, безопасность, подписания запросов.

Этап 1. Постановка задачи и контекст

Интервьюер формулирует задачу: спроектировать систему для обработки финансовых транзакций между пользователями и/или сервисами. Система должна позволять инициировать платежи, проверять баланс, проводить трансферы, обеспечивать идемпотентность операций и атомарность списания и зачисления средств, поддерживать безопасность и шифрование запросов, а также логировать операции для аудита. Нефункциональные требования: высокая доступность, консистентность данных, защита от двойного списания (double-spend), масштабируемость по числу пользователей и объёму транзакций, низкая латентность подтверждения платежей и надежная обработка отказов. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Тип платежей: P2P, P2M, B2B, кэш-аут?

Обязательная строгая консистентность или eventual consistency допустима в некоторых сценариях?

Нужно ли поддерживать multi-currency и конвертацию?

Какие SLA по latency: мгновенное подтверждение vs batch processing?

Какие требования к идемпотентности: на уровне request-id или transaction-id?

Требования к безопасности: шифрование, подпись, токенизация, PCI DSS совместимость?

Что делать с отказами: retry, compensation transactions?

Нефункционально фиксируются ключевые характеристики: atomicity, consistency, isolation, durability (ACID), idempotency, security, high availability, auditability.

Этап 3. Границы системы и публичный API

Границы системы включают публичный API для клиентов и внутренних сервисов, backend платёжного движка, интеграцию с банками/платёжными шлюзами, внутреннее хранилище транзакций и баланс-сервис.

Пример API:

POST /payments: инициирует платеж с параметрами (sender, receiver, amount, currency, idempotency_key).

GET /payments/{id}: проверка статуса платежа.

POST /accounts/{id}/topup: пополнение баланса.

POST /accounts/{id}/withdraw: вывод средств.

GET /accounts/{id}/balance: получение текущего баланса.

Admin API: просмотр логов, rollback, reconciliation, мониторинг.

Контракт: каждый request с idempotency_key гарантирует, что повторный вызов не создаст двойную транзакцию.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: пользователь инициирует платеж → API валидирует request и idempotency_key → проверка баланса → блокировка суммы на счёте отправителя → запись транзакции в транзакционный журнал (commit log) → выполнение списания и зачисления → уведомление клиента → обновление статуса.

Exceptional flows:

Недостаточно средств → отклонение, логирование.

Сбой в процессе списания → retry с idempotency_key.

Сбой при обновлении баланса получателя → compensation transaction.

Дублированный запрос → обработка через idempotency_key.

Основные компоненты:

API Gateway — аутентификация, валидация, rate limiting, подпись/токенизация запросов.

Payment Service / Transaction Engine — core logic: atomic debit/credit, балансировка нагрузки, идемпотентность, retry и compensation.

Account / Ledger Service — хранение актуального баланса и истории транзакций; реализуется как ACID DB или с транзакционной обработкой через Event Sourcing.

Transaction Log / Audit Trail — immutable журнал всех операций, необходим для reconciliation и compliance.

Notification Service — уведомление пользователей о статусе платежей.

External Gateway Integrations — связь с банками, платёжными шлюзами, обработка callback’ов.

Идемпотентность: idempotency_key хранится вместе с записью транзакции; повторный request проверяет наличие ключа, и если транзакция уже выполнена, возвращает текущий результат.

Безопасность: HTTPS/TLS, подпись запросов HMAC или JWT, токенизация чувствительных данных, PCI DSS совместимость для хранения платёжной информации, ограничение доступа через RBAC и audit logging.

Этап 5. Концептуальная схема и целостный обзор

Архитектура:

API Gateway принимает и валидирует запросы, проверяет подписи.

Payment Engine получает запрос, проверяет баланс, резервирует средства и записывает транзакцию в Transaction Log.

Ledger / Account DB применяет изменения атомарно (например, в ACID-базе или через Event Sourcing с атомарными projections).

Notification Service сообщает клиентам результат.

External Payment Gateways подключаются через асинхронные callback’и и подтверждают завершение платежей.

Данные движутся в pipeline: Request → Validation → Ledger/Transaction Engine → Commit → Notification. При сбоях используется retry, DLQ и compensation.

Этап 6. Выбор технологий и sizing

Технологии:

DB: PostgreSQL/MySQL для ACID, либо распределённые NewSQL (CockroachDB, Spanner) для глобального масштаба.

Transaction Log: Kafka для событийной модели, immutable journal для audit & recovery.

Payment Engine / Microservices: Go/Java/Python с поддержкой concurrency и worker pools.

API Gateway: Nginx/Envoy + JWT/HMAC validation.

Caching: Redis для hot balances и rate-limiting.

Security: TLS, HSM для ключей, tokenization service.

Sizing:

Для 1M пользователей, 10k tx/day, средний payload 1 KB → ~10 MB/day журнал транзакций; 30 days retention → 300 MB; легко управляется любой SQL/NoSQL хранилищем.

Throughput: 10k tx/day ≈ 0.1 tx/sec average, пиковый load 50 tx/sec → 3–5 worker nodes с резервом.

Для глобальной системы требуется распределённая ACID DB с sharding по userId или ledgerId, replication factor ≥2.

Этап 7. Расширения и эксплуатационные аспекты

Multi-currency и FX: отдельные ledger per currency, конверсия с актуальными курсами.

Fraud detection: интеграция с ML/Rule engine для аномалий.

Batch processing: payroll, массовые выплаты.

Reconciliation: сверка транзакций с external gateways и internal ledgers.

Monitoring & SLA: latency, failed transactions, retry rate, balance correctness.

High availability: multi-region deployment, leader election for transaction engine.

Compliance: immutable logs, retention policies, audit reports.

E-commerce Checkout System

Корзина, ордеринг, инвентаризация, платежи.

Этап 1. Постановка задачи и контекст

Интервьюер формулирует задачу: спроектировать систему оформления заказов для интернет-магазина. Система должна обеспечивать работу корзины, формирование заказов, проверку наличия товаров на складе (инвентаризация), интеграцию с платёжной системой, поддержку скидок, налогов и доставки, а также уведомления пользователя о статусе заказа. Нефункциональные требования включают высокую доступность, согласованность данных о товарах и остатках, предотвращение overselling, масштабируемость по числу пользователей и заказов, низкую задержку оформления заказа и поддержку отказоустойчивости. После постановки контекста интервьюер замолкает, кандидат переходит к уточнению деталей.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Нужно ли поддерживать multi-currency и multi-warehouse?

Какова допустимая консистентность между корзиной и складом — строгая или eventual consistency?

Какие сценарии скидок и акций нужно поддерживать: купоны, bundle, loyalty?

Поддержка частичных оплат или отложенных платежей?

Как управлять отменой и возвратами?

SLA по latency checkout: мгновенное подтверждение или batch?

Политика при падении внешней платёжной системы.

Ключевые архитектурные характеристики: atomicity при создании заказа, consistency запасов, high throughput, scalability, fault tolerance, idempotency для повторных запросов, auditability и безопасность платежей.

Этап 3. Границы системы и публичный API

Границы системы охватывают корзину, checkout сервис, управление заказами, интеграцию с платёжными провайдерами и управление запасами.

Публичный API:

Cart API: add_item(userId, productId, quantity), remove_item, get_cart, update_quantity.

Checkout API: create_order(cartId, payment_info, shipping_info), confirm_payment(orderId), cancel_order(orderId).

Inventory API: check_stock(productId, quantity), reserve_stock(orderId), release_stock(orderId).

Order API: get_order_status(orderId), list_orders(userId).

Admin API: manage inventory, apply discounts, audit logs.

Контракт: API должен обеспечивать идемпотентность при повторных вызовах (например, повторный create_order с тем же requestId не создаёт дублированный заказ).

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: пользователь добавляет товары в корзину → инициирует checkout → система проверяет наличие товаров → резервирует stock → создаёт запись заказа → инициирует платёж → подтверждает платёж → подтверждает заказ и уведомляет пользователя → обновляет stock и accounting.

Exceptional flows:

Недостаточно товара → отказ с сообщением пользователю.

Платёж отклонён → заказ остаётся в pending, stock возвращается.

Сбой в резервировании stock → retry или отказ.

Дублированный запрос create_order → обработка через idempotency_key.

Компоненты:

Cart Service — хранит временные корзины, поддерживает session, кеширование, idempotent операции.

Inventory Service — проверка и резервирование stock, поддержка concurrency и транзакционность, репликация для HA.

Order Service — создание заказа, управление статусами, интеграция с Inventory и Payment Service.

Payment Service — безопасная интеграция с платёжными шлюзами, подтверждение транзакций, retry и idempotency.

Notification Service — уведомления о статусе заказа.

Discount / Pricing Service — вычисляет финальную цену, применяет купоны и акции.

Архитектурные решения:

Atomicity: при создании заказа важна транзакция между Order и Inventory (можно реализовать через distributed transaction / saga / two-phase commit).

Idempotency: create_order с уникальным requestId.

Concurrency: оптимистическая блокировка stock или pessimistic lock при high contention.

Resilience: retry и compensating transactions при сбоях в Payment Service.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Frontend / API Gateway — принимает запросы от клиентов, аутентифицирует, валидирует и применяет idempotency.

Cart & Order Services — управляют корзинами, заказами, резервированием stock и статусами.

Inventory Service — консистентное хранилище остатков, интеграция с warehouses.

Payment Service — платёжный движок с idempotent API и retry.

Discount / Pricing Service — вычисление финальной суммы, налоги, акции.

Notification & Audit — уведомления, логирование, мониторинг.

Поток данных: Cart → Checkout → Inventory reservation → Order creation → Payment → Notification → Commit stock changes.
Для fault tolerance используются retry, DLQ и compensating transactions.

Этап 6. Выбор технологий и sizing

Технологии:

DB: PostgreSQL/MySQL или распределённые ACID NewSQL (CockroachDB, Spanner) для Order + Inventory.

Caching: Redis/Memcached для Cart и hot-stock.

Message Queue: Kafka/RabbitMQ для интеграции между сервисами и событийной обработки.

Payment Gateway: интеграция с внешними провайдерами.

Notification: push/email/queue.

Sizing:

100k пользователей, средний cart 5 товаров, 10k заказов/день.

Stock updates: 50k ops/day, low-latency queries → Redis hot cache.

Orders: 10k/day → 2–3 Order worker nodes, queue-based processing.

Inventory: масштабируется по productId, репликация для HA.

Trade-offs:

Strong consistency на stock vs high throughput — можно использовать optimistic locking + compensating transactions.

Distributed transaction сложны на scale → Saga pattern предпочтительнее.

Этап 7. Расширения и эксплуатационные аспекты

Multi-warehouse & fulfillment: распределение stock по локациям.

Partial shipments: split order по availability.

Flash sales / spikes: rate-limiting, queueing и priority handling.

Audit & compliance: immutable logs, monitoring.

Discount & loyalty programs: сложные правила применения.

Idempotent checkout: защищает от повторных кликов или network retries.

Monitoring & SLOs: latency checkout, stock reservation failures, payment errors.

Design a Notification System (email + push + SMS)

Ротация каналов, очереди, массовая доставка.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать систему уведомлений, которая поддерживает несколько каналов доставки — email, push, SMS. Система должна обеспечивать массовую рассылку уведомлений, при этом управлять приоритетами и ротацией каналов (fallback: если push недоставлен, отправить SMS), гарантировать высокую доставляемость, обработку отказов и retry, а также мониторинг и аналитическую отчётность. Нефункциональные требования: масштабируемость до миллионов уведомлений в сутки, низкая латентность доставки для критичных уведомлений, устойчивость к пиковым нагрузкам (flash campaigns), идемпотентность отправки, fault tolerance и observability. После постановки контекста интервьюер замолкает.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Какие типы уведомлений: transactional (OTP, alerts) vs marketing (promotions)?

SLA по latency для каждого канала?

Поддержка персонализации: template engine, placeholders?

Ротация каналов: fallback при failure, or prioritized sending?

Требования к retry: количество попыток, backoff strategy?

Политика rate limiting для SMS / email / push?

Нужно ли отслеживать opens/clicks/delivery status?

Ключевые нефункциональные характеристики: high throughput, scalability, reliability, idempotency, low latency, observability, fault tolerance.

Этап 3. Границы системы и публичное API

Границы системы включают публичные API для внутренних сервисов и админ-панели, обработку очередей уведомлений и взаимодействие с внешними каналами (SMTP, push providers, SMS gateways).

Пример API:

POST /notifications: создать уведомление с параметрами (recipient, channel(s), templateId, priority, metadata).

GET /notifications/{id}/status: получить статус доставки.

Admin API: просмотр очередей, retry, отмена, статистика delivery.

Контракт: один notification_id = одна логическая цель; повторные вызовы с тем же notification_id должны быть идемпотентны.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: сервис создаёт уведомление → enqueue в centralized notification queue → dispatcher забирает уведомление → выбирает канал и template → отправляет через provider → обновляет status → логирование и метрики.

Fallback / channel rotation: если push fails (undelivered) → enqueue retry на SMS → при SMS failure → enqueue retry на email (опционально).

Exceptional flows:

Недоступность внешнего провайдера → retry с backoff.

Ограничение rate limit → delay queue.

Повторное получение того же notification_id → skip / return cached status.

Template rendering fail → логирование, alert, skip delivery.

Основные компоненты:

API Gateway / Notification Service — принимает уведомления, валидирует, применяет idempotency, enqueues.

Central Queue / Message Broker — Kafka/RabbitMQ/Redis Streams для распределённой обработки.

Dispatcher / Worker Pool — забирает уведомления, выбирает канал и template, вызывает provider API.

Channel Providers — SMTP/email service, push provider (Firebase/APNs), SMS gateway.

Retry & DLQ — задачи, не доставленные после N попыток, помещаются в DLQ для анализа.

Monitoring / Analytics — delivery rate, latency, errors, opens/clicks.

Ротация каналов и fallback: реализуется в dispatcher с state machine per notification. При failure → next channel → retry → update status.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Producer/API layer — принимает уведомления, enqueues с idempotency key и priority.

Queue / Broker — хранение и шардирование уведомлений по каналам и приоритетам, поддержка delay queues.

Dispatcher / Worker Layer — канал-агностичные workers, выбирают канал и template, выполняют отправку, обрабатывают retries и fallback.

Provider Layer — интеграция с внешними email/SMS/push провайдерами.

Analytics & Monitoring — метрики по доставке, latency, retry rate, failed notifications.

DLQ / Retry Management — отдельные очереди для failed notification, возможность ручного вмешательства.

Поток данных: Notification request → Queue → Dispatcher → Channel provider → Update status → Analytics. Для масштабирования queue шардируется по recipient hash / channel type.

Этап 6. Выбор технологий и sizing

Технологии:

Queue: Kafka для high throughput, RabbitMQ или Redis Streams для low-latency.

Workers: контейнеризированные dispatchers на Go/Java/Python, горизонтально масштабируемые.

Template engine: Handlebars, Jinja2, или proprietary templating service.

Provider integration: SMTP servers, Firebase Cloud Messaging, APNs, Twilio/Plivo для SMS.

Monitoring: Prometheus/Grafana, ELK stack или ClickHouse для аналитики.

DLQ & Retry: отдельная топика/queue, retry policy с exponential backoff.

Sizing:

1M notifications/day, смешанный канал: 50% push, 30% email, 20% SMS.

Среднее время доставки push ≈ 1s, email ≈ 5s, SMS ≈ 2s.

Throughput requirement ~12 notifications/sec average, peak 500/sec → horizontal scaling of dispatcher pool (20–30 workers per channel type).

Queue retention: 7 days for retry and monitoring.

Analytics storage: aggregated metrics ~10 MB/day, raw logs ~100 MB/day → manageable with Kafka + ClickHouse.

Trade-offs: для критичных notifications (OTP, alerts) priority queue; marketing notifications can be batched for cost efficiency. Retry и DLQ позволяют выдерживать SLA при отказах провайдера.

Этап 7. Расширения и эксплуатационные аспекты

Personalization: dynamic templates, user preferences for channel.

Rate limiting & throttling: per channel, per recipient, global daily cap.

Batching: объединение маркетинговых сообщений для экономии SMS/email cost.

Monitoring & alerting: failed deliveries, latency spikes, retry saturation.

High availability: multi-region queues, autoscaling dispatcher pool.

Audit & compliance: immutable logs, retention policies.

Feedback loop: opens/clicks/events feed into recommendation engine or analytics.

Real-Time Analytics Dashboard

Агрегации, окна, стрики, кластер Kafka/ClickHouse.

Этап 1. Постановка задачи и контекст

Интервьюер ставит задачу: спроектировать систему для отображения реального времени метрик и событий на дашборде. Система должна обрабатывать события с различных источников (например, веб-сайт, мобильные приложения, IoT), агрегировать их по различным измерениям (user, region, product, event type), строить скользящие окна (tumbling, sliding), поддерживать стриминговые и исторические данные, обеспечивать низкую задержку обновления (sub-second – секунды), и масштабироваться на миллионы событий в минуту. Нефункциональные требования: high throughput, low latency, fault tolerance, горизонтальное масштабирование и возможность добавления новых источников без downtime. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Какие виды метрик: count, sum, average, unique users, percentiles?

Размер и количество окон: минутные, часовые, sliding?

SLA latency: обновление дашборда в реальном времени (1–2s) или near-real-time (10–30s)?

Историческое хранение: retention событий и агрегатов?

Количество источников событий и ожидаемый EPS (events per second)?

Требования к консистентности: точное vs approximate aggregates (HyperLogLog, sketches)?

Нужно ли поддерживать ad-hoc queries или только predefined dashboards?

Нефункциональные характеристики: high throughput, low latency, scalability, fault tolerance, data retention, query flexibility.

Этап 3. Границы системы и публичное API

Границы включают источники событий, стриминговый слой, слой хранения и API/сервис визуализации.

Публичное API:

POST /events: ingestion событий с payload (event_type, userId, timestamp, metadata).

GET /dashboard/{metric}: возвращает агрегированные данные для фронтенда.

Admin API: управление retention, источниками, схемой событий, мониторинг throughput.

Контракт: события не теряются; каждый event_id идемпотентен; агрегаты должны быть согласованы с окном времени.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: источник отправляет событие → Kafka topic (partitioned by key) → Stream Processor (Flink / Spark Streaming / ksqlDB) → вычисление агрегатов по нужным окнам → запись в ClickHouse (OLAP) → фронтенд дашборда делает запрос → визуализация в реальном времени.

Exceptional flows:

Kafka broker недоступен → producer retry + buffering.

Stream processor node падает → state recovery из checkpoint / Kafka offsets.

ClickHouse shard недоступен → fallback на реплику, частичная доставка.

Высокая нагрузка → autoscaling Stream Processor, горизонтальное масштабирование ClickHouse.

Основные компоненты:

Event Producers — приложения, сервисы, IoT-устройства.

Message Broker / Event Bus — Kafka, sharding и partitioning по ключу (userId, event_type).

Stream Processing / Aggregation — Flink, Spark Streaming, ksqlDB, выполняет windowed aggregations, counts, sums, averages, sketches.

State Store — RocksDB или встроенные state backend stream processors для windowed state.

OLAP Storage — ClickHouse, хранит агрегаты и исторические данные для ad-hoc queries.

Dashboard API / Frontend — REST или GraphQL API, обновление в режиме push (WebSocket) или pull.

Monitoring & Alerting — lag monitoring Kafka, stream processing throughput, ClickHouse query performance.

Windowing & aggregation: tumbling (fixed), sliding (overlapping), session windows. Для unique counts используют approximate structures (HyperLogLog, Count-Min Sketch) для уменьшения памяти.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Producers — источники событий.

Event Bus / Kafka — durable, partitioned, fault-tolerant.

Stream Processor — windowed aggregation, stateful computation, checkpointing for recovery.

OLAP / ClickHouse — хранение агрегатов и исторических данных.

Dashboard API / Frontend — WebSocket/REST API для push обновлений.

Monitoring & Logging — lag metrics, throughput, error rate, alerts.

Поток данных: Event → Kafka → Stream Processor → Aggregates → ClickHouse → Dashboard. При high-throughput partitioning по userId/event_type + replication. Checkpoints обеспечивают recovery на случай сбоя.

Этап 6. Выбор технологий и sizing

Технологии:

Message Broker: Kafka cluster с replication factor ≥2, partitioning по event_type/userId.

Stream Processing: Flink, Spark Streaming, ksqlDB — поддержка stateful windowed aggregation, exactly-once semantics.

State Store: RocksDB или Flink state backend для windowed state.

OLAP Storage: ClickHouse, поддержка merge-tree, materialized views, retention policies.

Frontend / API: Node.js / Go / Python + WebSocket для realtime push.

Monitoring: Prometheus + Grafana, Kafka lag monitoring, Flink metrics.

Sizing:

EPS = 1M events/sec, 100 bytes per event → 100 MB/sec ingestion.

Kafka: 100 partitions, replication factor 3 → устойчивость и throughput.

Stream Processor: ~20 nodes, state backend для окон (~1GB per node).

ClickHouse: 5 shards × 2 replicas, хранение агрегатов + исторических данных (~1TB/month).

Dashboard: caching top metrics, WebSocket push, batch updates для heavy queries.

Trade-offs:

Строгая консистентность vs low-latency — выбирается exactly-once processing при critical metrics, approximate counts для high-cardinality metrics.

Materialized views в ClickHouse ускоряют queries, но увеличивают storage.

Этап 7. Расширения и эксплуатационные аспекты

Ad-hoc queries: поддержка OLAP-запросов поверх ClickHouse.

Multi-tenancy: разные dashboards для разных клиентов.

Alerts & thresholds: real-time anomaly detection на stream level.

Backfill & replay: возможность пересчитать агрегаты при schema change или баге.

High availability: multi-region Kafka + ClickHouse replication.

Monitoring: latency per aggregation, lag monitoring, state store size, dashboard refresh time.

Approximate algorithms: HyperLogLog, Count-Min Sketch для unique counts и heavy hitters.

API Rate Control / Throttling Gateway

API gateway, quotas, metering, JWT.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать API Gateway, который обеспечивает контроль частоты вызовов API, лимиты использования (quotas), метрики использования и аутентификацию с JWT. Система должна защищать backend-сервисы от перегрузки, обеспечивать fair usage для клиентов, поддерживать различные типы лимитов (per user, per API key, per endpoint), и быть масштабируемой на десятки тысяч запросов в секунду. Нефункциональные требования включают низкую задержку обработки запросов, горизонтальное масштабирование, консистентное применение лимитов, высокую доступность и observability. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Какие виды лимитов нужны: rate per second/minute/hour, burst limits, quotas per month?

Аутентификация: JWT с payload (userId, plan, scopes) или API keys?

Как обрабатывать превышение лимита: reject (429) или delay (leaky bucket)?

Нужно ли поддерживать глобальные лимиты или только per-user/client?

SLA latency: sub-ms для gateway или допускается ~10ms overhead?

Нужна ли интеграция с billing / plan enforcement?

Ключевые архитектурные характеристики: high throughput, low latency, consistency (в пределах одного bucket), scalability, fault tolerance, observability, idempotency для повторных запросов.

Этап 3. Границы системы и публичный API

Границы включают: API Gateway с rate control, бекенд-сервисы, метрики и админ-интерфейс.

Публичный API:

Incoming client API: проксирование запросов с JWT/ключом, rate limiting и quota enforcement.

Admin API: set/update rate limits, view metrics per user/endpoint, reset quotas.

Metrics API: expose usage stats for monitoring (requests/sec, quota usage, blocked requests).

Контракт: каждый request проверяется на rate limit; превышение лимита → 429 Too Many Requests; лимиты применяются консистентно на shard’ах.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: клиент делает request → API Gateway проверяет JWT → извлекает userId/plan → проверяет текущий rate/quotas → request allowed → proxy to backend → update counters → respond клиенту.

Exceptional flows:

JWT invalid → reject 401.

Quota exceeded → reject 429 + Retry-After header.

Gateway node failure → replication/consistent counters via distributed store.

Burst traffic → token bucket allows burst, leaky bucket smooths traffic.

Компоненты:

API Gateway / Proxy Layer — принимает и валидирует запросы, извлекает JWT, enforces rate limits.

Rate Limiter / Throttling Engine — per-user, per-endpoint, per-plan; реализует token bucket / leaky bucket; хранение state в Redis/etcd/Consul.

Distributed Counter Store — хранит текущее состояние лимитов (Redis cluster, DynamoDB, Cassandra).

Quota Manager / Plan Engine — хранит план пользователя и лимиты, применяет политики.

Metrics & Monitoring — количество запросов, blocked requests, usage per API key, latency.

Admin API — управление лимитами, просмотр usage stats.

Token Bucket vs Leaky Bucket:

Token Bucket: позволяет burst traffic до определённого размера, хорошо для планов с burst allowance.

Leaky Bucket: smooths traffic, предотвращает spikes на backend.

Distributed Limiter: shard по userId/API key, consistent hashing для масштабирования; при multi-node необходимо согласованное хранение токенов и atomic increment.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Client Requests → JWT / API key authentication.

API Gateway → извлекает claims, проверяет лимиты, proxy to backend.

Rate Limiter → token/leaky bucket per user/endpoint/plan.

Counter Store → Redis cluster или DynamoDB, хранение state.

Metrics & Monitoring → Prometheus/Grafana для observability.

Admin Interface → управление лимитами, просмотр usage stats.

Поток данных: Request → Gateway → Rate Limiter → Counter Store → Backend.
Для horizontal scaling shard’ы распределяются по userId/API key, при необходимости используется replicated state для HA.

Этап 6. Выбор технологий и sizing

Технологии:

API Gateway: Envoy, NGINX, Kong, or custom Go/Java gateway.

Rate Limiter Storage: Redis Cluster (fast increment, TTL), optionally DynamoDB/Cassandra for distributed consistency.

Metrics: Prometheus + Grafana, ELK stack for logs.

JWT handling: HMAC or RSA validation, claims extraction in gateway.

Sizing:

Target: 100k requests/sec, average burst 10 req/sec/user.

Redis cluster: 20 shards, replication factor 2, ~100k active buckets per node.

Gateway nodes: 10–20 horizontally scalable instances behind load balancer.

Counters TTL: per-second/minute/hour window → ~5–10 MB per shard for active users.

Trade-offs:

Strong consistency vs low latency → for distributed token buckets можно использовать approximate counters or Lua scripts in Redis.

Burst handling → token bucket preferred; smoothing → leaky bucket.

Этап 7. Расширения и эксплуатационные аспекты

Dynamic plan updates: менять лимиты без downtime.

Global vs local limits: per-region or multi-region rate limiting.

Failover / HA: replicated counter store, gateway autoscaling.

Analytics: usage patterns, abusive clients, billing integration.

Backpressure: reject or queue requests on backend saturation.

Quota reset & rollover: daily/monthly quotas with TTL.

Security: protect rate limiter store from malicious manipulation, audit logs.

Design a Social Graph (friends / followers)

Хранение графов, рекомендации друзей.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать Social Graph для платформы с друзьями и подписчиками. Система должна хранить отношения пользователей (friend / follower), обеспечивать быстрый доступ к спискам друзей, mutual friends, followers/following, поддерживать поиск рекомендаций друзей и подписок, а также обеспечивать масштабируемость на миллионы и миллиарды пользователей. Нефункциональные требования включают высокую доступность, низкую задержку чтения списка друзей, горизонтальное масштабирование, быстрые query на рекомендации (friend-of-friend), и возможность batch или realtime обработки graph analytics. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Тип графа: directed (followers) vs undirected (friends)?

SLA на чтение списков друзей/followers: sub-second?

Частота обновления графа: высоконагруженные изменения (friend request, follow/unfollow)?

Поддержка рекомендаций: friend-of-friend, collaborative filtering, graph embeddings?

Ограничения на размер списка друзей/followers для одного пользователя?

Исторические данные: нужна ли версия графа в прошлых состояниях?

Ключевые характеристики: high throughput, low latency, horizontal scalability, consistency vs eventual consistency trade-offs, fault tolerance, query efficiency.

Этап 3. Границы системы и публичный API

Границы системы охватывают storage для графа, query layer для friends/followers, recommendation engine, и админ/metrics сервисы.

Пример API:

POST /users/{id}/follow/{targetId} — подписка на пользователя.

POST /users/{id}/friend/{targetId} — отправка / подтверждение friend request.

GET /users/{id}/friends — список друзей.

GET /users/{id}/followers — список подписчиков.

GET /users/{id}/recommendations — friend suggestions.

Admin API — просмотр статистики, управление rate limits, audit logs.

Контракт: операция friend/follow должна быть идемпотентной; удаление связи или unfollow обновляет graph state корректно.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: пользователь A отправляет запрос дружбы / подписки → система обновляет graph storage → обновляет индексы → при необходимости recompute recommendations → уведомление пользователя B.

Exceptional flows:

Конфликт friend request → обработка на уровне application layer.

Массовое добавление / удаление → batch update для efficiency.

Недоступность storage → fallback на read replicas с eventual consistency.

Recommendation engine heavy load → precompute embeddings / materialized views.

Основные компоненты:

Graph Storage — хранение связей; варианты: adjacency list (RDBMS, key-value), graph DB (Neo4j, JanusGraph), or wide-column stores (Cassandra/HBase).

Index Layer — быстрый lookup friends/followers; sharding по userId.

Recommendation Engine — friend-of-friend, collaborative filtering, graph embeddings, caching top-k recommendations.

API Layer — REST/GraphQL API с rate limiting и auth.

Notification Service — события friend request, new follower.

Analytics / Batch Processing — периодическое recompute сложных recommendations.

Storage trade-offs:

RDBMS — ACID, но масштабирование и join-heavy queries проблематично.

Graph DB — fast traversal, query-friendly, но сложнее масштабировать.

Wide-column / key-value — легко shard по userId, fast read of adjacency lists, eventual consistency.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

API Layer — принимает friend/follow actions, возвращает friend/follower lists, recommendations.

Graph Storage — adjacency list per user (key = userId, value = list of friends/followers).

Index / Cache Layer — Redis/Memcached для hot users, top friends, mutuals.

Recommendation Engine — friend-of-friend traversal, embeddings, collaborative filtering, batch precompute.

Notification Service — push/email alerts.

Analytics Layer — batch recompute for trending recommendations.

Поток данных: Action → Graph Storage → Index update → Recommendation Engine → Cache → API response → Notification.

Этап 6. Выбор технологий и sizing

Технологии:

Graph DB: Neo4j, JanusGraph (backend Cassandra/HBase) для traversal-heavy queries.

Wide-column / KV store: Cassandra, DynamoDB для adjacency lists per user.

Cache: Redis/Memcached для hot users, mutual friends, precomputed recommendations.

Batch processing / Analytics: Spark, Flink, Hadoop for graph embeddings, friend-of-friend counts.

API Layer: Go/Java/Python microservices, GraphQL or REST.

Sizing:

100M users, avg 200 friends → 20B edges.

Adjacency lists: 200 friends × 8B users = 1.6B entries, stored in Cassandra (~50–100 GB depending on replication).

Hot cache: top 10 friends per 1M active users → ~10M entries in Redis (~1–2 GB).

Recommendation engine: precompute daily embeddings, serve top-K recommendations from cache.

Trade-offs:

RDBMS: ACID, joins costly at scale.

Graph DB: fast traversal, harder horizontal scaling.

Wide-column + cache: high throughput reads, eventual consistency acceptable for recommendations.

Этап 7. Расширения и эксплуатационные аспекты

Friend suggestions: friend-of-friend, collaborative filtering, content-based, embeddings.

Multi-tier caching: hot users, cold users, precomputed recommendations.

Rate limiting: prevent spam friend requests or follow/unfollow abuse.

Monitoring & observability: edge-case queries, latency, hot shards.

High availability: multi-region replication, read replicas, failover.

Graph analytics: trending users, communities, influence scores.

Eventual consistency vs strong consistency: friend/follow writes consistent per shard, recommendation results can be eventually consistent.

Ride-Sharing System (Uber Lite)

Matching riders/drivers, геолокации, очереди.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать упрощённую версию сервиса ride-sharing. Система должна обеспечивать поиск и сопоставление водителей и пассажиров, учитывать геолокацию, поддерживать очереди запросов на поездку, уведомлять участников, учитывать ETA (Estimated Time of Arrival) и динамическую загрузку водителей. Нефункциональные требования: низкая латентность matching, масштабируемость по количеству водителей и пассажиров, fault tolerance, горизонтальное масштабирование и возможность обработки пиковых нагрузок. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Какой географический масштаб: город, регион, страна?

Тип matching: nearest driver vs pooled rides?

SLA по latency matching: sub-second или допустимо несколько секунд?

Требования к persistence: хранение поездок, истории, платежей?

Необходимость ETA для пассажира и водителя?

Политика отказа: водитель отклонил запрос, пассажир отменил поездку?

Поддержка surge pricing или динамических тарифов?

Ключевые характеристики: high throughput, low latency, scalability, fault tolerance, real-time geospatial processing, availability, auditability.

Этап 3. Границы системы и публичный API

Границы системы включают:

API для пассажиров: request ride, cancel ride, track ride, rate driver.

API для водителей: accept/reject ride, update location, complete ride.

Matching engine и geospatial service.

Storage: trips, users, drivers, payments, location history.

Notification service.

Пример API:

POST /rides/request — пассажир создаёт запрос на поездку (pickup, dropoff).

POST /drivers/{id}/location — водитель обновляет текущее местоположение.

GET /rides/{id}/status — статус поездки.

POST /rides/{id}/cancel — отмена поездки.

Контракт: ride request обрабатывается атомарно; если request отменён, все связанные ресурсы (driver slot, ETA) освобождаются.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path: пассажир делает запрос → геосервис находит ближайших доступных водителей → matching engine выбирает оптимального водителя → уведомление водителя → водитель принимает → поездка подтверждена → мониторинг статуса (ETA, маршрут) → завершение → оплата.

Exceptional flows:

Водитель отклонил → retry с next nearest driver.

Пассажир отменил → free driver slot, notify driver.

Нет доступных водителей → notify passenger, put request in queue.

Гео-сервис недоступен → fallback на last-known location / cached grids.

Основные компоненты:

API Gateway / Request Service — принимает ride requests, validates, idempotency for retries.

Matching Engine — real-time matching of drivers and riders, prioritizes nearest driver, may use weighted scoring (distance, driver rating, ETA).

Geospatial Service — indexes driver locations (grid-based, quadtrees, geohashes), supports nearest-neighbor queries.

Driver & Rider Queues — temporary queues for requests and available drivers.

Trip Management / State Store — tracks ongoing rides, status, ETA, pricing.

Notification Service — push notifications to drivers/pax.

Monitoring & Analytics — rides completed, latency, driver utilization.

Geospatial indexing:

Partition city into grids (e.g., geohash), store active drivers in grid → fast lookup nearest driver.

Optional in-memory caching (Redis / Hazelcast) for hot zones.

Matching algorithm: nearest-driver-first, weighted by ETA, driver rating, dynamic pricing factors.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Client API / Gateway — принимает ride requests, driver location updates.

Geospatial Service — driver location indexing, nearest-neighbor search.

Matching Engine — selects optimal driver, manages queues, retry on rejection.

Trip / State Management — trip lifecycle, status updates, ETA, pricing.

Notification Service — push notifications for request/accept/cancel.

Monitoring / Analytics — utilization, latency, rides per region, surge events.

Поток данных: Ride request → Matching Engine + Geospatial Service → Driver → ETA & route updates → Trip completion → Payment & rating.

Этап 6. Выбор технологий и sizing

Технологии:

API Gateway: Envoy / Nginx / Kong.

Geospatial Storage / Indexing: Redis (geo-indexes), Elasticsearch geo queries, PostGIS.

Matching Engine: in-memory processing, scalable microservices, possibly with queue (Kafka) for decoupling requests.

Trip Store: PostgreSQL / MySQL / NoSQL (Cassandra) for horizontal scaling.

Notification: Firebase / APNs / WebSocket.

Monitoring: Prometheus + Grafana, ELK stack for logs.

Sizing:

City-scale: 100k active users, 10k drivers.

Average 1 ride/sec → 3600 rides/hour.

Geohash grids: 1km × 1km, average 10–50 drivers per grid → fast lookup in Redis.

Matching Engine: ~10–20 nodes for parallel processing of ride requests.

Notification: low latency push, autoscaling per demand.

Trade-offs:

Strong consistency (exact driver availability) vs low latency → prefer optimistic matching + compensation if double-book occurs.

ETA calculation: real-time vs cached routing → balance latency and accuracy.

Этап 7. Расширения и эксплуатационные аспекты

Pooling / ride-sharing: multiple passengers per ride.

Dynamic pricing / surge: adjust fares based on supply-demand in grid.

Driver rating & scoring: incorporate in matching decisions.

High availability: multi-region deployment for disaster recovery.

Backpressure handling: queue ride requests if system saturated.

Analytics: driver utilization, heatmaps, demand prediction.

Monitoring & alerting: failed matches, stale driver locations, queue backlog.

Ads Targeting System

Сегментация пользователей, high-throughput сервисы.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать Ads Targeting System, которая позволяет рекламодателям таргетировать пользователей на основе их интересов, поведения и демографических данных. Система должна поддерживать высокую пропускную способность при показе рекламных объявлений, обеспечивать низкую латентность при запросе на показ (real-time bidding), масштабироваться на миллионы пользователей и миллиардные event-потоки. Ключевые нефункциональные требования: high throughput, low latency, горизонтальное масштабирование, fault tolerance, сегментация аудитории, гибкая конфигурация кампаний. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Тип таргетинга: демографический, поведенческий, look-alike, ретаргетинг?

SLA latency: сколько времени допускается на решение “показать объявление пользователю”?

Event sources: веб, мобильные приложения, CRM данные?

Поддержка real-time bidding (RTB) или batch-показы?

Частота обновления сегментов: real-time, hourly, daily?

Требования к аутентификации и privacy (GDPR/CCPA)?

Метрики эффективности: CTR, conversions, revenue attribution?

Ключевые характеристики: high throughput, low latency, scalability, flexibility, fault tolerance, observability, data privacy compliance.

Этап 3. Границы системы и публичный API

Границы системы включают: ingestion событий, сегментацию пользователей, storage сегментов и аудитории, real-time targeting engine, API для рекламодателей и платформы доставки объявлений.

Пример API:

POST /events — отправка пользовательских событий (page_view, click, purchase).

GET /ads?userId={id} — получение релевантного объявления для конкретного пользователя.

POST /campaigns — создание/обновление кампаний, таргетинг и бюджеты.

Admin API — просмотр сегментов, performance metrics, debug tools.

Контракт: каждый user event обрабатывается и доступен для сегментации в допустимые SLA; запрос на показ объявления должен учитывать актуальные сегменты и кампании.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path:

Пользователь совершает действие → event отправляется в ingestion pipeline.

Pipeline агрегирует события → обновляет user profile / segments.

Ads Engine при показе запроса → получает userId → извлекает сегменты → выбирает релевантные кампании → ранжирует объявления → возвращает ad payload в клиентское приложение.

Exceptional flows:

Недоступность сегментационного хранилища → fallback на cached segments.

High load на Ads Engine → throttle или batch decisions.

Event ingestion backlog → eventual consistency для segment updates.

Privacy constraints → filter PII, opt-out users.

Компоненты:

Event Ingestion / Stream — Kafka, Pulsar; partitioned by userId, high throughput.

User Profile & Segment Store — scalable key-value store (Cassandra, DynamoDB, Redis), хранение атрибутов, сегментов.

Real-Time Ads Engine — принимает userId, извлекает сегменты, выбирает объявления, ranks by priority/CTR/price.

Campaign Management Service — CRUD для кампаний, таргетинг правил, budgets.

Analytics / Monitoring — CTR, conversions, campaign effectiveness.

Admin / API Layer — управление, мониторинг, debug.

Сегментация и таргетинг:

Сегменты вычисляются в потоковом режиме (real-time) и batch (hourly/daily).

Для high-cardinality сегментов используется bitmap indexing, inverted indices или Bloom filters.

Ads Engine использует precomputed top-K candidates per segment, cached in-memory.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Event Producers — веб/мобильные события пользователей.

Ingestion Pipeline — Kafka / Pulsar, partitioned by userId.

Stream Processing — Flink/Spark Streaming для real-time segment updates.

Segment / Profile Store — Cassandra/DynamoDB/Redis, хранение актуальных сегментов per user.

Ads Engine — selects relevant ad based on segments and campaign rules; ranks and returns payload.

Campaign Management — CRUD, targeting rules, budgets.

Analytics / Monitoring — event metrics, ad performance dashboards.

Поток данных: Event → Ingestion → Stream Processing → Profile/Segment Store → Ads Engine → Delivery → Analytics.

Этап 6. Выбор технологий и sizing

Технологии:

Event Bus: Kafka / Pulsar, high throughput ingestion (млн+ events/sec).

Stream Processing: Flink / Spark Streaming, stateful real-time computation.

Segment / Profile Store: Cassandra / DynamoDB для scalable key-value storage, Redis for hot segments.

Ads Engine: in-memory processing (Go/Java), low-latency retrieval from KV store.

Analytics: ClickHouse / Druid for ad performance metrics.

Sizing:

EPS = 10M events/sec → partitioned across 100+ Kafka partitions.

User base = 100M, average 20 attributes/segments → 2B segment entries, ~100–200 GB storage.

Ads Engine: top-K candidates cached in Redis (~1–2 GB), hundreds of concurrent requests per node.

Stream processors: ~20 nodes for low-latency segment computation.

Trade-offs:

Strong consistency vs latency → eventual consistency на segment updates допустима для non-critical campaigns.

Real-time vs batch → critical campaigns real-time, long-tail campaigns batch.

Этап 7. Расширения и эксплуатационные аспекты

Look-alike / ML-based targeting — precompute embeddings, user similarity.

Personalization — dynamic ranking of ads per user/session.

Dynamic budgets & pacing — campaign spend per time unit.

Privacy compliance — GDPR/CCPA opt-out, data anonymization.

High availability & multi-region — replication for ingestion pipeline, segment store.

Monitoring & alerts — ingestion lag, ad delivery latency, CTR metrics.

Fallback / cold-start — default generic ads for users without sufficient profile data.

Online Code Execution Service (как LeetCode / Judge0)

Изоляция, sandboxing, распределение задач по воркерам.

Этап 1. Постановка задачи и контекст

Интервьюер объявляет задачу: спроектировать сервис, позволяющий пользователям выполнять код в онлайн-режиме на различных языках программирования. Система должна обеспечивать изоляцию исполняемого кода (sandboxing), поддержку параллельного выполнения задач, обработку различных языков и версий, безопасное управление ресурсами (CPU, memory, disk), а также масштабирование под большое количество одновременных запросов. Нефункциональные требования: безопасность (изоляция и предотвращение злоумышленного кода), low latency, fault tolerance, горизонтальное масштабирование и управление очередями заданий. После постановки контекста интервьюер молчит.

Этап 2. Формализация требований и уточняющие вопросы

Кандидат уточняет:

Какие языки и версии поддерживать: Python, Java, C++, JS, Go и др.?

SLA latency: среднее время выполнения кода и максимальная задержка?

Ограничения ресурсов на задачу: CPU time, memory, disk, network?

Persistent storage: нужен ли для тестов, логов, или execution ephemeral?

Нужно ли поддерживать batch execution или только interactive submissions?

Поддержка long-running или бесконечного цикла кода → timeout enforcement?

Отслеживание execution metrics: runtime, memory usage, exit status, stderr/stdout?

Ключевые характеристики: security, isolation, scalability, high throughput, low latency, fault tolerance, multi-language support, observability.

Этап 3. Границы системы и публичный API

Границы включают: веб/API layer для приема submissions, execution engine с sandboxing, storage для логов и результатов, очередь заданий, мониторинг и администрацию.

Пример API:

POST /submissions — загрузка кода с параметрами (language, version, stdin, constraints).

GET /submissions/{id}/status — текущий статус выполнения (queued, running, finished, error).

GET /submissions/{id}/result — stdout, stderr, exit code, runtime metrics.

Admin API — управление execution nodes, monitoring, scaling.

Контракт: каждый submission имеет уникальный id; повторный запрос по id идемпотентен; system enforces resource limits and timeouts.

Этап 4. Проектирование: сценарии, потоки данных и компоненты

Happy path:

Пользователь отправляет submission → API validates + enqueues in Job Queue.

Worker забирает задачу из очереди → запускает в sandbox (container, VM, Firecracker microVM, chroot).

Код выполняется с лимитами CPU, memory, disk, timeout → stdout/stderr и exit code сохраняются.

Worker сохраняет результат → updates submission status → notify user.

Exceptional flows:

Code hangs → enforce timeout → terminate sandbox → return timeout error.

Code exceeds memory → terminate → return memory exceeded.

Worker node crashes → job requeued → ensure idempotency.

Security violation (network/file access) → sandbox prevents access, log incident.

Компоненты:

API / Submission Service — принимает submissions, validates payload, enqueues jobs.

Job Queue — Kafka/RabbitMQ/Redis Queue, decouples submissions from workers.

Execution Workers / Sandbox Engine — containerized/microVM execution, resource-limited, isolated.

Language Runtime Images — prebuilt docker/microVM images per language/version.

Result Store — persistent storage for stdout, stderr, exit code, runtime metrics.

Monitoring & Metrics — worker health, queue size, execution latency, sandbox violations.

Admin / Scheduler — scale workers, monitor load, manage language images.

Sandboxing approaches:

Containers (Docker) — lightweight, OS-level isolation.

Firecracker microVMs — stronger isolation, lower overhead than full VM.

chroot / seccomp / cgroups — minimal isolation, less secure for untrusted code.

Resource enforcement: cgroups (CPU, memory), timeout, ephemeral filesystem, network disabled.

Этап 5. Концептуальная схема и целостный обзор

Система делится на слои:

Client / API Layer — принимает submissions, applies validation and idempotency.

Job Queue — decouples submission ingestion and worker execution; handles retries.

Execution Worker — picks job → launches sandbox → executes code → collects results.

Language Runtime Images — prebuilt environments for supported languages.

Result Storage — persistent logs, stdout/stderr, metrics.

Monitoring & Admin — metrics collection, autoscaling, health checks.

Поток данных: Submission → Queue → Worker → Sandbox Execution → Result Storage → API response → Monitoring.

Этап 6. Выбор технологий и sizing

Технологии:

Job Queue: Kafka / RabbitMQ / Redis Streams — durable, partitioned, high throughput.

Execution Workers: Go / Java / Python microservices, horizontally scalable.

Sandboxing: Docker, Firecracker microVMs, cgroups/seccomp for resource isolation.

Result Storage: S3 / MinIO (stdout/stderr), PostgreSQL / DynamoDB (submission metadata).

Monitoring: Prometheus + Grafana, alerting for worker failures or slow jobs.

Sizing:

100k submissions/day, avg execution 1–2s.

Queue throughput ~2–3 submissions/sec on average, peak 500/sec → partitioned queue.

Workers: 50–100 nodes with multiple concurrent sandboxes per node.

Resource limits: 500MB RAM, 2 CPU cores per sandbox.

Storage: stdout/stderr avg 10KB per submission → ~1GB/day.

Trade-offs:

Strong isolation (microVM) vs latency / resource overhead.

Prebuilt language images reduce startup latency.

Queue decouples spikes, ensures retry on worker failure.

Этап 7. Расширения и эксплуатационные аспекты

Multi-language support: добавление новых runtimes и версий.

Autoscaling: workers scale horizontally based on queue depth.

Security monitoring: detect malicious submissions, audit logs.

Timeouts and retries: enforce per submission limits, prevent queue starvation.

Caching: for repeated identical submissions (deduplication).

Interactive execution: support stdin streaming for interactive challenges.

Analytics: submission patterns, execution times, popular languages.

0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x