← назад к разделу

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

Принципы, которые держат метод

Прежде шагов — четыре принципа, нарушение любого из которых обесценивает остальное:

  1. Дизайн — производная от ограничений. Нет «правильной архитектуры уведомлений вообще»; есть архитектура под 100 тысяч уведомлений в день и под 100 миллионов — и это разные системы. Пока не названы числа, обсуждать нечего.
  2. Числа решают споры. «Потянет ли PostgreSQL?» — не вопрос мнений: считается на салфетке за две минуты. Любое «кажется, не потянет» обязано превратиться в оценку.
  3. Простейшее достаточное (Оккам и KISS). Каждый компонент схемы должен оправдываться требованием, а не привычкой или модой. Дизайн, который нельзя упростить, — а не тот, в который нечего добавить.
  4. 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.