System Design в IT-проектах

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

1. Роль System Design и постановка задачи

Роль System Design и постановка задачи

Зачем нужен System Design в IT-проектах

System Design — это практический процесс преобразования бизнес-задачи в техническое решение: что строим, для кого, в каких условиях и с какими компромиссами.

System Design нужен, чтобы:

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

    Что входит в роль System Design в команде

    System Design не равен “нарисовать архитектурную схему”. Это цикл решений и коммуникаций.

    Ключевые обязанности:

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

  • Tech Lead / Senior Engineer — часто ведёт дизайн внутри команды.
  • Solution Architect — фокус на интеграциях и согласовании решения между системами.
  • System/Platform Architect — фокус на платформенных стандартах, масштабировании, SRE/DevOps практиках.
  • Важно: роль не обязательно привязана к должности. В небольших командах System Design выполняет один человек, в крупных — это совместная работа.

    “Постановка задачи” в контексте System Design

    Постановка задачи — это этап, где вы превращаете размытое “надо сделать фичу/сервис” в чёткое описание:

  • Какую проблему решаем и зачем.
  • Для кого и какие сценарии использования важны.
  • Какие требования обязательны, а какие желательны.
  • Какие ограничения нельзя нарушать.
  • Как поймём, что решение успешное.
  • Если этот этап пропустить, дизайн будет строиться на догадках: вы можете оптимизировать не то (например, производительность вместо удобства эксплуатации) или построить лишнее.

    Входы и выходы System Design

    Типичные входные данные

  • Бизнес-цель и метрики успеха.
  • Описание пользователей и контекста использования.
  • Текущая архитектура и точки интеграции.
  • Ожидаемые нагрузки и рост.
  • Ограничения по срокам, бюджету, инфраструктуре и технологиям.
  • Регуляторные требования и требования безопасности.
  • Типичные выходные артефакты

  • Сформулированная задача и границы системы.
  • Список функциональных и нефункциональных требований.
  • Высокоуровневая архитектура (HLD) и ключевые решения.
  • Описание основных данных и интерфейсов.
  • Риски и план их снижения.
  • План итеративной поставки (что делаем в MVP, что позже).
  • Как правильно формулировать задачу: минимальный “каркас”

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

    Цель и мотивация

  • Какую проблему решаем.
  • Почему сейчас.
  • Какая ценность для бизнеса и пользователей.
  • Стейкхолдеры и пользователи

  • Кто заказчик и кто будет принимать результат.
  • Кто реальные пользователи (персоны).
  • Кто сопровождает систему (эксплуатация, поддержка).
  • Сценарии использования

  • Основной сценарий (самый частый и важный).
  • Критические сценарии (ошибки, отмены, повторы, деградации).
  • Административные сценарии (настройка, мониторинг, разбор инцидентов).
  • Границы системы: in scope и out of scope

    Чтобы дизайн был управляемым, нужно явно отделить:

  • Что система делает.
  • Что система не делает (даже если “когда-нибудь” понадобится).
  • Пример формулировки out of scope:

  • “Не реализуем аналитику в реальном времени в первой версии”.
  • “Не поддерживаем офлайн-режим на старте”.
  • Функциональные требования

    Функциональные требования описывают что система должна уметь.

    Примеры:

  • Пользователь может создать заказ.
  • Система отправляет уведомление о статусе.
  • Администратор может отменить операцию.
  • Нефункциональные требования

    Нефункциональные требования описывают как система должна работать и в каких условиях.

    Ключевые категории:

  • Производительность (время ответа, пропускная способность).
  • Надёжность и доступность (ожидаемая доступность, поведение при сбоях).
  • Масштабируемость (рост нагрузки, горизонтальное масштабирование).
  • Безопасность (аутентификация, авторизация, аудит, шифрование).
  • Наблюдаемость (логи, метрики, трассировки, алерты).
  • Стоимость (ограничения на инфраструктурные расходы).
  • Сопровождаемость (простота релизов, откатов, тестирования).
  • Полезно фиксировать нефункциональные требования в измеримом виде. Например, не “быстро”, а “95-й перцентиль времени ответа не более X мс при Y запросах в секунду”.

    Ограничения, допущения и зависимости

    На этом этапе часто выявляется, что “идеальная архитектура” недостижима в текущей реальности.

    Что важно зафиксировать:

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

  • “Пиковая нагрузка не превышает N запросов в секунду в первый квартал”.
  • “Внешний платёжный провайдер обеспечивает SLA X”.
  • Если допущения ошибочны, дизайн может “сломаться”. Поэтому допущения нужно либо проверять, либо закладывать механизмы адаптации.

    Критерии успеха и “Definition of Done” для дизайна

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

    Примеры критериев успеха:

  • Понятны границы системы и интеграции.
  • Перечислены ключевые сценарии и крайние случаи.
  • Согласованы нефункциональные требования (хотя бы для MVP).
  • Известны главные риски и меры по снижению.
  • Команда может оценить объём работ и разбить на итерации.
  • Типичные ошибки постановки задачи

  • Подмена цели решением: “давайте сделаем микросервисы”, вместо “нужно снизить время вывода изменений и повысить устойчивость”.
  • Отсутствие out of scope: система разрастается до бесконечности.
  • Игнорирование нефункциональных требований: “потом оптимизируем”, но потом оказывается поздно или дорого.
  • Неучтённые владельцы и ответственность: непонятно, кто поддерживает компонент после релиза.
  • Неоговорённые компромиссы: решения принимаются “по ощущениям”, а не по приоритетам.
  • Мини-кейс: как меняется дизайн от постановки задачи

    Представим запрос: “Нужен сервис коротких ссылок для маркетинга”.

    Если поставить задачу поверхностно, можно быстро сделать простую CRUD-базу.

    Если задать уточняющие вопросы, выясняется:

  • Нужны редиректы с минимальной задержкой.
  • Ожидаются пики трафика из рекламных кампаний.
  • Нужна защита от злоупотреблений (спам, вредоносные ссылки).
  • Важна аналитика переходов, но не обязательно в реальном времени.
  • Нужен срок хранения и политика удаления.
  • После этого меняется дизайн: появляется кэширование редиректов, лимиты и антиабьюз, разделение “поток редиректов” и “поток аналитики”, требования к наблюдаемости.

    Базовая визуализация, которая помогает почти всегда

    !Диаграмма контекста показывает границы системы и внешние зависимости

    Как эта тема связана с дальнейшими темами курса

    Постановка задачи — фундамент. Дальше в курсе мы будем последовательно “достраивать” решение:

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

  • AWS Well-Architected Framework
  • Martin Fowler: Architecture
  • Google SRE Book (онлайн-версия)
  • 2. Требования: функциональные, нефункциональные, ограничения

    Требования: функциональные, нефункциональные, ограничения

    Зачем отдельно разбирать требования в System Design

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

  • Спроектировать (архитектура, данные, интеграции).
  • Оценить (объём работ, риски, стоимость).
  • Проверить (тестами, нагрузочными измерениями, аудитом безопасности).
  • В System Design требования обычно делят на три группы:

  • Функциональные требования: что система должна делать.
  • Нефункциональные требования: как система должна это делать и в каких условиях.
  • Ограничения: что нельзя менять или нарушать (рамки, в которых живёт решение).
  • Важно: эти группы связаны. Один и тот же пункт иногда можно трактовать по-разному, но цель классификации — не “идеальная терминология”, а управляемое принятие решений.

    !Диаграмма показывает различие и пересечения типов требований

    Функциональные требования

    Функциональные требования описывают поведение системы в терминах пользовательских и бизнес-сценариев.

    Как выглядит хорошее функциональное требование

    Хорошее функциональное требование:

  • Привязано к актору (кто делает действие).
  • Описывает действие и ожидаемый результат.
  • Уточняет границы: что считается успехом и что считается ошибкой.
  • Примеры:

  • Пользователь может зарегистрироваться по email.
  • Пользователь может создать заказ с позициями и адресом доставки.
  • Система отправляет уведомление о смене статуса заказа.
  • Администратор может заблокировать пользователя.
  • Техника: сценарии и крайние случаи

    Функциональные требования удобно собирать как сценарии. Минимальный набор сценариев для любой “боевой” функции:

  • Основной сценарий (happy path).
  • Ошибки ввода и валидация.
  • Повтор запроса (клиент ретраит, пользователь нажал дважды).
  • Отмена действия (где применимо).
  • Параллельные действия (гонки, например два обновления одного объекта).
  • Если эти случаи не зафиксировать, архитектурные решения будут “слепыми”: например, можно забыть про идемпотентность или про необходимость версионирования.

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

    Нефункциональные требования (НФТ) задают свойства системы: производительность, доступность, безопасность, наблюдаемость и так далее. Именно НФТ чаще всего “ломают” наивный дизайн, потому что они требуют дополнительных компонентов и компромиссов.

    Основные категории НФТ

    Ниже — практичный список категорий, которые стоит проходить как чеклист:

  • Производительность (latency, throughput).
  • Доступность и надёжность (SLA/SLO, отказоустойчивость).
  • Масштабируемость (рост нагрузки, горизонтальное масштабирование).
  • Консистентность данных (требования к согласованности).
  • Безопасность (аутентификация, авторизация, аудит, шифрование).
  • Приватность и соответствие требованиям (например, хранение персональных данных).
  • Наблюдаемость (логи, метрики, трассировки, алерты).
  • Сопровождаемость (релизы, откаты, тестирование, миграции).
  • Стоимость (лимиты на инфраструктуру, стоимость запроса).
  • Как сделать НФТ измеримыми

    Плохая формулировка НФТ обычно звучит так: “быстро”, “надёжно”, “безопасно”. Хорошая — содержит метрику и условия.

    Примеры измеримых формулировок:

  • p95 времени ответа API “создать заказ” не более 300 мс при 200 RPS.
  • Доступность внешнего API редиректа не ниже 99.9% в месяц.
  • RPO не более 15 минут, RTO не более 60 минут.
  • Журналы аудита для операций администратора хранятся 180 дней.
  • Пояснение терминов:

  • p95 — 95-й перцентиль: 95% запросов укладываются в указанное время.
  • RPO (Recovery Point Objective) — сколько данных можно потерять при аварии.
  • RTO (Recovery Time Objective) — за какое время сервис должен восстановиться.
  • Почему НФТ нельзя “добавить потом”

    Некоторые свойства действительно можно улучшать итеративно, но многие НФТ влияют на фундамент:

  • Доступность влияет на топологию (репликации, мультизона, фейловер).
  • Производительность влияет на выбор хранилища, кэширование и модель данных.
  • Безопасность влияет на границы доверия, секреты, модель прав.
  • Наблюдаемость влияет на стандарты логирования, трассировки и корреляционные идентификаторы.
  • Поэтому для MVP часто фиксируют минимальный набор НФТ, без которого нельзя выпускаться.

    Ограничения

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

    Типичные источники ограничений:

  • Технологические (разрешённые языки, базы, облака).
  • Инфраструктурные (только on-prem, нет Kubernetes, один регион).
  • Организационные (маленькая команда, нет 24/7 дежурства).
  • Финансовые (лимит расходов, ограничение на платные сервисы).
  • Регуляторные (хранение данных в определённой стране, требования аудита).
  • Внешние зависимости (SLA провайдера, лимиты API, формат данных партнёра).
  • Примеры:

  • “Храним данные только в определённом регионе”.
  • “Нельзя использовать managed-сервисы, только собственная инфраструктура”.
  • “Интеграция только через SFTP, без API”.
  • “Релизы возможны раз в две недели из-за процесса согласования”.
  • Ограничения полезно фиксировать отдельно, чтобы команда не тратила время на “идеальную” архитектуру, которая всё равно запрещена условиями.

    Как отличать НФТ от ограничений: практическое правило

    Иногда одно и то же звучит похоже.

  • НФТ — это какое свойство нужно обеспечить.
  • Ограничение — это каким способом нельзя/нужно обеспечить или какие рамки нельзя нарушать.
  • Примеры:

  • “Доступность 99.9%” — НФТ.
  • “Только один датацентр” — ограничение, которое усложняет достижение доступности.
  • “Шифрование данных на диске” — НФТ (требование к безопасности).
  • “Используем только KMS провайдера X” — ограничение (предписанный механизм).
  • Как документировать требования, чтобы ими можно было пользоваться

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

    Мини-шаблон требований для System Design

    Ниже — компактная структура, которая хорошо сочетается с RFC-форматом или описанием эпика.

  • Контекст и границы (in/out of scope).
  • Функциональные требования списком сценариев.
  • Нефункциональные требования с метриками.
  • Ограничения, допущения, зависимости.
  • Нерешённые вопросы.
  • Язык требований: “обязательно” и “желательно”

    Чтобы избежать двусмысленности, удобно использовать модальные слова:

  • MUST — обязательно.
  • SHOULD — желательно, но возможны исключения.
  • MAY — опционально.
  • Эта практика формализована в RFC 2119.

    Трассируемость: от требований к архитектуре и тестам

    В System Design полезно явно связывать требования с решениями и проверками.

    Таблица-пример:

    | Требование | Тип | Архитектурное решение | Как проверяем | |---|---|---|---| | Создание заказа | Функциональное | API + сервис заказов + БД | Интеграционные тесты сценариев | | p95 < 300 мс при 200 RPS | Нефункциональное | Кэш, индексы, лимиты, профилирование | Нагрузочные тесты, APM | | Доступность 99.9% | Нефункциональное | Репликация, health-check, фейловер | SLO-отчёты, game days | | Только PostgreSQL | Ограничение | Выбор схемы и транзакций под Postgres | Архитектурный ревью | | Логи аудита 180 дней | Нефункциональное | Аудит-события + защищённое хранилище | Проверка ретенции, аудит |

    Идея проста: если требование не влияет на дизайн и не имеет способа проверки, оно либо сформулировано слишком абстрактно, либо не является реальным требованием.

    Типичные ошибки при работе с требованиями

  • Подмена требований решениями: “сделаем микросервисы” вместо “нужны независимые релизы и изоляция отказов”.
  • Смешение обязательного и желательного: без приоритета команда “тонет” в ожиданиях.
  • НФТ без метрик: невозможно доказать, что система соответствует ожиданиям.
  • Игнорирование ограничений: красивый дизайн не проходит по бюджету, срокам или правилам.
  • Нет владельца требования: не ясно, кто принимает решение, если требование конфликтует с другим.
  • Как тема требований связана со следующими шагами курса

    Требования — это вход в архитектурное проектирование:

  • Из функциональных требований рождается декомпозиция на компоненты, границы сервисов и API.
  • Из нефункциональных требований — решения по масштабированию, хранению данных, отказоустойчивости, консистентности и наблюдаемости.
  • Из ограничений — реалистичный выбор технологий и план поставки (MVP и дальнейшие итерации).
  • Полезные источники

  • AWS Well-Architected Framework
  • RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
  • Google SRE Book
  • 3. Архитектурные стили и декомпозиция на компоненты

    Архитектурные стили и декомпозиция на компоненты

    Как эта тема связана с предыдущими

    В прошлых статьях мы разобрали:

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

  • делает ключевые сценарии простыми в реализации
  • обеспечивает нужные нефункциональные свойства (надёжность, масштабирование, безопасность)
  • учитывает ограничения (команда, инфраструктура, сроки)
  • Базовые понятия: стиль, компонент, граница

    Архитектурный стиль

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

    Важно: стиль — не “модная технология”. Это выбранный набор компромиссов.

    Компонент

    Компонент — часть системы с чёткой ответственностью и контрактом взаимодействия.

    Компонентом может быть:

  • модуль внутри одного приложения
  • отдельный сервис
  • очередь сообщений
  • база данных как самостоятельный элемент дизайна
  • Граница (boundary)

    Граница — место, где меняются правила и ответственность:

  • другой владелец (команда)
  • другой жизненный цикл релизов
  • другое хранилище данных
  • другой протокол (HTTP вместо вызова функции)
  • Чем больше границ, тем выше накладные расходы на интеграцию, тестирование и эксплуатацию.

    Зачем нужна декомпозиция

    Декомпозиция отвечает на практичные вопросы:

  • какие части системы можно разрабатывать параллельно
  • где изолировать изменения, чтобы они не ломали всё вокруг
  • где нужны отдельные масштабирование и отказоустойчивость
  • как управлять сложностью (чтобы код и процессы оставались поддерживаемыми)
  • Цель декомпозиции — не “порезать на микросервисы”, а сделать систему управляемой при ваших требованиях и ограничениях.

    !Диаграмма показывает, что стиль и декомпозиция являются следствием требований и ограничений

    Основные архитектурные стили

    Ниже — стили, которые чаще всего встречаются в реальных IT-проектах. Их можно комбинировать, но полезно понимать “центр тяжести” решения.

    Монолит

    Монолит — единое приложение (часто единый процесс и деплой), где модули взаимодействуют через вызовы функций, а не через сеть.

    Когда подходит:

  • небольшой или средний продукт, важна скорость разработки
  • команда небольшая, нет выделенной SRE/платформы
  • требования по независимым релизам частей системы пока не критичны
  • Риски:

  • сложнее масштабировать разные части по-разному
  • со временем растёт связность, релизы становятся “тяжёлыми”
  • Полезный ориентир: часто лучше начать с монолита, но сразу держать модульные границы. См. MonolithFirst.

    Модульный монолит

    Модульный монолит — монолит, в котором архитектурно и организационно закреплены границы модулей:

  • явные зависимости между модулями
  • минимизация общего “глобального состояния”
  • контрактные интерфейсы между частями
  • Когда подходит:

  • хотите скорость монолита, но готовитесь к росту и усложнению
  • есть риск, что система станет “спагетти”, если не ввести границы заранее
  • Ключевая идея: сначала научиться декомпозировать внутри процесса, и только потом переносить границы “через сеть”, если это действительно нужно.

    Микросервисы

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

    Плюсы:

  • независимые релизы и изоляция изменений
  • независимое масштабирование “горячих” компонентов
  • ограничение размера контекста для команд
  • Минусы (часто недооценённые):

  • распределённые сбои и сложная отладка
  • накладные расходы на инфраструктуру, наблюдаемость, контракты
  • сложнее обеспечить согласованность данных
  • Микросервисы обычно оправданы, когда нефункциональные требования и организация (много команд, независимые релизы, высокая нагрузка на отдельные части) “окупают” сложность. Практический ввод — Microservices.

    Событийно-ориентированная архитектура (Event-Driven)

    Event-Driven подход строится вокруг событий (фактов), которые публикуются и потребляются компонентами асинхронно.

    Когда подходит:

  • много интеграций и реакций на изменения состояния
  • нагрузка “скачет”, и важно сглаживать пики очередями
  • важна слабая связность между производителями и потребителями
  • Типичные риски:

  • сложнее трассировать бизнес-процесс “сквозь систему”
  • появляются вопросы идемпотентности, порядка событий, повторной обработки
  • Событийность хорошо сочетается и с монолитом (внутренние события), и с микросервисами (брокер сообщений).

    Слоистая архитектура (Layered)

    Layered architecture — разделение на слои, например:

  • API/контроллеры
  • бизнес-логика
  • доступ к данным
  • Когда подходит:

  • типичные CRUD-сценарии
  • нужны понятные правила структуры кода
  • Риск:

  • слой “бизнес-логики” может превратиться в большой общий ком
  • Слои — полезный инструмент, но сами по себе не решают проблему границ домена.

    Гексагональная архитектура (Ports and Adapters)

    Гексагональная архитектура (она же Ports and Adapters) отделяет доменную логику от внешних деталей (БД, HTTP, очереди):

  • домен общается с внешним миром через “порты” (интерфейсы)
  • конкретные интеграции реализуются “адаптерами”
  • Когда подходит:

  • важна тестируемость и заменяемость инфраструктуры
  • много интеграций, которые меняются
  • Хороший обзор — Hexagonal Architecture.

    CQRS (разделение чтения и записи)

    CQRS — разделение моделей и путей обработки команд (запись) и запросов (чтение).

    Когда подходит:

  • чтение и запись имеют разные требования по производительности
  • нужны разные представления данных для разных сценариев
  • Риск:

  • усложнение данных и консистентности
  • CQRS не обязательно означает Event Sourcing и не обязан быть “везде”. Часто это точечная оптимизация для узких мест. См. CQRS.

    Serverless (FaaS + managed-сервисы)

    Serverless — стиль, где значимая часть инфраструктуры скрыта провайдером, а логика реализуется функциями и управляемыми сервисами.

    Когда подходит:

  • нерегулярная нагрузка, важна оплата “по факту”
  • нужен быстрый time-to-market
  • команда не хочет (или не может) содержать инфраструктуру
  • Ограничения:

  • vendor lock-in
  • холодные старты и лимиты выполнения
  • сложнее тестировать end-to-end без эмуляторов
  • Как выбирать стиль: связь с требованиями

    Полезно не “голосовать за стиль”, а проверять его через требования.

    Быстрый чеклист вопросов

  • Какие 2–3 сценария самые критичные и частые?
  • Где ожидаются пиковые нагрузки и рост?
  • Нужны ли независимые релизы частей системы?
  • Какие требования к доступности (например, 99.9%) и восстановлению?
  • Насколько критична консистентность данных?
  • Какой уровень зрелости эксплуатации доступен (наблюдаемость, on-call, SRE)?
  • Какие есть жёсткие ограничения по технологиям и инфраструктуре?
  • Таблица: стили и типичные компромиссы

    | Стиль | Сильные стороны | Цена/риски | Частые триггеры выбора | |---|---|---|---| | Монолит | Скорость разработки, простая отладка | Сложнее независимые релизы и изоляция масштабирования | MVP, небольшая команда, ограниченный бюджет | | Модульный монолит | Контроль сложности без распределённости | Требует дисциплины границ | Рост функционала, подготовка к масштабированию команд | | Микросервисы | Автономность, независимые релизы | Высокая сложность эксплуатации | Много команд, разные профили нагрузки, нужна изоляция отказов | | Event-Driven | Слабая связность, асинхронность | Труднее трассировка и консистентность | Много реакций на события, интеграции, пики нагрузки | | Hexagonal | Тестируемость, сменяемость интеграций | Дисциплина и больше “слоёв” в коде | Интеграционно насыщенные домены | | CQRS | Оптимизация чтения/записи | Усложнение данных | Сильно отличается профиль чтения и записи | | Serverless | Быстрый старт, меньше ops | Lock-in, лимиты | Нерегулярная нагрузка, небольшой ops-ресурс |

    Декомпозиция: как разрезать систему на компоненты

    Принципы хорошей декомпозиции

  • Высокая связность внутри компонента: всё, что относится к одной ответственности, находится рядом.
  • Низкая связность между компонентами: изменения в одном компоненте минимально затрагивают другие.
  • Явные контракты: API, события, схемы данных, версии.
  • Явное владение данными: понятно, кто “источник истины” для каждой сущности.
  • Типовые подходы к разрезанию

    #### По слоям (техническая декомпозиция)

    Пример: отдельно “UI”, отдельно “Backend”, отдельно “DB”.

    Плюсы:

  • понятно, как начинать
  • совпадает с навыками многих команд
  • Минусы:

  • бизнес-изменение затрагивает все слои
  • легко получить “общую бизнес-логику”, которую никто не контролирует
  • #### По доменам и сценариям (вертикальные срезы)

    Пример: “Заказы”, “Оплата”, “Доставка”, “Каталог”.

    Плюсы:

  • бизнес-изменения локализуются
  • проще выделять владение и ответственность
  • Минусы:

  • сложнее в начале, нужны обсуждения границ
  • Практический ориентир для доменных границ — bounded context из DDD. Обзорный материал: Domain-Driven Design.

    Алгоритм декомпозиции (практика)

  • Выпишите ключевые сценарии (из функциональных требований).
  • Выделите основные сущности и их жизненный цикл.
  • Сгруппируйте сущности и сценарии по “одной причине для изменения”.
  • Проведите границы владения данными: у каждой сущности должен быть “хозяин”.
  • Опишите взаимодействия: синхронные API и асинхронные события.
  • Проверьте НФТ: где нужен кэш, очередь, репликация, rate limiting.
  • Проверьте ограничения: что команда реально может поддерживать.
  • !Пример декомпозиции на доменные компоненты и способы взаимодействия

    Контракты взаимодействия: API и события

    Синхронные вызовы (API)

    Синхронное взаимодействие (например, HTTP/gRPC) удобно, когда:

  • нужен мгновенный ответ пользователю
  • операция короткая и надёжность канала высокая
  • Риски:

  • каскадные отказы (если зависимый сервис медленный или недоступен)
  • рост латентности из-за цепочек вызовов
  • Практики защиты:

  • таймауты и лимиты
  • ретраи только там, где это безопасно
  • circuit breaker
  • деградация (fallback)
  • Асинхронные события

    События подходят, когда:

  • можно обработать действие “в фоне”
  • важно развязать компоненты по времени
  • нужно подключать новых потребителей без изменения отправителя
  • Критические понятия, которые стоит зафиксировать в дизайне:

  • идемпотентность: повтор события не должен ломать состояние
  • гарантии доставки: “хотя бы один раз” чаще реалистичнее, чем “ровно один раз”
  • схема события и версионирование: потребители должны переживать изменения формата
  • Данные и границы: кто владеет источником истины

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

    Практичное правило:

  • у каждой сущности есть один компонент-владелец, который принимает решения об изменениях
  • другие компоненты получают данные через API владельца или через реплики (read model), но не “пишут напрямую”
  • Антипаттерн:

  • “общая база на всех сервисов” как быстрый старт микросервисов
  • Это может быть временно допустимо как миграционный шаг, но тогда границы сервисов остаются условными: изменения схемы и транзакции превращаются в общий риск.

    Частые ошибки при выборе стиля и декомпозиции

  • Выбор микросервисов без готовности к эксплуатации: нет наблюдаемости, нет контрактного тестирования, нет практик релизов.
  • Декомпозиция по технологиям вместо домена, из-за чего любое бизнес-изменение требует синхронизации нескольких команд.
  • Неявные контракты: нет версионирования API/событий, изменения ломают потребителей.
  • Нет владельца данных: несколько компонентов “думают”, что они источники истины.
  • Границы не соответствуют требованиям: например, выделили сервис, который всегда должен вызываться синхронно в критической цепочке, и получили лишнюю латентность.
  • Что фиксировать в артефактах System Design

    Минимальный набор, который помогает команде реализовать и сопровождать архитектуру:

  • Диаграмма контекста: внешние системы и границы ответственности.
  • Диаграмма компонентов/контейнеров: основные части системы и связи.
  • Контракты: ключевые API, события, схемы данных и версия.
  • Владение данными: таблица “сущность → владелец”.
  • Ключевые архитектурные решения и их причины (ADR-подход полезен). См. Documenting Architecture Decisions.
  • Связь с дальнейшими темами курса

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

  • проектирование API и потоков данных
  • выбор хранилищ, консистентность и транзакционные границы
  • масштабирование, отказоустойчивость и деградация
  • наблюдаемость и эксплуатационные практики
  • Полезные источники

  • Martin Fowler: Microservices
  • Martin Fowler: MonolithFirst
  • Alistair Cockburn: Hexagonal Architecture
  • Martin Fowler: CQRS
  • Cognitect: Documenting Architecture Decisions
  • 4. Данные: моделирование, базы, кэш, согласованность

    Данные: моделирование, базы, кэш, согласованность

    Как эта тема связана с предыдущими

    В прошлых статьях мы:

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

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

  • Сущность — объект предметной области, например Заказ, Пользователь, Платёж.
  • Модель данных — как сущности и их связи представлены в хранилище (таблицы, документы, ключи, граф).
  • Источник истины — компонент и хранилище, которым “верят” при конфликтах и восстановлении.
  • Согласованность — насколько разные части системы видят одни и те же данные одинаково и когда.
  • Транзакция — набор изменений, который применяется как единое целое: либо всё, либо ничего.
  • Моделирование данных: от сценариев к структуре

    Начинаем со сценариев, а не с таблиц

    Модель данных должна обслуживать ключевые сценарии. Практичный порядок:

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

    В реальных системах важнее не “красивые связи”, а границы изменений.

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

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

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

  • Если система ориентирована на запись и корректность, нормализация обычно выигрывает.
  • Если система ориентирована на чтение и простые быстрые ответы, денормализация часто неизбежна.
  • Цена денормализации:

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

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

    Что важно фиксировать в дизайне:

  • Какие запросы должны быть быстрыми.
  • Какие поля фильтрации и сортировки нужны.
  • Какой рост объёма данных ожидается.
  • Полезная отправная точка для SQL — документация по индексам PostgreSQL: PostgreSQL: Indexes.

    Эволюция схемы и миграции

    Данные живут дольше кода, поэтому изменения нужно планировать:

  • Добавление поля обычно безопаснее, чем переименование или изменение типа.
  • Для совместимости полезна стратегия сначала пишем в новое, потом читаем из нового.
  • Для API и событий нужны версии схем.
  • Выбор базы данных: не “лучшая”, а подходящая

    Какие характеристики сравнивать

    При выборе хранилища сравнивают не только скорость:

  • Модель данных и запросов (реляционная, документная, ключ-значение).
  • Транзакции и изоляция.
  • Масштабирование (вертикальное, репликация, шардинг).
  • Операционная сложность (резервные копии, обновления, мониторинг).
  • Ограничения проекта (облако, лицензии, навыки команды).
  • Основные классы хранилищ

    #### Реляционные базы (SQL)

    Подходят, когда:

  • Много связей и сложных запросов.
  • Нужны транзакции и строгая корректность.
  • Важны ограничения целостности.
  • Типичный представитель — PostgreSQL: PostgreSQL Documentation.

    #### Документные базы

    Документная база хранит объект целиком, обычно в формате, похожем на JSON.

    Подходит, когда:

  • Данные естественно группируются в документ.
  • Схема часто меняется.
  • Запросы часто читают объект целиком.
  • Пример — MongoDB: MongoDB Manual.

    #### Ключ-значение

    Подходит, когда:

  • Нужны быстрые операции get/set.
  • Данные легко адресуются ключом.
  • Часто используется для кэша и сессий.
  • Классический инструмент — Redis: Redis Documentation.

    #### Колонночные аналитические хранилища

    Часто используются для аналитики, где запросы читают много строк, но мало колонок.

    В рамках System Design важно отделять:

  • OLTP — операционные транзакции (заказы, платежи).
  • OLAP — аналитика (отчёты, агрегации).
  • Репликация, шардинг и партиционирование

  • Репликация — копирование данных для отказоустойчивости и распределения чтения.
  • Шардинг — разбиение данных по нескольким узлам по ключу (например, user_id).
  • Партиционирование — разбиение таблицы на части внутри одной БД или кластера по правилам (часто по времени).
  • Практические последствия:

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

    Кэширование: где ускоряем и какие риски берём

    Зачем нужен кэш

    Кэш используют, когда нужно:

  • Снизить задержку ответов.
  • Уменьшить нагрузку на базу.
  • Защититься от пиков чтения.
  • Но кэш почти всегда добавляет риск рассогласования и усложняет инвалидацию.

    Основные паттерны кэширования

    #### Cache-aside

    Приложение:

  • Сначала ищет в кэше.
  • При промахе читает из БД и кладёт результат в кэш.
  • Плюсы:

  • Простая логика.
  • Кэш можно отключить без потери корректности.
  • Минусы:

  • Инвалидация после записи ложится на приложение.
  • #### Write-through

    Запись идёт:

  • В кэш.
  • И синхронно в БД.
  • Плюсы:

  • Кэш чаще актуален.
  • Минусы:

  • Запись медленнее.
  • Нужно аккуратно обрабатывать ошибки частичной записи.
  • #### Write-behind

    Запись идёт:

  • В кэш сразу.
  • В БД асинхронно из очереди.
  • Плюсы:

  • Очень быстрые записи.
  • Минусы:

  • Риск потери данных при сбое.
  • Сложнее гарантировать корректность.
  • !Сравнение паттернов кэширования по потокам чтения и записи

    Инвалидация кэша и TTL

    Ключевая проблема кэша — как понять, что значение устарело.

    Инструменты:

  • TTL — время жизни записи в кэше.
  • Инвалидация по событию (после изменения в БД публикуем событие и чистим ключи).
  • Версионирование ключей (например, profile:v3:user:123).
  • Проверка здравого смысла:

  • Если данные меняются часто и критична корректность, кэш должен быть очень осторожным или вообще не применяться.
  • Если небольшая задержка допустима, TTL обычно даёт лучший баланс простоты и пользы.
  • Защита от проблем кэша

    Типовые проблемы и меры:

  • Cache stampede — много запросов одновременно “пробивают” кэш.
  • Hot key — один ключ перегружает кэш и базу.
  • Thundering herd — лавинообразные повторные попытки.
  • Практики:

  • Блокировка на ключ при пересчёте.
  • Рандомизация TTL, чтобы ключи не истекали одновременно.
  • Лимиты, деградация и отдача “чуть устаревшего” значения, если бизнес допускает.
  • Согласованность данных: что гарантируем и где

    Виды согласованности в прикладных терминах

    В System Design важно формулировать согласованность не абстрактно, а через ожидания пользователей.

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

  • Поиска.
  • Рекомендаций.
  • Аналитики.
  • Лент и витрин.
  • Сильная согласованность чаще нужна для:

  • Денег и лимитов.
  • Складских остатков.
  • Прав доступа.
  • Транзакции и уровни изоляции

    Транзакция защищает от частичных обновлений и гонок.

    Уровень изоляции определяет, какие эффекты параллельной работы допустимы.

    Практическая рекомендация для дизайна:

  • Начинайте с изоляции и транзакций, которые даёт ваша основная реляционная БД.
  • Переходите к более сложным схемам только когда понятно, что одна транзакционная граница не покрывает ваш сценарий.
  • Справочник по транзакциям и изоляции в PostgreSQL: PostgreSQL: Transaction Isolation.

    Согласованность в распределённых системах

    Как только данные и логика разделены на компоненты, появляются новые вопросы:

  • Что делать, если один компонент записал данные, а второй упал?
  • Как не потерять событие между записью в БД и публикацией в брокер?
  • Как обрабатывать повторы сообщений?
  • Две практики, которые часто становятся стандартом.

    #### Outbox pattern

    Идея:

  • В той же транзакции, что и бизнес-изменение, пишем запись в таблицу outbox.
  • Отдельный процесс читает outbox и публикует события в брокер.
  • Плюс:

  • Событие не “теряется” между БД и брокером.
  • Минус:

  • Появляется асинхронность и задержка доставки.
  • #### Идемпотентность

    Идемпотентность — повторная обработка сообщения не меняет результат.

    Она нужна, потому что на практике доставка часто “как минимум один раз”, то есть повторы возможны.

    Обычные приёмы:

  • Уникальный id операции и таблица обработанных id.
  • Версионирование сущности.
  • Условные обновления.
  • !Outbox и идемпотентность: как надёжно публиковать события и переживать повторы

    Саги вместо распределённых транзакций

    Если бизнес-процесс затрагивает несколько сервисов, часто используют сагу:

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

  • Создать заказ.
  • Зарезервировать деньги.
  • Зарезервировать товар.
  • Если товар не удалось — отменить резерв денег.
  • Такой процесс может быть событийным и занимать время, но он лучше масштабируется организационно и технически, чем попытка держать одну транзакцию на несколько сервисов.

    Практичный чеклист для System Design по данным

    Перед тем как считать дизайн готовым, проверьте:

  • У каждой сущности есть владелец и источник истины.
  • Понятны транзакционные границы и где нужна сильная согласованность.
  • Зафиксированы требования к задержке чтения и записи.
  • Понятно, где применяется кэш и как работает инвалидация.
  • Для событий есть стратегия повторов, идемпотентности и версионирования схем.
  • Есть план миграций схемы и совместимости при релизах.
  • Что фиксировать в артефактах дизайна

    Минимальный набор, который помогает реализации и сопровождению:

  • Диаграмма компонентов с владельцами данных.
  • Таблица “сущность → владелец → хранилище → требования к согласованности”.
  • Описание ключевых запросов и индексов.
  • Описание кэша: что кэшируем, TTL, инвалидация.
  • Потоки событий: какие события, кто публикует, кто потребляет, как обеспечивается идемпотентность.
  • Полезные источники

  • PostgreSQL Documentation
  • PostgreSQL: Indexes
  • PostgreSQL: Transaction Isolation
  • MongoDB Manual
  • Redis Documentation
  • 5. Масштабирование и производительность: нагрузки, балансировка, очереди

    Масштабирование и производительность: нагрузки, балансировка, очереди

    Связь с предыдущими темами курса

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

    Важная идея: производительность почти никогда не «лечится» одной оптимизацией. Обычно это комбинация:

  • правильной модели нагрузки
  • ограничения и управления трафиком
  • балансировки и горизонтального масштабирования
  • асинхронности через очереди
  • наблюдаемости и итеративных измерений
  • !Обзорная схема, показывающая, где обычно решаются вопросы производительности и масштабирования

    Термины, которые нужны дальше

  • Нагрузка — входящий поток запросов и фоновых задач (частота, размеры, распределение по времени).
  • Пропускная способность (throughput) — сколько запросов/сообщений система обрабатывает за единицу времени.
  • Задержка (latency) — время обработки одного запроса.
  • Перцентиль — значение, которое не превышается в заданной доле запросов (например, p95).
  • Боттлнек (узкое место) — компонент, который первым упирается в лимит (CPU, I/O, соединения, блокировки).
  • Backpressure — механизм, который замедляет приём работы, когда система перегружена.
  • Очередь — буфер, который отделяет скорость приёма работы от скорости её обработки.
  • Модель нагрузки: прежде чем масштабировать

    Почему средние значения опасны

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

    Практика:

  • фиксировать пиковую нагрузку и её длительность
  • фиксировать долю «тяжёлых» запросов (например, 5% запросов с дорогими джойнами)
  • учитывать сезонность и кампании (маркетинг, распродажи)
  • Что именно описывать в нагрузке

    Удобный минимальный профиль нагрузки включает:

  • входящий поток: RPS, распределение по endpoint’ам, доли чтения/записи
  • размеры: средний и максимальный размер запросов/ответов
  • конкуренцию: сколько одновременных запросов держит клиент (мобильный, браузер, интеграция)
  • время жизни соединений: короткие или долгие (HTTP/1.1 vs HTTP/2, gRPC)
  • фоновые процессы: импорты, пересчёты, обработка событий
  • Зачем нужны перцентили (p95/p99)

    Перцентили помогают видеть «хвост» задержек: даже если среднее время ответа хорошее, p99 может разрушать пользовательский опыт.

    Практический ориентир:

  • p50 показывает «типичный» запрос
  • p95 часто используют как продуктовую метрику скорости
  • p99 важен для выявления редких, но болезненных деградаций
  • !Иллюстрация, почему перцентили важнее среднего

    Базовая математика очередей без «теории ради теории»

    Есть один закон, который полезен в System Design почти всегда: закон Литтла.

    Где:

  • — среднее число задач «в системе» (в обработке и в ожидании)
  • — средняя скорость поступления задач (например, запросов в секунду)
  • — среднее время, которое задача проводит в системе
  • Практический смысл:

  • если входной поток растёт, а время обработки не уменьшается, то число одновременно «висящих» задач растёт
  • рост означает рост очередей, потребления памяти, числа соединений и, как следствие, рост задержек
  • Источник: Little's law.

    Стратегии масштабирования: вертикально, горизонтально, функционально

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

    Вертикальное масштабирование — увеличить ресурсы одного узла (CPU/RAM/IOPS).

    Плюсы:

  • проще внедрить
  • меньше распределённых проблем
  • Минусы:

  • есть верхний предел «размера» машины
  • часто растёт стоимость нелинейно
  • не решает отказоустойчивость само по себе
  • Горизонтальное масштабирование

    Горизонтальное масштабирование — добавить больше инстансов приложения/воркеров.

    Плюсы:

  • линейнее масштабируется для статлес-компонентов
  • улучшает устойчивость при отказе одного узла
  • Минусы:

  • нужен балансировщик и health-check
  • появляются проблемы с состоянием (сессии, кэш, локальные файлы)
  • усложняется эксплуатация и наблюдаемость
  • Функциональное масштабирование

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

    Примеры:

  • отдельно масштабировать API чтения и обработку фоновых задач
  • выделить сервис, который обслуживает только редиректы (короткие ссылки), отдельно от аналитики
  • отделить тяжёлые отчёты от транзакционного контура
  • Производительность как «цепь»: где обычно ломается

    Типовые узкие места

  • CPU: тяжёлая сериализация, шифрование, большие JSON, сложные вычисления
  • RAM: утечки, слишком большие кэши в процессе, большие очереди в памяти
  • I/O: медленная БД, нет индексов, медленные внешние интеграции
  • соединения: лимиты пулов к БД, лимиты файловых дескрипторов
  • блокировки: транзакции, конкурентные обновления, горячие строки
  • Практика поиска узкого места

  • Зафиксировать SLO/SLA и целевые перцентили.
  • Разделить время ответа на этапы (приложение, БД, внешние вызовы).
  • Найти компонент, который первым «упирается» в ресурс.
  • Решать проблему на уровне причины, а не симптома (например, не добавлять кэш, если проблема в блокировках записи).
  • Рекомендуемая база практик: Google SRE Book.

    Балансировка нагрузки: что важно зафиксировать в дизайне

    L4 и L7 балансировка

  • L4 (TCP/UDP) — маршрутизирует соединения на уровне транспорта.
  • L7 (HTTP/gRPC) — понимает запрос (путь, заголовки), может делать умные правила маршрутизации.
  • Практический выбор:

  • L7 нужен для routing по URL, канареечных релизов, A/B, авторизации на периметре.
  • L4 часто проще и быстрее, полезен для протоколов ниже HTTP.
  • Полезные источники:

  • NGINX HTTP Load Balancing
  • AWS Elastic Load Balancing documentation
  • Алгоритмы балансировки

    | Алгоритм | Идея | Где подходит | Риск | |---|---|---|---| | Round-robin | по очереди | однородные инстансы | плохо при разной «тяжести» запросов | | Least connections | на узел с меньшим числом активных соединений | долгие запросы, стриминг | не всегда отражает реальную нагрузку CPU | | Weighted | разные веса узлам | узлы разной мощности | требует актуальных весов | | Consistent hashing | один ключ обычно попадает на один узел | кэширование по ключу, sticky по пользователю | сложнее, нужен ключ и стабильная топология |

    Health-check и деградация

    Балансировщик должен уметь отличать:

  • «инстанс жив» (процесс отвечает)
  • «инстанс здоров» (может обслуживать запросы, есть доступ к БД/кэшу)
  • Практика:

  • отдельный endpoint для health-check
  • быстрые таймауты на проверку
  • защита от ситуации, когда все инстансы одновременно «выпали» из пула из-за слишком строгой проверки
  • Sticky sessions: когда они вредны

    Sticky sessions (привязка пользователя к одному инстансу) иногда упрощают состояние, но часто ухудшают масштабирование.

    Рекомендация:

  • стараться проектировать API как stateless
  • состояние хранить во внешнем хранилище (БД/Redis), а не в памяти инстанса
  • использовать sticky только при явной необходимости и с планом отказоустойчивости
  • Управление перегрузкой: rate limiting, таймауты, backpressure

    Rate limiting

    Rate limiting ограничивает входящий поток, защищая систему и соседей по инфраструктуре.

    Типовые политики:

  • на пользователя
  • на токен/клиентское приложение
  • на IP (осторожно с NAT)
  • на конкретный endpoint
  • Популярные алгоритмы:

  • token bucket
  • leaky bucket
  • Источник: Token bucket.

    Таймауты и ретраи

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

    Практические правила:

  • таймаут должен быть меньше, чем таймаут у вызывающего уровня (чтобы не копить «висящие» запросы)
  • ретраи делать только для операций, которые безопасны (идемпотентны) или имеют ключ идемпотентности
  • добавлять джиттер (случайную задержку), чтобы не создавать волны повторов
  • Backpressure

    Backpressure нужен, когда система не успевает обрабатывать входящий поток.

    Типовые механизмы:

  • возвращать 429 Too Many Requests
  • ограничивать конкуренцию внутри сервиса (пулы, семафоры)
  • ставить запросы в очередь (если бизнес допускает асинхронность)
  • Очереди и асинхронность: когда они необходимы

    Зачем вводить очередь

    Очередь полезна, когда:

  • входящий поток «рваный», а обработка должна быть ровнее
  • обработка долгая и не должна блокировать пользовательский запрос
  • нужна изоляция внешних интеграций (платёжные шлюзы, почта, SMS)
  • нужно гарантировать доставку работы при временных сбоях
  • !Типовой асинхронный контур обработки с очередью, ретраями и DLQ

    Очередь меняет контракт с пользователем

    Синхронно:

  • пользователь ждёт результат
  • система обязана укладываться в latency SLO
  • Асинхронно:

  • пользователь получает подтверждение приёма
  • результат становится доступен позже
  • появляется новое требование: время до обработки (сколько задача ждёт в очереди)
  • Гарантии доставки и идемпотентность

    На практике большинство брокеров дают модель at-least-once: сообщение будет доставлено минимум один раз, но повторы возможны.

    Следствия для дизайна:

  • обработчик должен быть идемпотентным
  • нужны ключи идемпотентности и защита от повторной записи
  • ретраи нужно ограничивать и переводить «неисправимые» сообщения в DLQ (dead-letter queue)
  • Связь с темой данных: паттерн outbox помогает не терять события между транзакцией в БД и публикацией в брокер.

    Очередь как инструмент масштабирования

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

    Что важно зафиксировать:

  • максимальная длина очереди или допустимое время ожидания
  • политика ретраев (сколько раз, с какими интервалами)
  • политика DLQ и процесс разборов
  • порядок сообщений (нужен или нет)
  • Выбор типа брокера: упрощённо

    | Тип решения | Сильная сторона | Типичный сценарий | |---|---|---| | Очередь задач | простая модель «взял и обработал» | фоновые задачи, email/SMS, webhooks | | Лог событий | высокая пропускная способность, ретенция, реплеи | события домена, аналитика, интеграции |

    Примеры документации для ориентира:

  • Apache Kafka Documentation
  • RabbitMQ Documentation
  • Практичный чеклист для System Design по масштабированию

    Перед тем как считать дизайн готовым, проверьте:

  • описан профиль нагрузки (пики, распределение по endpoint’ам, чтение/запись)
  • определены целевые метрики latency (перцентили) и throughput
  • есть стратегия горизонтального масштабирования для stateless-компонентов
  • балансировщик настроен с корректными health-check и таймаутами
  • определены лимиты: rate limiting, ограничения конкуренции, backpressure
  • для асинхронных контуров описаны ретраи, идемпотентность, DLQ
  • есть план нагрузочного тестирования и наблюдаемости (метрики, трассировки)
  • Что фиксировать в артефактах дизайна

    Минимальный набор, который помогает реализации и эксплуатации:

  • таблица endpoint → RPS/p95 → зависимые компоненты → лимиты/таймауты
  • схема балансировки (L4/L7, алгоритм, sticky или нет)
  • схема очередей (топики/очереди, ключи, ретраи, DLQ)
  • решение по backpressure и деградации (что возвращаем клиенту, что ставим в очередь)
  • план проверки: какие нагрузочные тесты и какие SLO-дашборды нужны
  • Полезные источники

  • Google SRE Book
  • NGINX HTTP Load Balancing
  • AWS Elastic Load Balancing documentation
  • Little's law
  • Apache Kafka Documentation
  • RabbitMQ Documentation
  • 6. Надежность и безопасность: отказоустойчивость, наблюдаемость, угрозы

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

    Связь с предыдущими темами курса

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

    Эта тема добавляет два критичных слоя System Design:

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

    !Как требования по надежности и безопасности «приземляются» в архитектурные решения и практики эксплуатации

    Надежность: что это и как её измерять

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

    SLA, SLO, SLI

  • SLI (Service Level Indicator) — измеряемый индикатор качества (например, доля успешных запросов, p95 задержки).
  • SLO (Service Level Objective) — целевое значение SLI (например, "99.9% успешных запросов в месяц").
  • SLA (Service Level Agreement) — контрактное обязательство, часто с последствиями (штрафы, кредиты).
  • Практика System Design: проектировать систему под SLO, а SLA рассматривать как внешнюю упаковку, обычно более консервативную.

    Доступность как доля времени

    Для простых обсуждений полезна формула доступности:

    Где:

  • — доступность за период (например, за месяц)
  • — время, когда сервис был доступен и выполнял SLO
  • — общее время периода
  • Эта формула помогает переводить "девятки" в понятный бюджет простоя (например, 99.9% в месяц это несколько десятков минут допустимой недоступности).

    Error budget и управление изменениями

    Error budget — допустимый "бюджет ошибок" при выбранном SLO. Идея: если бюджет выгорает, команда снижает риск (замедляет релизы, усиливает тестирование, устраняет причины деградаций). Если бюджет не выгорает, команда может позволить более высокий темп изменений.

    Источник практики: Google SRE Book.

    Отказоустойчивость: проектируем систему под сбои

    Отказоустойчивость начинается с признания факта: сбои неизбежны, а значит дизайн должен отвечать не только на вопрос "что делаем", но и "что делаем, когда что-то ломается".

    Типовые классы сбоев

  • Сбой инстанса приложения (процесс упал, OOM, завис).
  • Сбой зоны/узла инфраструктуры (сеть, диск, хост).
  • Деградация зависимости (БД тормозит, внешний API отвечает долго).
  • Логические ошибки (баг релиза, несовместимость схем, ошибочная миграция).
  • Перегрузка (пики трафика, "шторм" ретраев, горячие ключи кэша).
  • Резервирование и устранение единых точек отказа

    Практики, которые обычно фиксируют уже на уровне HLD:

  • Горизонтальное масштабирование stateless-компонентов за балансировщиком.
  • Репликация данных и понятная модель переключения (failover).
  • Размещение в нескольких зонах доступности (если доступно по ограничениям проекта).
  • Устранение SPOF (single point of failure): один инстанс, один брокер без кластера, один секрет без ротации.
  • Таблица-подсказка для дизайна:

    | Компонент | Типичный SPOF | Частый способ убрать SPOF | Цена/сложность | |---|---|---|---| | API-сервис | один инстанс | несколько инстансов + балансировка | нужны health-check и централизованная сессия | | База данных | один мастер без плана аварии | реплика + автоматический или ручной failover | сложнее операции, возможна задержка реплики | | Кэш | один Redis | кластер/репликация, или деградация без кэша | риск рассогласования, нужна стратегия инвалидации | | Очередь | один брокер | кластер/managed-сервис + DLQ | нужно продумать ретраи и идемпотентность |

    Таймауты, ретраи, circuit breaker

    В распределённой системе ожидание по умолчанию опасно. Базовый набор механизмов защиты:

  • Таймауты на все сетевые вызовы.
  • Ретраи только там, где это безопасно (операция идемпотентна или есть ключ идемпотентности).
  • Джиттер в ретраях, чтобы не создавать синхронные волны повторов.
  • Circuit breaker — временно "размыкает" вызовы к деградирующей зависимости, чтобы не добивать её и не копить хвосты.
  • Материал по паттерну: Martin Fowler: Circuit Breaker.

    Bulkhead и изоляция ресурсов

    Bulkhead (переборка) — изоляция ресурсов между потоками работы, чтобы один тип нагрузки не "утопил" остальные.

    Примеры:

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

    Деградация отвечает на вопрос: что мы продолжаем делать, когда не можем сделать всё.

    Типовые варианты:

  • отдавать упрощённый ответ (без рекомендаций, без вторичных блоков)
  • временно отключать дорогие функции (фича-флаг)
  • обслуживать только чтение, а запись ставить в очередь (если бизнес допускает)
  • возвращать 429 или 503 с понятным контрактом для клиента
  • Связь с темой производительности: это практическая реализация backpressure и load shedding.

    Надежность данных: бэкапы, восстановление, RPO/RTO

    Даже при репликации нужны резервные копии и регулярная проверка восстановления.

  • RPO — сколько данных можно потерять.
  • RTO — сколько времени можно восстанавливаться.
  • Проверка в дизайне: выбранные механизмы репликации, бэкапов и процедуры аварии должны соответствовать RPO/RTO, иначе это просто декорация.

    Надежная доставка событий и фоновой работы

    Если вы используете очереди и события (из прошлых тем), то надежность упирается в две вещи:

  • Не потерять событие между изменением в БД и публикацией.
  • Корректно пережить повторы доставки.
  • Типовые практики:

  • Outbox pattern для публикации событий из транзакции.
  • Идемпотентные обработчики и дедупликация по id операции.
  • Безопасность релизов как часть надежности

    Релиз — частая причина инцидентов, поэтому его тоже проектируют:

  • Canary и постепенный rollout.
  • Blue-green для быстрого отката.
  • Feature flags для отключения рискованных участков без срочного деплоя.
  • Миграции схемы в несколько шагов с обратной совместимостью.
  • Наблюдаемость: как понимать, что происходит в проде

    Наблюдаемость — способность по внешним сигналам понять внутреннее состояние системы и быстро ответить:

  • что сломалось
  • где сломалось
  • почему сломалось
  • как ограничить ущерб и восстановить
  • Три столпа наблюдаемости

  • Метрики: численные ряды во времени (RPS, ошибки, задержки, длина очереди).
  • Логи: события и контекст (кто, что сделал, с какими параметрами, что ответили).
  • Трейсы: путь одного запроса через компоненты с разбиением по спанам.
  • !Как метрики, логи и трейсы дополняют друг друга и связываются correlation_id

    Golden Signals и прикладные дашборды

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

  • latency (задержка)
  • traffic (трафик)
  • errors (ошибки)
  • saturation (насыщение ресурсов)
  • Практика: проектировать дашборды вокруг пользовательского опыта и SLO, а не вокруг "CPU на нодах" как единственной метрики.

    Корреляция: как связать всё в одну картину

    Чтобы расследования занимали минуты, а не часы, обычно закладывают:

  • correlation_id или trace_id на входе (балансировщик, API gateway)
  • проброс идентификатора во все вызовы и события
  • структурированные логи (ключ-значение), чтобы фильтровать и агрегировать
  • минимальный набор стандартных полей (сервис, версия, user_id где допустимо, endpoint, status_code, latency)
  • Алертинг: сигнал, а не шум

    Типовая ошибка: алерты на "причины" (CPU, память) без привязки к пользователю.

    Практика:

  • алерты на симптомы нарушения SLO (ошибки, задержка, недоступность)
  • вторичные алерты на причины (рост очереди, деградация БД) как вспомогательные
  • явные runbook: что проверить и какие первые действия
  • Инциденты и постмортемы

    Устойчивые команды рассматривают инциденты как часть жизненного цикла:

  • Детект: алерт или пользовательский сигнал.
  • Триаж: масштаб, затронутые функции, текущий статус.
  • Митигация: ограничить ущерб (деградация, отключение фичи, откат).
  • Восстановление: вернуть SLO.
  • Постмортем: причины, действия, профилактика.
  • Подход часто называют blameless postmortems и связывают с практиками SRE.

    Безопасность: модель угроз и базовые меры

    Безопасность в System Design отвечает на вопрос: какие угрозы реалистичны для нашей системы и какие меры снижают риск до приемлемого уровня.

    Моделирование угроз: STRIDE

    Упрощённый и популярный подход — STRIDE, который помогает не забыть основные классы угроз:

  • Spoofing: подмена личности
  • Tampering: подмена данных
  • Repudiation: отрицание действий (нет аудита)
  • Information disclosure: утечки данных
  • Denial of service: отказ в обслуживании
  • Elevation of privilege: повышение привилегий
  • Практика: начинать с контекста и границ доверия (внешние клиенты, внутренние сервисы, админка, интеграции), затем проходить STRIDE по ключевым потокам данных.

    !Пример схемы моделирования угроз с границами доверия и типовыми угрозами

    Аутентификация и авторизация

  • Аутентификация: как система понимает "кто ты".
  • Авторизация: что тебе можно делать.
  • Типовые требования, которые фиксируют в дизайне:

  • где проходит проверка (gateway, сервис, оба)
  • модель прав (RBAC, ABAC, ACL)
  • срок жизни токенов, ротация, отзыв
  • разделение пользовательских и сервисных учётных данных
  • Минимизация привилегий и сегментация

    Принцип least privilege в дизайне обычно означает:

  • сервис имеет доступ только к тем данным и операциям, которые ему нужны
  • разные окружения и роли разделены (prod отдельно от dev, админка отдельно от пользовательского контура)
  • сетевые политики и IAM не позволяют "ходить куда угодно"
  • Защита данных: в пути, на диске, в логах

    Минимальный набор, который обычно считается базой:

  • шифрование трафика через TLS между клиентом и периметром
  • шифрование между сервисами, если есть недоверенные сегменты сети
  • шифрование данных "на диске" и контроль ключей
  • запрет на попадание секретов и персональных данных в логи
  • Управление секретами

    Ошибки с секретами часто приводят к критичным инцидентам.

    Практика:

  • хранить секреты в секрет-хранилище, а не в репозитории
  • ротация ключей
  • минимальные права на чтение секретов
  • аудит доступа к секретам
  • Типовые прикладные уязвимости и OWASP

    Чтобы покрыть основные риски веб-приложений, полезно сверяться с OWASP:

  • OWASP Top 10
  • В контексте System Design важно не "выучить список", а ответить дизайном:

  • где валидируем входные данные
  • как защищаемся от инъекций (параметризованные запросы, запрет динамического SQL)
  • как ограничиваем доступ (авторизация на уровне доменных операций)
  • как защищаемся от массового перебора (rate limiting, CAPTCHA где уместно)
  • DoS и злоупотребления как пересечение безопасности и надежности

    Меры часто общие для обоих направлений:

  • rate limiting на периметре
  • квоты и лимиты на пользователя/клиента
  • ограничение размеров запросов
  • таймауты и circuit breaker на внешние зависимости
  • защита очередей от бесконечных ретраев (лимиты, DLQ)
  • Цепочка поставки и безопасность зависимостей

    Даже отличный код уязвим, если компрометирован pipeline или зависимости.

    Минимальные практики:

  • фиксация версий зависимостей и регулярные обновления
  • сканирование зависимостей на известные уязвимости
  • контроль прав CI/CD и секретов в pipeline
  • подпись артефактов и контроль источников
  • Компромиссы и стыки: где надежность влияет на безопасность и наоборот

  • Наблюдаемость vs приватность: больше логов помогает расследовать, но повышает риск утечки. Нужны маскирование и политика ретенции.
  • Кэширование vs контроль доступа: кэшировать можно только если вы уверены, что ключ учитывает контекст прав.
  • Ретраи vs безопасность: ретраи без ограничений могут превратиться в самодельный DoS; нужны лимиты и backoff.
  • Шифрование vs задержка: безопасность добавляет накладные расходы, это нужно учитывать в SLO и производительности.
  • Что фиксировать в артефактах System Design

    Минимальный набор, который делает надежность и безопасность "реальными", а не декларативными:

  • Таблица SLI/SLO по ключевым сценариям.
  • Матрица зависимостей: сервис → зависимость → таймаут → ретраи → circuit breaker.
  • План деградации: что отключаем и как, при каких симптомах.
  • План аварийного восстановления: RPO/RTO, бэкапы, процедура восстановления, ответственные.
  • Набор стандартов наблюдаемости: поля логов, метрики, трассировка, правила алертов.
  • Модель угроз: границы доверия, ключевые потоки, меры по STRIDE.
  • Модель доступа: роли/права, сервисные аккаунты, аудит.
  • Полезные источники

  • Google SRE Book
  • Martin Fowler: Circuit Breaker
  • OWASP Top 10
  • OWASP Application Security Verification Standard (ASVS)
  • NIST SP 800-61 Rev. 2: Computer Security Incident Handling Guide
  • Principles of Chaos Engineering
  • 7. Документация и практика: диаграммы, ADR, дизайн-интервью, кейсы

    Документация и практика: диаграммы, ADR, дизайн-интервью, кейсы

    Зачем эта тема в курсе System Design

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

    Эта статья про инженерную практику System Design:

  • как документировать архитектуру так, чтобы команда реально могла по ней строить и сопровождать систему
  • какие диаграммы использовать и зачем
  • как фиксировать решения в формате ADR
  • как готовиться и проводить дизайн-интервью (и как оценивать ответы)
  • как тренироваться на кейсах, чтобы дизайн стал навыком, а не теорией
  • Главная идея: документ — это инструмент коммуникации

    Документация в System Design не должна быть:

  • “романом” на десятки страниц
  • формальностью ради процесса
  • коллекцией красивых картинок без решений и причин
  • Хороший дизайн-артефакт делает три вещи:

  • объясняет что строим (структура)
  • объясняет почему так (компромиссы и причины)
  • помогает проверить (как убедимся, что оно работает и соответствует требованиям)
  • Минимальный набор артефактов System Design

    Ниже — практичный минимальный набор, который покрывает большинство реальных проектов.

  • Контекст и границы системы: кто пользователи, какие внешние системы, что в scope и out of scope.
  • Требования: функциональные сценарии и измеримые нефункциональные требования.
  • Высокоуровневая архитектура: компоненты и взаимодействия.
  • Данные: владельцы данных, хранилища, консистентность, кэш.
  • Надёжность и безопасность: SLO, деградация, угрозы, ключевые меры.
  • ADR: ключевые решения с причинами и альтернативами.
  • План поставки: MVP и итерации, риски и проверки.
  • Диаграммы: какие бывают и как выбирать

    Диаграмма — это способ быстро синхронизировать понимание. Но диаграммы полезны только тогда, когда отвечают на конкретный вопрос.

    Каркас C4: простой способ не утонуть в деталях

    Популярная практика — модель C4 (Context, Container, Component, Code). Она помогает рисовать архитектуру слоями абстракции и не смешивать “общую картину” с деталями.

  • Context: система и внешние акторы.
  • Container: крупные части системы (приложения, БД, брокеры).
  • Component: логические компоненты внутри контейнера.
  • Источник: C4 model.

    !Сравнение уровней абстракции C4: Context → Container → Component

    Базовый набор диаграмм и когда они нужны

    | Диаграмма | На какой вопрос отвечает | Когда использовать | Типичные ошибки | |---|---|---|---| | Диаграмма контекста | Где границы системы и зависимости? | В начале любого дизайна | Не указаны внешние владельцы и интеграции | | Диаграмма контейнеров | Какие крупные части системы и связи между ними? | Для HLD и обсуждения архитектурного стиля | Смешение контейнеров и компонентов, нет протоколов | | Диаграмма компонентов | Как устроен сервис внутри, где границы ответственности? | Когда нужно прояснить владение и контракты | Рисуют “классы”, а не архитектурные компоненты | | Sequence-диаграмма | Как проходит сценарий шаг за шагом? | Для критических сценариев и отказов | Нет таймаутов/ретраев, нет веток ошибок | | Deployment (развёртывание) | Где что запущено, зоны/регионы, SPOF? | Для надёжности, безопасности, сети | Пропущены зоны, секреты, точки входа | | Data-flow (потоки данных) | Откуда куда текут данные и события? | Для очередей, ETL, аналитики, outbox | Нет гарантий доставки и идемпотентности |

    Практические правила для диаграмм

  • У каждой диаграммы должен быть заголовок “зачем она” и дата/версия.
  • На стрелках фиксируйте протокол и тип взаимодействия: HTTP, gRPC, “событие”, “батч”.
  • Отмечайте границы доверия и внешние системы, если обсуждаете безопасность.
  • Показывайте владение: какой компонент является источником истины для сущности.
  • Не рисуйте всё сразу: лучше 2–4 диаграммы под разные вопросы, чем “карта метро”.
  • ADR: как фиксировать ключевые решения

    ADR (Architecture Decision Record) — короткая запись одного архитектурного решения: что решили и почему. ADR особенно полезен, когда:

  • есть значимые компромиссы (стоимость, надёжность, консистентность)
  • решение повлияет на команды и эксплуатацию
  • вы хотите уменьшить “археологию” через полгода
  • Практика описана у Майкла Найгарда: Documenting Architecture Decisions.

    Мини-шаблон ADR

    ADR и дизайн-док: как не путать

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

    Примеры тем для ADR

  • Выбор стиля интеграции: синхронные вызовы vs события.
  • Выбор хранилища: PostgreSQL vs документная БД.
  • Стратегия консистентности: сильная vs событийная в конкретном сценарии.
  • Выбор брокера: очередь задач vs лог событий.
  • Подход к публикации событий: outbox.
  • Политика кэша: cache-aside и TTL, инвалидация по событиям.
  • RFC и дизайн-ревью: как принимать решения командой

    Зачем нужен процесс ревью

    System Design — это не только “придумать”, но и согласовать: безопасность, эксплуатация, продукт, другие команды. Нужен минимальный процесс, чтобы решения были воспроизводимы.

    Лёгкий процесс RFC

  • Автор публикует короткий документ (problem → proposal → alternatives → risks).
  • Команда оставляет комментарии в фиксированный срок.
  • Автор обновляет документ и фиксирует итог.
  • Ключевые решения выносятся в ADR.
  • Полезно использовать терминологию MUST/SHOULD/MAY из RFC 2119: RFC 2119.

    Чеклист для дизайн-ревью

  • Ясна ли граница системы и список внешних зависимостей?
  • Есть ли трассируемость: требование → решение → как проверяем?
  • Понятны ли владение данными и транзакционные границы?
  • Есть ли план деградации при сбоях зависимостей?
  • Продуманы ли таймауты, ретраи, circuit breaker, rate limiting?
  • Появляются ли новые риски безопасности (границы доверия, секреты, аудит)?
  • Что будет MVP, а что отложено (и почему)?
  • Дизайн-интервью: как тренировать System Design как навык

    Дизайн-интервью проверяет не “знание технологий”, а умение:

  • уточнять требования и ограничения
  • строить понятную структуру системы
  • находить узкие места и риски
  • выбирать компромиссы и защищать их
  • Типовая структура ответа на интервью

  • Уточнение задачи: пользователи, сценарии, in/out of scope.
  • Нефункциональные требования: задержки, доступность, рост, стоимость.
  • Оценка нагрузки: порядок величин, пики, доли чтения/записи.
  • Высокоуровневая архитектура: компоненты, протоколы, границы.
  • Данные: сущности, источники истины, индексы/кэш, согласованность.
  • Масштабирование: stateless, балансировка, очереди, backpressure.
  • Надёжность и безопасность: деградация, наблюдаемость, угрозы.
  • Итоги: риски, trade-offs, план MVP.
  • Как интервьюер обычно оценивает кандидата

    | Что оценивают | Хороший сигнал | Плохой сигнал | |---|---|---| | Уточнение требований | задаёт вопросы и фиксирует допущения | сразу рисует микросервисы без контекста | | Работа с нагрузкой | думает пиками, перцентилями, “горячими” путями | опирается на среднее и игнорирует хвост | | Декомпозиция | компоненты с контрактами и ответственностью | “сервис1/сервис2” без границ данных | | Консистентность | понимает где нужна сильная, где возможна событийная | “везде eventual” или “везде транзакции” | | Надёжность | таймауты, деградация, outbox, идемпотентность | ретраи “везде”, нет DLQ и ограничений | | Наблюдаемость | метрики, логи, трейсы, SLO | “поставим мониторинг потом” |

    Частые ловушки на интервью

  • Пытаться спроектировать всё идеально вместо MVP.
  • Выбирать технологию без причины и без альтернатив.
  • Забывать про эксплуатацию: миграции, алерты, онколл.
  • Игнорировать отказ внешних зависимостей.
  • Не проговаривать допущения и границы применимости решения.
  • Кейсы для практики: как тренироваться системно

    Тренировка на кейсах работает лучше, если вы не просто “рисуете архитектуру”, а каждый раз прогоняете один и тот же скелет рассуждений.

    Универсальный шаблон работы с кейсом

  • Коротко переформулировать задачу и перечислить ключевые сценарии.
  • Выписать НФТ в измеримом виде (хотя бы на уровне порядка величин).
  • Составить диаграмму контекста.
  • Составить диаграмму контейнеров и описать контракты.
  • Выделить сущности и владельцев данных.
  • Отметить “горячие пути” и предложить масштабирование.
  • Придумать 3–5 отказов и сценарии деградации.
  • Зафиксировать 2–4 ADR на ключевые компромиссы.
  • Мини-кейс: сервис коротких ссылок (как тренажёр)

    Что обычно проверяет этот кейс:

  • разделение контуров: быстрый редирект vs аналитика
  • кэширование редиректов и защита от горячих ключей
  • антиабьюз: rate limiting, блок-листы, модерация
  • событийная обработка аналитики (очередь, идемпотентность)
  • Какие ADR логично написать:

  • ADR: где хранить маппинг short -> long и как обеспечивать быстрый read
  • ADR: как собирать аналитику (события, батчи, ретенция)
  • ADR: какая стратегия защиты от злоупотреблений
  • Мини-кейс: оформление заказа в интернет-магазине

    Что обычно проверяет этот кейс:

  • границы доменов: заказы, оплата, склад, доставка
  • согласованность и распределённые процессы (саги)
  • идемпотентность платежей и обработчиков событий
  • наблюдаемость бизнес-процесса “сквозь сервисы”
  • Какие ADR логично написать:

  • ADR: синхронный API vs событийная сага между заказом и оплатой
  • ADR: модель резервирования товара и консистентность остатков
  • ADR: outbox для событий заказа/оплаты
  • Как превратить кейсы в портфолио

    Если вы учитесь или готовитесь к собеседованиям, полезно собирать “папку дизайнов” одинакового формата:

  • 2–3 диаграммы (context, container, sequence на критический сценарий).
  • Таблица требований (функциональные/НФТ/ограничения).
  • 2–5 ADR на ключевые решения.
  • Короткий раздел “риски и проверки”: нагрузочные тесты, SLO, алерты.
  • Что забрать с собой из темы

  • Диаграммы нужны не “для красоты”, а чтобы отвечать на конкретные вопросы.
  • C4 помогает удерживать правильный уровень абстракции.
  • ADR превращает “архитектуру в голове” в историю решений, пригодную для команды.
  • Дизайн-интервью проверяет структуру мышления: требования → архитектура → данные → масштабирование → надёжность → безопасность.
  • Кейсы лучше решать по шаблону и фиксировать решения в артефактах, похожих на реальные рабочие.
  • Полезные источники

  • C4 model
  • Documenting Architecture Decisions
  • RFC 2119
  • Google SRE Book
  • Martin Fowler: Circuit Breaker