systems · advanced · 8d
Досье по проектированию системы
Возьми одну крупную систему — сократитель URL, который должен обслуживать миллиарды редиректов, — и напиши проектный документ, который staff-инженер защищал бы на ревью. Никакого работающего кода: результат — это строгий текст, числа и диаграммы. Ты пройдёшь путь от расплывчатого «сделай короткие ссылки» к очерченным требованиям, прикидочной модели нагрузки, конкретной модели данных и API, к тому единственному узкому месту, которое реально ломается под нагрузкой, и к честной карте того, чем ты пожертвовал ради его устранения.
Результат
Письменное досье на проектирование (8–15 страниц) для одной выбранной системы: очерченные функциональные и нефункциональные требования, прикидочная модель нагрузки с явными допущениями, модель данных и поверхность API, выявленное главное узкое место с его устранением, явный реестр компромиссов и раздел про режимы отказа и масштабирование — подкреплённые диаграммами (HLD, поток данных и один deep-dive).
Этапы
0/6 · 0%- 01Очерти границы, прежде чем считать
Большинство провальных проектов проваливаются именно здесь: кандидат начинает рисовать прямоугольники, ещё не решив, что система вообще должна своим пользователям. Выбери свою систему и запиши, что входит в объём работ и — что не менее важно — что явно остаётся за его пределами. Раздели функциональные требования («создать короткую ссылку», «развернуть короткую ссылку в её цель», «истечение срока ссылок») и нефункциональные, которые определяют всю форму проекта: целевой QPS, соотношение чтений к записям, бюджет задержки p99 для редиректа, долговечность и насколько устаревшим разрешено быть результату. Для сократителя URL ключевой факт в том, что чтений на порядки больше, чем записей, — назови это соотношение явно, ведь на него опирается каждое последующее решение. Не отделывайся фразами «должно быть быстро и масштабируемо»: преврати каждое прилагательное в число, за которое ты отвечаешь.
Критерии готовности- В досье функциональные и нефункциональные требования перечислены отдельно, и как минимум целевой QPS, соотношение чтений к записям и бюджет задержки p99 названы конкретными числами.
- Явный список «вне объёма» называет минимум две вещи, которые проект сознательно не решает (например, кастомные vanity-домены, аналитические дашборды).
- 02Посчитай вслух
Теперь преврати требования в числа, которые ограничивают архитектуру. Оцени записи в день и чтения в день из заявленного соотношения, переведи в средний и пиковый QPS, а затем выведи то, что определяет стоимость: сколько байт занимает одна запись, сколько записей накопится за пять лет, суммарный объём хранения и рабочий набор кэша, который поглотил бы горячий хвост чтений. Для самого короткого кода посчитай размер пространства ключей — сколько символов base-62 нужно, чтобы не закончились уникальные ссылки, — потому что именно этот расчёт диктует стратегию генерации ID. Запиши каждое допущение (средний URL — около 100 байт, год — примерно 31,5 млн секунд), чтобы ревьюер мог оспорить входные данные, а не только результат. Цель — не точный прогноз, а модель порядка величины, которая подсказывает, один сервер или тысяча серверов — правильная стартовая картина.
Критерии готовности- В досье пиковый QPS, объём хранения за пять лет и размер рабочего набора кэша выведены из заявленного размера записи в байтах и явных допущений, каждый шаг показан.
- Расчёт пространства ключей короткого кода показывает, сколько символов base-62 нужно, чтобы покрыть прогнозируемое число ссылок с запасом.
- 03Выбери модель данных под паттерн доступа
Модель нагрузки уже подсказала форму работы: крошечная запись, подавляющий паттерн чтения по ключу и почти полное отсутствие реляционных соединений. Пусть это и определяет модель данных, а не привычка. Опиши запись (короткий код, длинный URL, владелец, время создания, опциональный срок истечения) и реши, каким будет первичный ключ, а затем обоснуй выбор хранилища по существу: key-value-хранилище или одна индексированная таблица читают по первичному ключу почти за O(1) и чисто шардируются по коду, тогда как богатая реляционная схема покупает тебе соединения, которые ты никогда не выполнишь. Спроектируй API под это: путь записи, который чеканит код, и путь чтения, который представляет собой одиночный поиск, возвращающий редирект. Отнесись осознанно к самому редиректу: 301 кэшируется навсегда и дёшев, но лишает тебя подсчёта кликов, а 302 заставляет каждый переход возвращаться к тебе; этот выбор — настоящий продуктовый компромисс, а не деталь. Опиши форматы запроса и ответа и статус-коды для несчастливых путей (неизвестный код, истёкшая ссылка).
Критерии готовности- В досье описаны схема записи, первичный ключ и выбор движка хранения, обоснованный паттерном чтения по ключу (а не просто «возьмём Postgres»).
- Раздел API определяет эндпоинты создания и разворачивания со статус-кодами для неизвестных и истёкших кодов и явно выбирает 301 или 302 с обоснованием.
- 04Найди то, что ломается первым
Проект под нагрузкой хорош ровно настолько, насколько понятно, где он падает. Проследи путь редиректа по своей высокоуровневой диаграмме и найди компонент, который насыщается первым при вычисленном тобой пиковом QPS, — для сократителя это почти всегда путь чтения, долбящий хранилище, или конкуренция за то, что чеканит новые коды. Назови его точно, а затем точно устрани. Стандартное решение для чтения — кэш перед хранилищем, рассчитанный на твой рабочий набор, но кэш вынуждает принимать реальные решения: какая политика вытеснения, какой TTL и что происходит при промахе или холодном старте — лавина запросов (cache stampede) на популярном коде может оказаться хуже, чем кэш вовсе. Если узкое место — генерация ID, сопоставь стратегии: счётчик, раздаваемый диапазонами, хэш с обработкой коллизий или предгенерированные ключи из пула, — и выбери одну, помня про её поведение при отказе. Закончи новым узким местом, которое обнажает твоё исправление, ведь устранение одного всегда вскрывает следующее.
Критерии готовности- В досье назван главный узел насыщения при пиковой нагрузке, объяснено, почему он насыщается первым, и предложено конкретное исправление с собственными оговорками (например, размер кэша, защита от лавины, политика вытеснения).
- Оно называет следующее узкое место, которое обнажает исправление, показывая, что проект понят глубже одного слоя.
- 05Опиши, как оно отказывает и как растёт
Сеньорскую работу оценивают по несчастливому пути. Напиши раздел про режимы отказа: что происходит, когда падает узел кэша, когда реплика основного хранилища отстаёт, когда гаснет зона доступности и когда трафик на одну вирусную ссылку концентрируется на одном шарде (проблема горячего ключа). Для каждого случая назови радиус поражения и плавную деградацию: редирект переживёт устаревший кэш, но чеканка совершенно нового кода, возможно, должна отказывать «закрыто», лишь бы не рискнуть дубликатом. Затем честно напиши раздел про масштабирование: объясни, как путь чтения масштабируется горизонтально за балансировщиком и CDN, как ты шардируешь пространство ключей по коду, чтобы рост сводился к добавлению шардов, и где проект упирается в стену, которую большая машина не пробьёт. Свяжи это с требованиями из первого этапа: цель доступности 99,99% — это бюджет примерно в 52 минуты простоя в год, и каждый перечисленный режим отказа тратит из него.
Критерии готовности- В досье есть раздел про режимы отказа, охватывающий как минимум потерю узла кэша, горячий ключ/горячий шард и отказ зоны, каждый — с радиусом поражения и стратегией деградации.
- Раздел про масштабирование объясняет горизонтальное масштабирование чтения и шардирование по коду и называет один предел, за который архитектура не масштабируется без переработки.
- 06Запиши компромиссы и защити их
Каждое решение в досье закрыло какую-то альтернативу, и проект, делающий вид, что это не так, выглядит наивным. Собери реестр компромиссов: для каждого крупного выбора — key-value против реляционного хранилища, 301 против 302, ID на счётчике против ID на хэше, строгая против итоговой согласованности для только что созданной ссылки — укажи, что ты выбрал, чем пожертвовал и при каком условии выбрал бы иначе. Именно здесь кусается согласованность: пользователь, который создал ссылку и сразу ею поделился, ждёт, что она развернётся, поэтому «итогово согласованные чтения» — это обещание, которое нужно оговорить. Заверши досье коротким абзацем «если бы требования изменились»: что перевернётся, если записи вдруг сравняются с чтениями или если продукт потребует аналитику по кликам, — доказывая, что проект это обоснованная позиция, а не заученный шаблон. Ревьюер должен иметь возможность не согласиться с выбором и всё же увидеть, что ты понимал его цену.
Критерии готовности- Реестр компромиссов перечисляет минимум четыре крупных решения, каждое — с тем, что выбрано, чем пожертвовано и при каком условии выбор изменился бы.
- Завершающий абзац «если бы требования изменились» показывает, как проект адаптируется к перевёрнутому соотношению чтений и записей или к новому требованию аналитики.
Рубрика
| Джуниор | Миддл | Сеньор | |
|---|---|---|---|
| Строгость прикидочной оценки нагрузки | В досье присутствуют числа (QPS, объём хранения), но вывод отсутствует или принят на веру; допущения не заявлены, и ревьюер не может оспорить исходные данные. | Пиковый QPS, объём хранения за пять лет и рабочий набор кэша выведены шаг за шагом из явных размеров записи и заявленного соотношения чтений и записей; расчёт пространства ключей показывает, сколько символов base-62 нужно с запасом. | Каждое допущение явно помечено и проверяемо; модель также выводит необходимый cache-hit rate, чтобы хранилище оставалось в пределах лимита IOPS, и ты называешь допущение, которое сильнее всего изменило бы архитектуру, если бы оно ошибалось на порядок величины. |
| Качество выявления узкого места и его устранения | Кэш размещён перед хранилищем, потому что «кэширование помогает при чтениях»; политика вытеснения, TTL и поведение при холодном старте не рассматриваются. | Главное узкое место прослежено до пути чтения из хранилища при пиковом QPS; кэш рассчитан на рабочий набор с именованной политикой вытеснения и TTL; признана хотя бы одна вторичная проблема (лавина на популярном ключе, холодный старт). | Ты моделируешь усиление промахов кэша при лавине (N одновременных промахов, каждый выдаёт чтение из хранилища) и предлагаешь смягчение (объединение запросов на основе блокировки, вероятностное раннее истечение или дедупликация запросов); затем называешь следующее узкое место, которое обнажает кэш, — как правило, fanout записей или конкуренция при генерации ID, — доказывая, что проект понят послойно. |
| Покрытие режимов отказа и радиус поражения | Режимы отказа перечислены пунктами («если кэш упал, чтения идут в БД») без количественной оценки радиуса поражения или уточнения плавной деградации. | Хотя бы потеря узла кэша, концентрация горячего ключа и отказ зоны проанализированы: назван радиус поражения, описано поведение плавной деградации, и выбор между fail-open и fail-closed обоснован хотя бы в одном случае. | Каждый режим отказа привязан к бюджету SLO: ты вычисляешь, сколько минут в год каждый режим потребляет из бюджета доступности 99,99% (≈52 мин/год), и показываешь, какая комбинация решений об избыточности расходует бюджет наиболее эффективно. |
| Артикуляция компромиссов и их защищаемость | Решения заявлены без рассмотрения альтернатив; выбор 301 против 302 описан как техническая деталь, а не продуктовый компромисс между кэшируемостью и видимостью аналитики. | Реестр компромиссов охватывает не менее четырёх решений (движок хранения, код редиректа, стратегия ID, модель согласованности) с тем, что выбрано, чем пожертвовано и при каком условии выбор изменился бы. | Раздел «если бы требования изменились» демонстрирует, что досье — это обоснованная позиция: ты показываешь, как изменение соотношения чтений и записей (профиль чата против сократителя) или добавление аналитики по кликам инвертирует хотя бы три из четырёх крупных решений, и можешь назвать конкретную связанность, которую нужно разрезать первой. |
Эталонный разбор (спойлер)
Почему граница важнее расчёта: требования фиксируют доминирующий паттерн доступа, а он определяет почти всё остальное — движок хранения, стратегию кэширования, ключ шардирования, модель согласованности. Для сократителя URL единственный важнейший факт в том, что чтений в 100 раз больше записей или более; ошибка в этом соотношении делает все последующие расчёты вводящими в заблуждение.
Прикидочный расчёт как инструмент ограничений: ценность расчёта нагрузки не в самом числе, а в том, что оно исключает. Если рабочий набор кэша — 20 ГБ, один сервер справляется; если 200 ТБ — распределённый кэш-уровень обязателен до написания единой строки кода. Указывай допущения явно: ревьюер должен уметь изменить один входной параметр и заново вывести заключение, а не реконструировать предположения.
Режим отказа — лавина: когда истекает популярный ключ кэша, все параллельные читатели промахиваются одновременно и бомбардируют хранилище. Радиус поражения масштабируется с долей трафика ключа — истечение вирусной ссылки может поднять нагрузку на хранилище до 50× нормальной менее чем за секунду. Меры защиты: вероятностное досрочное обновление (обновление до истечения TTL), объединение запросов под блокировкой или отдельный путь прогрева, не позволяющий ключу остыть.
Компромисс, удивляющий большинство: код редиректа. 301 (Moved Permanently) кэшируется браузерами бессрочно, что устраняет почти весь трафик чтения на твои серверы, — но это также означает, что ты не можешь отслеживать клики и теряешь возможность обновить или истечь целевой URL без ожидания, пока браузерные кэши очистятся. 302 (Found) заставляет каждый редирект обращаться к твоим серверам, что стоит инфраструктуры, но сохраняет аналитику и контроль. Правильный ответ зависит от продукта, а не от протокола.
Сделай по-сеньорски
- Напиши то же досье для второй системы с противоположным профилем — чат-бэкенда, который write-heavy и stateful, — и сопоставь, как перевёрнутое соотношение чтений и записей инвертирует почти каждое решение, принятое тобой для сократителя.
- Добавь количественный бюджет доступности: переведи целевой SLO в бюджет ошибок в минутах в год, затем покажи, как каждый режим отказа из твоего досье съедает его часть и куда ты потратил бы остаток бюджета на избыточность.
- Добавь раздел «что бы я измерял в проде»: три-четыре метрики (p99-задержка редиректа, hit rate кэша, доля ошибок на пути записи, перекос шардов), чьи дашборды сказали бы тебе, что проект держится или вот-вот сломается.