system-design
Проектирование распределённых систем — декомпозиция монолита на модули/сервисы (критерии границ), синхронное vs асинхронное взаимодействие (очереди, события), уровни масштабирования 100 → 10k RPS (кэш
SKILL.mdskills/system-design/SKILL.md
GitHubНавык: System Design
Декомпозиция: монолит → модули → сервисы
Дефолт — модульный монолит (структура — скилл backend-architecture): модули с жёсткими границами внутри одного деплоя. Отдельный сервис выделяй, только когда у куска есть своя причина жить отдельно.
Критерии границы (модуля или сервиса):
- Своя предметная область (bounded context): свой словарь, свои инварианты. «Биллинг» и «Уведомления» — разные контексты; «UserUtils» — не граница.
- Свои данные: модуль владеет своими таблицами; чужие данные — только через его публичный API, не JOIN'ом в его схему.
- Своя причина меняться: релизится/масштабируется независимо (рассылки идут пачками ночью, API — днём).
- Свой профиль нагрузки или команда: CPU-тяжёлый рендер PDF рядом с латентно-чувствительным API — кандидат на вынос.
Пример: интернет-магазин → модули catalog (товары, поиск), orders (корзина, инварианты заказа), billing (платежи, вебхуки провайдера), notifications (email/push, ретраи). Первый кандидат в отдельный сервис — notifications: асинхронный, независимый ритм, безопасен при отставании. Плохая декомпозиция — по техслоям («сервис контроллеров», «сервис БД») или по CRUD-сущностям без инвариантов.
Синхронно vs асинхронно
| Признак | Синхронно (HTTP/gRPC) | Асинхронно (очередь/событие) |
|---|---|---|
| Ответ нужен прямо в этом запросе | ✅ | — |
| Результат можно отдать позже (email, отчёт, вебхук) | — | ✅ |
| Вызываемый может лежать, а мы должны жить | — | ✅ (очередь буферизует) |
| Пики нагрузки надо сглаживать | — | ✅ |
| Несколько независимых потребителей одного факта | — | ✅ (событие, pub/sub) |
| «Проверь и сразу реши» в одной транзакции запроса | ✅ | — |
- Команда («сделай X», один получатель, важен результат) → очередь задач с подтверждением и ретраями (Redis Streams / RabbitMQ / SQS).
- Событие («X случилось», реагирует кто подписан) → pub/sub; producer не знает потребителей.
- Асинхронность тянет обязательства: идемпотентные потребители (at-least-once = дубли будут), DLQ для «ядовитых» сообщений, eventual consistency в UX («заказ обрабатывается»).
Масштабирование: 100 → 10k RPS
- ~100 RPS — один сервер приложения + одна БД. Хватает индексов и вменяемых запросов. Не усложняй.
- ~1k RPS — сначала кэш: Redis cache-aside для горячих чтений, CDN для статики — снимает большинство чтений с БД. Затем горизонтально масштабируй stateless-приложение за балансировщиком (состояние сессий — в Redis/JWT, не в памяти процесса).
- ~5k RPS — узкое место обычно БД → read-реплики (чтения на реплики, записи в primary; учитывай лаг репликации), пул соединений (pgbouncer), тяжёлые операции — в фоновые очереди.
- ~10k+ RPS — шардинг (партиционирование записей по ключу: user_id, tenant). Последний ход, дорог в поддержке (cross-shard-запросы, решардинг); прежде чем шардить, проверь, что не хватило кэша, реплик и выноса горячих таблиц.
Каждый уровень включай, только когда упёрся измеримо (метрики), а не «на вырост».
Отказоустойчивость (минимум)
Таймаут на каждый сетевой вызов → ретраи с экспонентой и jitter только для идемпотентных операций → circuit breaker к падающей зависимости → graceful degradation (рекомендации отвалились — каталог работает). Идемпотентность записи — ключи идемпотентности (скилл api-design).
Наблюдаемость
- Логи — структурные (JSON), с
traceId/requestId, сквозным через все сервисы. - Метрики — RED на каждый эндпоинт и зависимость: Rate, Errors, Duration (p50/p95/p99, не среднее).
- Трейсинг — OpenTelemetry: один trace через сервисы и очереди; без него распределённую задержку не найти.
Что рисовать на архитектурной диаграмме
(Строить — скиллы mermaid-diagrams / drawio-diagrams.)
- Уровень C4-container: по блоку на деплоймент-единицу — сервисы, БД, кэш, очереди, внешние API. Не по классу и не по функции.
- Стрелки = вызовы, с протоколом и направлением инициации:
API → Postgres (SQL),API → Billing (HTTP, sync),Orders → queue → Notifications (async). Sync и async визуально различай (сплошная/пунктир). - Границы доверия/сети (VPC, публичное/приватное) — рамками; точки входа (LB, gateway) — явно.
- Не тащи всё в одну диаграмму: контекст (пользователи и внешние системы) — отдельно, контейнеры — отдельно, критичные потоки — sequence-диаграммой.