Системный дизайн кажется искусством, пока не разложен в процедуру. На деле это метод — последовательность шагов, у каждого из которых есть конкретный результат. Эта статья — сам метод; строительные блоки и сквозной пример — в соседних.
Почему нельзя сразу рисовать квадратики
Типичная ошибка: архитектор слышит задачу и сразу начинает рисовать компоненты. Через десять минут на доске красивая схема — и никто не знает, выдержит ли она 10 000 запросов в секунду или 100, зачем там три очереди и что произойдёт, если упадёт база.
Проблема в том, что схема нарисована до того, как заданы вопросы. Метод ниже задаёт их в правильном порядке.
Шаги идут слева направо, но оценки возвращают к требованиям, а разбор отказов правит схему: порядок есть, линейности нет.
Шаг 1. Функциональные требования
Что система делает: 5–8 главных сценариев словами пользователя. «Отправить уведомление в push и email», «пользователь управляет подписками», «продукт видит статистику доставки».
Важно зафиксировать и анти-требования — чего сознательно не делаем. Без них объём дизайна определяет самый тревожный участник обсуждения: «а что если пользователей будет миллиард?», «а если хотят работать в оффлайне?» — и система разрастается бесконечно.
Результат шага: список сценариев + список того, что выходит за рамки.
Шаг 2. Нефункциональные требования
Это числа и обязательства, из которых вырастает весь дальнейший дизайн:
- Нагрузка: сколько запросов в секунду на запись и чтение, каков пик относительно среднего.
- Объёмы: размер одной записи × количество записей × срок хранения = место на диске; как растёт за год.
- Скорость ответа: целевые показатели на ключевые операции.
- Доступность: что случится при отказе и сколько «девяток» реально нужно. Девятка — это разрешённый простой за год: 99 % — почти четверо суток, 99,9 % — 8,8 часа, 99,99 % — 53 минуты, 99,999 % — 5 минут. Каждая следующая девятка обходится примерно вдесятеро дороже предыдущей — это расхожее эмпирическое правило, а не измеренная величина, но порядок оно передаёт верно: разница между 99,9 % и 99,99 % — это не «чуть аккуратнее», а другой бюджет и другая архитектура.
- Согласованность данных: где нужна строгая («прочитал то, что только что записал»), где достаточно получить обновлённые данные чуть позже — по каждой операции, не «вообще».
Требования, которые бизнес не назвал, назначаются явно как допущения — и записываются. Проектирование под невысказанные требования — главный источник избыточной сложности.
Шаг 3. Оценки на салфетке
Оценка на салфетке — грубая арифметика, которая отсекает целые классы решений ещё до того, как нарисован первый квадратик:
| Что оцениваем | Из чего считаем | Сколько выходит |
|---|---|---|
| поток на запись | 100 млн уведомлений в день | ≈1200/сек в среднем, на пике ×5 — ≈6000/сек |
| объём данных | запись ~1 КБ × 100 млн в день | ≈100 ГБ в день, ≈36 ТБ сырых данных за год |
| поток на чтение | 10 млн активных пользователей × 5 заходов | ≈600 запросов в секунду на чтение истории |
Точность ±50% достаточна: оценка нужна, чтобы выбрать класс решения (одна PostgreSQL-база / PostgreSQL с партиционированием / отдельное хранилище под историю), а не конкретный сервер. Считать стоит на пике, а не на среднем — системы падают именно на пиках.
Пятёрка в строке «на пике ×5» — не универсальная константа, а допущение про форму суточного графика. Отношение пика к среднему берут из реальных графиков своего сервиса: у рабочего инструмента, которым пользуются с девяти до шести, выходит около трёх, у вечернего развлекательного — пять и больше, а у сервиса с распродажами раз в квартал пик меряют не сутками, а тем самым днём распродажи. Пока графиков нет, множитель записывают как допущение из шага 2 и перепроверяют, как только появятся данные.
Шаг 4. Контракты
API ключевых операций: адреса или события, структуры запросов и ответов, идемпотентность, коды ошибок — на уровне сигнатур, без полной спецификации.
Перед формой контракта — выбор стиля интеграции. Классически их четыре (из Enterprise Integration Patterns): общий файл, общая база данных, синхронный API и обмен сообщениями. Общая база связывает схемы и релизы двух систем — обычно антипаттерн; API удобен для запроса-ответа; сообщения развязывают стороны по времени и нагрузке. Стиль выбирают до сигнатур — он определяет, синхронный это вызов или событие.
Зачем контракт раньше схемы компонентов: он обнаруживает скрытые требования. «А как клиент узнаёт статус доставки?» — этот вопрос, заданный при проектировании контракта, добавляет в дизайн новую операцию. Если его не задать сейчас, она всплывёт в коде — переделывать дороже.
Для синхронного API «на уровне сигнатур» — это конкретный чек-лист, а не два примера ради галочки:
- Ресурсы и методы. Существительные вместо глаголов, стандартная семантика
GET/POST/PUT/PATCH/DELETE. Операции, не ложащиеся на CRUD (отмена заказа, пересчёт корзины), — отдельные под-ресурсы или действия. Подробно — URL и ресурсы (Java, Go, Node, Python) и Алиасы и действия (Java, Go, Node, Python). - Коды ответов и формат ошибок. Какие статусы возвращает каждая операция и как выглядит тело ошибки — одинаково для всего API (Ошибки (Java, Go, Node, Python)).
- Идемпотентность мутаций. Что случится при повторе
POST /ordersпосле сетевого таймаута? Ключ идемпотентности — обязательная часть контракта оплат и заказов, а не деталь реализации. - Пагинация и фильтры. Offset или курсор — выбор зависит от объёма и паттерна чтения (Параметры запроса (Java, Go, Node, Python)).
- Версионирование. Как контракт будет меняться, не ломая существующих клиентов (Версионирование (Java, Go, Node, Python)).
- Долгие операции. Что нельзя сделать за один запрос —
202 Acceptedплюс ресурс-статус или событие (Batch, асинхронность (Java, Go, Node, Python)).
Мини-контракт для заказа маркетплейса — достаточный уровень детализации на дизайн-сессии:
POST /orders Idempotency-Key: <uuid> -> 201 { id, status: "CREATED" }
GET /orders/{id} -> 200 { id, status, items[], total }
GET /orders?cursor=&limit= -> 200 { items[], nextCursor }
POST /orders/{id}/cancellation -> 202 { status: "CANCELLING" }
Результат шага — не устная договорённость, а черновик машиночитаемой спецификации: для синхронного API это OpenAPI, для обмена сообщениями — схема события. Черновик потом дорастает до полной спецификации, и он же попадает в документ с дизайном как контракт. Целиком дисциплина проектирования REST-контрактов разобрана в разделе REST API — от структуры URL до OpenAPI.
Шаг 5. Модель данных
Главные сущности, их ключи, объёмы и паттерны доступа. Паттерны доступа — это ответ на вопросы: что читается по ключу, что фильтруется, что агрегируется, что только дописывается. По этим ответам выбирается хранилище, а не по привычке.
Как это работает на практике: паттерн доступа записывают фразой, и фраза выбирает хранилище. Несколько примеров, которые покрывают большинство случаев:
| Паттерн доступа, записанный фразой | Что из него следует |
|---|---|
| «Читаем по первичному ключу, пишем по одной записи, объём небольшой» | Обычная реляционная база, и обсуждать нечего |
| «Читаем по ключу, но записей миллиарды и нужен предсказуемый отклик» | Хранилище по ключу с разделением по узлам; в реляционной придётся разделять руками |
| «Пишем потоком, читаем за период, никогда не обновляем» | Дописываемая таблица с секциями по времени; кандидат на аналитическое хранилище |
| «Агрегируем за месяц по разным разрезам» | Колоночное аналитическое хранилище, а не та же база, что обслуживает запросы пользователей |
| «Ищем по тексту с морфологией и опечатками» | Поисковый движок; реляционный полнотекст — только для простых случаев |
| «Читаем один документ целиком со всеми вложениями» | Документное хранилище или колонка с документом в реляционной базе |
| «Фильтруем по любому из двадцати полей в произвольных сочетаниях» | Реляционная база с индексами или поисковый движок, но не хранилище по ключу |
| «Считаем счётчики с огромной частотой» | Хранилище в памяти, а не строка в базе, которую все обновляют |
Обратите внимание, чего в левой колонке нет: названий технологий и слов «быстро», «много», «масштабируемо». Есть операция, ключ доступа, объём и частота — и этого достаточно, чтобы класс хранилища определился почти сам. Отсюда практический приём для дизайн-сессии: на каждую главную сущность пишут одну такую фразу, и спор «Postgres или Mongo» заканчивается до того, как начался, потому что становится виден вопрос, на который выбор отвечает.
Две ловушки, которые тут же появляются. Первая: у одной сущности два разных паттерна доступа — заказ читают по ключу в оформлении и агрегируют за месяц в отчётах. Это не повод искать хранилище, которое умеет и то и другое: это повод развести пути (отчёты считают на копии или в аналитическом хранилище), потому что один индекс не бывает хорош для обоих. Вторая: паттерн доступа, который вы придумали, а не взяли из сценариев шага 1. Проверка простая — каждая фраза должна выводиться из названного сценария; не выводится, значит вы проектируете под требование, которого нет.
Здесь же — владение данными: какая часть системы является источником правды для каждой сущности.
Шаг 6. Схема: компоненты и поток
Только теперь — квадратики. Компоненты, их обязанности, синхронные и асинхронные связи. На каждую стрелку стоит ответить: это синхронный вызов или асинхронное событие? Выбор влияет на поведение при отказах и задержку.
Хорошая проверка готовой схемы — прогнать по ней каждый сценарий из шага 1 пальцем: «запрос пришёл сюда, дальше очередь, дальше...». Сценарий, который не проходит по стрелкам, — дыра в дизайне.
Шаг 7. Горячие точки
В каждой системе есть 1–2 места, где концентрируется сложность: самая нагруженная таблица, компонент с состоянием, точка слияния потоков. Метод требует их назвать и углубиться: ключи шардирования, схема горячей таблицы, поведение очереди при отставании потребителей.
Как их находят — по признакам, а не по интуиции. Пять вопросов, каждый из которых указывает на горячую точку:
- Где концентрируется запись? Одна строка, которую обновляют все (счётчик, баланс, статус общего объекта), одна секция таблицы (все сегодняшние записи), один раздел очереди. Признак: запись идёт в одно место независимо от числа клиентов. Это самая частая горячая точка, потому что читать можно с копий, а писать — только в одно.
- Где живёт состояние? Компонент, который нельзя просто размножить: планировщик, координатор, кеш с данными в памяти, соединение, привязанное к конкретной копии. Признак: «а если этих сделать два — что будет?» — и ответ «сломается» или «выполнится дважды».
- Где потоки сливаются? Место, куда приходят данные из нескольких источников и должны сойтись: сверка, объединение по ключу, ожидание ответов от трёх систем. Признак: компонент, который ждёт несколько событий, а не одно.
- Что не масштабируется добавлением копий? Внешний сервис с пределом по числу запросов, лицензия, единственный узел записи, блокировка в базе. Признак из предыдущей статьи: копий больше, а суммарная пропускная способность не растёт.
- Что растёт быстрее остального? Таблица, которая за год станет в десять раз больше остальных; очередь, которая копит при малейшем отставании; журнал, который никто не чистит. Признак: рост не линейный по числу пользователей.
По найденной точке углубляются до конкретики: ключ разделения (по чему разложим и не станет ли один кусок горячим), схема самой нагруженной таблицы с индексами и объёмом, поведение очереди при отставании (сколько копит, когда начинает отбрасывать, что видит пользователь), что происходит при отказе именно этого места.
Простое правило соразмерности: горячих точек в дизайне одна-две, а не восемь. Если их получилось больше, скорее всего вы назвали горячими точками просто сложные места; вернитесь к вопросу «где концентрируется запись и где живёт состояние» — он обычно оставляет ровно те, что нужно.
Остальное дизайнится крупными мазками — углубляться везде одинаково означает не углубиться нигде.
Шаг 8. Поведение при отказах
По каждому компоненту: что видит пользователь, когда этот компонент недоступен? Какие операции продолжают работать в упрощённом режиме, какие останавливаются, что накапливается и где?
Здесь важна связь с идемпотентностью: если система умеет повторять запросы при сбое, повторные запросы не должны создавать дубликаты. Это свойство дизайна — его не добавить «потом».
Полезно мыслить доменами отказов (failure domains): что именно отказывает — процесс, машина, зона доступности или целый датацентр. У каждого уровня свой радиус поражения. Отсюда практическое правило: реплики и экземпляры разносят по разным зонам, чтобы отказ одной не унёс сервис целиком.
Шаг 8а. Доступ и данные пользователей
Шага про безопасность в методе нет, а в настоящем ревью дизайна это первый вопрос — и задают его люди, которые могут решение не пропустить. Полный разбор темы большой, но в дизайн-сессию она укладывается в четыре вопроса, и на них отвечают там же, у доски.
Кто вызывает и чем подтверждает. На каждую входящую стрелку схемы: это пользователь через публичную границу, соседний сервис внутри контура или задача по расписанию? Пользователь — значит, проверка на границе и дальше внутрь едет уже проверенный контекст. Сервис — значит, у него своя учётная запись, а не «мы внутри, тут все свои». Ответ «не решили» на этот вопрос означает, что дизайн не готов.
Кто имеет право на этот объект. Проверка «пользователь вошёл» — не то же, что «этот пользователь может смотреть этот заказ». Второе — свойство модели данных: у сущности есть владелец, и правило доступа формулируется через него. На дизайне это одна строка вида «заказ виден его покупателю и сотрудникам поддержки», и её отсутствие — самая частая дыра, которую находят уже в проде.
Какие персональные данные проходят через систему и где остаются. Перечисляют прямо на схеме: что в базе, что в журналах, что в очереди, что уезжает во внешний сервис. Дальше два решения: что не хранить вовсе (самое дешёвое решение из всех) и что обезличить. Сюда же — сроки хранения, потому что «храним всё вечно» и требования закона несовместимы; и отдельно отмечают, что уходит за пределы контура — внешние провайдеры, аналитика, поставщик модели.
Что должно остаться в журнале действий. Не отладочный журнал, а запись «кто, когда, что сделал» по операциям, за которые придётся отвечать: изменение прав, доступ к чужим данным, действия сотрудников поддержки, денежные операции. Это решение уровня дизайна, потому что задним числом такой журнал не восстановить.
Плюс три вещи, которые на дизайне просто отмечают галочкой, но отмечают всегда: секреты не в коде и не в конфигурации в репозитории; шифрование данных при передаче и решение про шифрование при хранении; границы доверия — где на схеме проходит линия между публичной и внутренней частью, и что её пересекает.
Практически это добавляет к дизайну один слайд и пять строк текста, а экономит недели: требования безопасности, всплывшие после реализации, переделывают модель данных, а не добавляют проверку.
Шаг 8б. Сколько это будет стоить
Оценки на салфетке дали 45 копий и 36 терабайт в год. Это деньги, и решение принимают с ними, а не без них — иначе разговор «дорого ли» переносится на счёт от поставщика.
Считать на дизайне надо грубо и по четырём строкам:
- Вычисления. Число копий × цена копии в месяц. Порядок цены обычной копии (2 ядра, 4 гигабайта) — десятки долларов в месяц; свои серверы считают по амортизации, но порядок тот же.
- Хранение. Объём × цена гигабайта в месяц, отдельно для быстрого хранилища и для архива — между ними разница в разы, и именно это делает ярусное хранение выгодным. Тридцать шесть терабайт на быстрых дисках и те же тридцать шесть в объектном хранилище — совсем разные строки счёта.
- Трафик. Обычно платят за исходящий и за пересечение зон. Вот это место, которое забывают чаще всего: обмен между копиями в разных зонах и выдача файлов пользователям иногда стоит больше, чем сами вычисления.
- Управляемые службы. Брокер, кеш, поисковый движок как услуга у поставщика: считают по их же прайсу, и часто выясняется, что услуга дороже трёх своих копий, а иногда — что дешевле одного инженера, который будет её обслуживать.
Два вывода, которые эта арифметика даёт почти всегда. Первый: хранение обычно дороже вычислений на системах, которые копят данные, и поэтому решение «сколько хранить» — самое дорогое решение в дизайне, а не выбор фреймворка. Второй: цена делится на число пользователей или операций — и получается стоимость одной операции, которую можно сравнить с тем, сколько она приносит. Разговор «наша система обходится в столько-то за тысячу уведомлений» — единственная форма, в которой дизайн обсуждается с бизнесом.
И то, что стоит написать на том же слайде: чем можно заплатить меньше, если понадобится. Сократить срок хранения, унести историю в архив, уменьшить число копий за счёт запаса, отказаться от управляемой службы. Это список, к которому вернутся через год, когда счёт вырастет, — и он должен появиться на дизайне, пока помнят, откуда взялись числа.
Шаг 9. Эволюция
Не «дизайн на 10 лет», а честная проверка: какое из принятых решений будет больно менять? Такие места стоит защитить интерфейсом; остальное переживёт рефакторинг. Плюс — явный план первой версии: что строим сейчас, что сознательно откладываем.
И вторая половина этого шага, без которой дизайн остаётся бумажным: как система попадёт из текущего состояния в спроектированное. Большинство дизайнов в работе — это изменение живой системы, а не чистый лист, и путь перехода проектируют так же, как саму систему.
Минимум, который отвечают на дизайне:
- Первый срез, который можно выкатить. Не «сделаем всё и включим», а что попадёт в прод первым и будет работать рядом со старым путём. Признак хорошего первого среза: он приносит пользу сам по себе, даже если продолжения не будет.
- Что делаем со старым путём. Выключаем сразу, держим оба и переключаем по флагу, или переводим по частям (по типу операции, по группе пользователей)? И кто останется на старом пути дольше всех — обычно это внешние потребители, про которых вспоминают последними.
- Что делаем с данными, которые уже есть. Перелив, двойная запись на время перехода, или новая схема живёт только для новых записей. Здесь же — как проверяем, что перелитое совпадает: сверка на выборке или полная.
- Как откатываемся. Не «если не получится, вернём» — а конкретно: до какого момента откат возможен без потери данных и что делают после этого момента (только вперёд).
Механику этих приёмов — душитель, двойная запись, совместимость схемы и границы обратимости — разбирает раздел «Глубже» ниже; здесь важно, что они входят в дизайн, а не изобретаются в момент выката. Дизайн, у которого есть отличная целевая схема и нет пути перехода, на практике не реализуется: работа останавливается на первом же вопросе «а как мы это включим, не остановив приём заказов».
Когда метод не нужен и сколько времени он занимает
Девять шагов на добавление поля в существующий сервис — это карго-культ, и он вредит так же, как отсутствие метода: команда один раз проходит всю церемонию ради мелочи, убеждается, что это долго и бесполезно, и не применяет метод там, где он был бы нужен. Поэтому границу стоит проводить явно.
Метод не нужен, когда изменение обратимо и умещается в голове одного человека: новое поле, новый эндпоинт в существующем сервисе по образцу соседних, правка бизнес-правила, оптимизация запроса. Здесь достаточно обычного пул-реквеста, и единственное, что стоит взять из метода, — вопрос «какие числа изменятся» (например, вырастет ли объём таблицы).
Метод нужен целиком, когда выполняется хотя бы одно: появляется новый компонент (сервис, хранилище, брокер) или новая зависимость; меняется модель данных так, что миграция необратима; решение дорого отменить (выбрали хранилище, разделили данные, вынесли функциональность в отдельный сервис); задача затрагивает больше одной команды; или в числах нагрузки появляется порядок, которого раньше не было.
Метод в сокращённом виде — самый частый режим на практике: берут четыре шага из девяти. Требования и анти-требования (десять минут), оценки на салфетке (пять минут), схема с прогоном сценариев (пятнадцать минут), поведение при отказах (десять минут). Сорок минут, одна страница текста — и это уже несравнимо лучше, чем сразу квадратики. Остальные шаги подключают, когда они что-то решают: контракты — если участвуют другие команды, модель данных — если появляется хранилище, эволюция — если система живая.
Сколько времени занимает полная сессия. Ориентир для дизайн-встречи на 90 минут, если требования собраны заранее (а собирают их до встречи, иначе на них уйдёт всё время):
| Этап | Время |
|---|---|
| Сверка требований и анти-требований | 10 минут |
| Оценки на салфетке вслух | 10 минут |
| Контракты ключевых операций | 10 минут |
| Модель данных и паттерны доступа | 10 минут |
| Схема и прогон сценариев пальцем | 25 минут |
| Горячие точки | 10 минут |
| Отказы, доступ и данные | 10 минут |
| Что решили, что открыто, кто владелец | 5 минут |
Что делает такую встречу продуктивной: требования и оценки разосланы заранее (иначе половина времени уходит на «а сколько у нас запросов»), у встречи есть один ведущий, который держит порядок шагов, и открытые вопросы фиксируются как открытые, а не обсуждаются до победы. Три шага, которые почти всегда не влезают, — детальная модель данных, безопасность в подробностях и эволюция; их доделывают письменно после, и это нормально: встреча нужна для развилок, а не для оформления.
И честное наблюдение: метод — это порядок вопросов, а не обязательный ритуал. Его ценность в том, что схема появляется после чисел и обязательств. Если в конкретной задаче достаточно двух вопросов, чтобы это условие соблюсти, значит, в этой задаче метод — два вопроса.
Принципы, которые удерживают метод
Четыре принципа, нарушение любого из которых обесценивает остальное:
- Дизайн — производная от ограничений. Нет «правильной архитектуры уведомлений вообще»; есть архитектура под 100 тысяч уведомлений в день и под 100 миллионов — и это разные системы.
- Числа решают споры. «Потянет ли PostgreSQL?» — не вопрос мнений: считается на салфетке за две минуты. Любое «кажется, не потянет» обязано превратиться в оценку.
- Простейшее достаточное. Каждый компонент в схеме должен оправдываться требованием, а не привычкой или модой.
- Компромисс называется вслух. Каждое решение что-то получает и чем-то платит; дизайн без списка «чем платим» — реклама, не инженерия.
Глубже: опорные числа для салфеткирасширенное
«Оценки на салфетке» и «числа решают споры» требуют чисел, с которыми сравнивают. Вот набор, который держат в голове; точность в разы здесь достаточна, потому что решения меняются на порядках.
Задержки, по возрастанию. Кэш процессора единицы наносекунд, оперативная память около 100 наносекунд, чтение с SSD 100 микросекунд, круговой путь по сети внутри зоны половина миллисекунды, между зонами одного региона одна-две миллисекунды, между регионами и континентами 50–150 миллисекунд. Из этого: обращение к Redis это сотни микросекунд, к базе в той же зоне единицы миллисекунд на простой запрос, к сервису в другом регионе десятки, и цепочка из пяти сетевых вызовов не бывает быстрее нескольких миллисекунд.
Пропускная способность. Один экземпляр обычного сервиса, на каком бы языке он ни был написан, держит от сотен до нескольких тысяч запросов в секунду, в зависимости от того, что делает запрос; узел PostgreSQL на хорошем диске делает 5–20 тысяч простых транзакций в секунду и десятки тысяч чтений; экземпляр Redis около ста тысяч операций в секунду; партиция Kafka порядка десяти мегабайт в секунду; SSD отдаёт 500 мегабайт в секунду и больше, сеть в 10 гигабит около гигабайта в секунду.
Объёмы. В сутках примерно сто тысяч секунд, поэтому миллион событий в день это десять в секунду в среднем и 30–50 в пик; пик обычно в два-пять раз выше среднего. Строка заказа с десятком полей это около килобайта, миллион строк это гигабайт, строка лога 300 байт, идентификатор UUID 16 байт, картинка с телефона несколько мегабайт. Терабайт в год это три гигабайта в день, и это мало для хранилища и много для полнотекстового индекса.
Как пользоваться. Считают в столбик и округляют вверх: 10 миллионов пользователей, каждый десятый заходит в день, у каждого 20 запросов, итого 20 миллионов в день, 200 в секунду в среднем, тысяча в пик; один экземпляр держит 500, значит четыре экземпляра плюс запас, о котором раздел про ёмкость в статье про строительные блоки. Чисел, которые расходятся с этими на порядок, не бывает без причины, и причина это то, что стоит найти до схемы.
Глубже: цена решения в деньгахрасширенное
Каждое решение метода принимается по нагрузке и надёжности, и ни одно по счёту в конце месяца, а «простейшее достаточное» без цены остаётся лозунгом. Считают на тех же салфетках, по ценам облаков из статьи про деньги в облаке, с точностью до раза.
Машина на два ядра стоит 60–80 долларов в месяц, значит 45 экземпляров из расчёта ёмкости это около 3 тысяч, и первый вопрос: нельзя ли сделать их вдвое меньше, ускорив обработчик. Управляемая база с репликой в другой зоне это 150–200 плюс диск; 36 терабайт данных в год в объектном хранилище это около 800 в месяц к концу года, а те же терабайты в горячем полнотекстовом индексе это узлы с памятью на тысячи в месяц. Отдельный ClickHouse под аналитику это три узла и от полутора тысяч; отдельный Elasticsearch столько же; Kubernetes добавляет управляющий слой и узлы. Сетевой трафик наружу по 5–9 центов за гигабайт превращает раздачу картинок в сотни долларов, а NAT и балансировщики стоят часами независимо от нагрузки.
Что дороже железа: люди. Каждый компонент в схеме это эксплуатация, дежурство и обновления, и второй ClickHouse рядом с PostgreSQL стоит четверть ставки инженера постоянно, о чём статья про цену второй базы. Поэтому сравнение вариантов в design doc выглядит как таблица: вариант, железо в месяц, компоненты в эксплуатации, чем платим при росте вдесятеро. «Всё в PostgreSQL на одном узле с репликой» выигрывает у «PostgreSQL плюс ClickHouse плюс Elasticsearch» до тех пор, пока не упрётся в числа из раздела выше, и граница видна заранее.
Глубже: ввод в работающую систему: душитель, двойная запись, совместимость и откатрасширенное
Девять шагов метода описывают систему с чистого листа, а большинство проектов это изменение работающей: новая система уведомлений рядом со старой, разделение базы, замена компонента. К методу добавляется десятый шаг, план ввода, и у него свои приёмы.
Душитель. Новая система встаёт рядом со старой, а маршрутизация на входе переводит на неё сценарии по одному: сначала чтение одного отчёта, потом создание одного типа объекта, пока старая не опустеет. Каждый переведённый сценарий можно вернуть назад переключением. Что это означает для данных, разбирает статья про структурные паттерны микросервисов.
Двойная запись и перелив. Пока сценарии переезжают, данные живут в двух местах: старое хранилище остаётся источником правды, новое наполняется переливом истории со снимка и догоном изменений через события или журнал изменений, и сверкой по контрольным суммам до тех пор, пока расхождений нет неделю. Перенос записи делают последним, и до него откат это выключение флага.
Совместимость контракта. Старые клиенты продолжают звать старый API, и новая система обязана отвечать на него так же; поля не переименовывают, а добавляют, о чём статьи про версионирование и про N-1 для схемы базы. Теневой трафик, когда новая система получает копию запросов и её ответы сравнивают со старыми, не отдавая пользователю, находит расхождения до переключения.
Откат как часть плана. У каждого шага ввода записано, как его отменить и за сколько: флаг за секунды, маршрутизация за минуты, перенос записи за часы с обратным потоком. Шаг без отката это шаг, который делают последним и с ручным подтверждением. В design doc это отдельный раздел «план ввода» с этапами, критериями перехода между ними и откатом каждого, и ревьюер читает его так же внимательно, как схему.
Коротко
- Нельзя рисовать компоненты до числовых требований — схема не имеет ответа, рассчитана ли она на реальную нагрузку. Функциональные требования фиксируют и что делаем, и что сознательно не делаем.
- Оценка на салфетке с точностью ±50% отсекает целые классы решений, считать нужно на пике. Контракт API проектируется до схемы компонентов — он обнаруживает скрытые операции. Хранилище выбирается по паттернам доступа, не по привычке. Схему компонентов проверяют, прогоняя по ней каждый сценарий вручную.
- В каждой системе 1–2 горячие точки — их разбирают глубоко, остальное крупными мазками. Поведение при отказах — это часть дизайна, не доработка после запуска. Опорные числа: память 100 нс, SSD 100 мкс, сеть в зоне 0,5 мс, между регионами 50–150 мс; экземпляр сервиса сотни-тысячи запросов в секунду, узел PostgreSQL 5–20 тысяч транзакций; в сутках сто тысяч секунд, пик в два-пять раз выше среднего.
- Решение считают и в деньгах: экземпляры, база с репликой, отдельные хранилища и трафик по ценам облака, плюс люди на каждый компонент; сравнение вариантов это таблица железа, эксплуатации и цены роста. Ввод в работающую систему это десятый шаг: душитель по сценариям, двойная запись с переливом и сверкой, совместимость контракта и теневой трафик, откат у каждого этапа.
- Паттерн доступа записывают фразой («читаем по ключу, пишем потоком, агрегируем за месяц»), и фраза сама выбирает класс хранилища; два разных паттерна у одной сущности — повод развести пути, а не искать универсальное хранилище.
- Горячие точки находят по пяти признакам: где концентрируется запись, где живёт состояние, где сливаются потоки, что не масштабируется копиями, что растёт быстрее остального; их одна-две, а не восемь.
- Доступ и данные — отдельный шаг из четырёх вопросов: кто вызывает и чем подтверждает, кто имеет право на этот объект, какие персональные данные где остаются и что попадает в журнал действий.
- Цену считают на дизайне по четырём строкам (вычисления, хранение, трафик, управляемые службы) и делят на операции; хранение обычно дороже вычислений, поэтому «сколько хранить» — самое дорогое решение.
- Эволюция включает путь из текущего состояния: первый полезный срез, судьба старого пути, что делаем с накопленными данными и до какого момента возможен откат.
- Метод не нужен на обратимой мелочи, нужен целиком при новом компоненте, необратимой миграции, дорогом решении или нескольких командах; обычный режим — четыре шага за сорок минут, полная сессия — 90 минут с требованиями, разосланными заранее.
Что почитать дальше
- Строительные блоки системного дизайна — словарь компонентов, из которого собирается шаг 6.
- Сквозной пример: система уведомлений — весь метод на одной задаче.
- Оформление и защита дизайна — как превратить результат в документ.