Системный дизайн кажется искусством, пока не разложен в процедуру. На деле это метод — последовательность шагов, каждый с конкретным выходом, — и дисциплина следования ему отличает дизайн от рисования квадратиков. Эта статья — сам метод; строительные блоки и сквозной пример — в соседних.
Принципы, которые держат метод
Прежде шагов — четыре принципа, нарушение любого из которых обесценивает остальное:
- Дизайн — производная от ограничений. Нет «правильной архитектуры уведомлений вообще»; есть архитектура под 100 тысяч уведомлений в день и под 100 миллионов — и это разные системы. Пока не названы числа, обсуждать нечего.
- Числа решают споры. «Потянет ли PostgreSQL?» — не вопрос мнений: считается на салфетке за две минуты. Любое «кажется, не потянет» обязано превратиться в оценку.
- Простейшее достаточное (Оккам и KISS). Каждый компонент схемы должен оправдываться требованием, а не привычкой или модой. Дизайн, который нельзя упростить, — а не тот, в который нечего добавить.
- Trade-off называется вслух. Каждое решение что-то покупает и чем-то платит; дизайн без списка «чем платим» — реклама, не инженерия (тот же принцип, что в ADR).
Шаг 1. Функциональные требования
Что система делает: 5–8 главных сценариев словами пользователя. «Отправить уведомление в push и email», «пользователь управляет подписками», «продукт видит статистику доставки». Важно зафиксировать и анти-требования — чего сознательно не делаем (без них объём дизайна определяет самый тревожный участник обсуждения).
Шаг 2. Нефункциональные требования
Скелет всего дизайна — числа и обязательства:
- Нагрузка: RPS на запись и чтение, соотношение, пики к среднему.
- Объёмы: размер записи × количество × retention = диск; рост в год.
- Латентность: целевые p50/p99 на ключевые операции.
- Доступность: что случится при отказе и сколько девяток реально нужно (каждая девятка — кратный рост цены).
- Согласованность: где нужна строгая, где достаточно eventual — по операциям, не «вообще».
Требования, которые не дал бизнес, назначаются явно как допущения — и записываются. Дизайн под невысказанные требования — главный источник переусложнения.
Шаг 3. Оценки на салфетке
Back-of-envelope — грубая арифметика, отсекащая целые классы решений:
100 млн уведомлений/день ≈ 1200/сек в среднем, пик ×5 ≈ 6000/сек
Запись ~1 КБ → 100 ГБ/день → 36 ТБ/год сырых данных
Чтение истории: 10 млн DAU × 5 заходов ≈ 600 RPS чтения
Точность ±50% достаточна: оценка нужна, чтобы выбрать класс решения (одна PG-база / PG + партиционирование / отдельное хранилище под историю), а не конкретный инстанс. Считать стоит на пике, а не на среднем — системы умирают на пиках.
Шаг 4. Контракты
API ключевых операций: эндпоинты или события, формы запросов/ответов, идемпотентность, коды ошибок — на уровне сигнатур, без полной OpenAPI. Контракт раньше схемы компонентов: он проявляет скрытые требования («а как клиент узнаёт статус доставки?» — и в дизайне появляется ещё одна операция).
Шаг 5. Модель данных
Главные сущности, их ключи, объёмы и паттерны доступа — потому что выбор хранилища делается по ним (PG vs Mongo, PG vs ClickHouse): что читается по ключу, что фильтруется, что агрегируется, что append-only. Здесь же — владение данными: какая часть системы является источником правды для какой сущности.
Шаг 6. Схема: компоненты и поток
Только теперь — квадратики: компоненты, их обязанности, синхронные и асинхронные связи (sync vs async — на каждую стрелку). Хорошая проверка схемы — прогнать через неё каждый сценарий из шага 1 пальцем: «уведомление пришло сюда, дальше очередь, дальше...». Сценарий, который не проходит по стрелкам, — дыра дизайна.
Шаг 7. Горячие точки
В каждой системе 1–2 места концентрируют сложность: самая нагруженная таблица, компонент с состоянием, точка слияния потоков. Метод требует их назвать и углубиться: ключи шардирования, схема горячей таблицы, поведение очереди при отставании потребителей. Остальное дизайнится крупными мазками — углубляться везде одинаково означает не углубиться нигде.
Шаг 8. Отказы и деградация
По каждому компоненту: что видит пользователь, когда этот компонент лежит? Какие операции деградируют, какие умирают, что копится и где? Здесь дизайн встречается с resilience-паттернами и идемпотентностью: ретраи означают повторы, повторы требуют идемпотентности — это свойство дизайна, не «добавим потом».
Шаг 9. Эволюция
Не «дизайн на 10 лет», а проверка обратного: какое из решений будет больно менять? Их стоит защитить интерфейсом (Protected Variations); остальное переживёт рефакторинг. Плюс честный план первой версии: что строим сейчас, что осознанно откладываем (YAGNI).
Метод одной таблицей
| Шаг | Выход | Типичный провал |
|---|---|---|
| 1. Функциональные | Сценарии + анти-требования | Дизайн «вообще», без границ |
| 2. Нефункциональные | Числа и обязательства | «Высоконагруженная» без единого числа |
| 3. Оценки | Класс решения по арифметике | Спор мнений там, где решает салфетка |
| 4. Контракты | Сигнатуры ключевых операций | API «потом» — и скрытые операции всплывают в коде |
| 5. Модель данных | Сущности, паттерны доступа, владение | Хранилище по привычке, не по паттернам |
| 6. Схема | Компоненты и поток, sync/async на стрелках | Квадратики, через которые не проходит сценарий |
| 7. Горячие точки | Глубокий разбор 1–2 мест | Равномерная поверхностность |
| 8. Отказы | Поведение при падении каждого компонента | «Сделаем надёжно» без сценариев отказа |
| 9. Эволюция | Что защищаем интерфейсом, что откладываем | Архитектура на вырост — везде |
Что почитать дальше
- Строительные блоки и их цена — словарь, из которого собирается шаг 6.
- Сквозной пример: система уведомлений — весь метод на одной задаче.
- Оформление и защита дизайна — как превратить результат в документ и решение.
- Архитектурный выбор — развилки, в которые упираются шаги 5 и 6.