open atlas
← Все проекты

systems · advanced · 8d

Досье по проектированию системы

Возьми одну крупную систему — сократитель URL, который должен обслуживать миллиарды редиректов, — и напиши проектный документ, который staff-инженер защищал бы на ревью. Никакого работающего кода: результат — это строгий текст, числа и диаграммы. Ты пройдёшь путь от расплывчатого «сделай короткие ссылки» к очерченным требованиям, прикидочной модели нагрузки, конкретной модели данных и API, к тому единственному узкому месту, которое реально ломается под нагрузкой, и к честной карте того, чем ты пожертвовал ради его устранения.

Собеседования по системному дизайну и настоящие архитектурные ревью награждают за одно и то же: за защищаемый аргумент, а не за заученную диаграмму. Это досье вынуждает пройти всю цепочку на письме — границы, числа, модель, API, узкое место, отказы и компромиссы, — так что слабое звено не спрятать за уверенным наброском. Главная дисциплина здесь — честность: заяви свои допущения, чтобы их можно было оспорить, назови, чем ты пожертвовал, и признай, где проект заканчивается. Сеньор — это тот, кто может сказать «вот что ломается первым, и вот чем я пожертвовал, чтобы отодвинуть это дальше», — и сказать всерьёз. Посчитай вслух, нарисуй три диаграммы и запиши компромиссы — именно этот артефакт отличает архитектора от того, кто умеет лишь пересказывать паттерны.

Результат

Письменное досье на проектирование (8–15 страниц) для одной выбранной системы: очерченные функциональные и нефункциональные требования, прикидочная модель нагрузки с явными допущениями, модель данных и поверхность API, выявленное главное узкое место с его устранением, явный реестр компромиссов и раздел про режимы отказа и масштабирование — подкреплённые диаграммами (HLD, поток данных и один deep-dive).

Этапы

0/6 · 0%
  1. 01Очерти границы, прежде чем считать

    Большинство провальных проектов проваливаются именно здесь: кандидат начинает рисовать прямоугольники, ещё не решив, что система вообще должна своим пользователям. Выбери свою систему и запиши, что входит в объём работ и — что не менее важно — что явно остаётся за его пределами. Раздели функциональные требования («создать короткую ссылку», «развернуть короткую ссылку в её цель», «истечение срока ссылок») и нефункциональные, которые определяют всю форму проекта: целевой QPS, соотношение чтений к записям, бюджет задержки p99 для редиректа, долговечность и насколько устаревшим разрешено быть результату. Для сократителя URL ключевой факт в том, что чтений на порядки больше, чем записей, — назови это соотношение явно, ведь на него опирается каждое последующее решение. Не отделывайся фразами «должно быть быстро и масштабируемо»: преврати каждое прилагательное в число, за которое ты отвечаешь.

    Критерии готовности
    • В досье функциональные и нефункциональные требования перечислены отдельно, и как минимум целевой QPS, соотношение чтений к записям и бюджет задержки p99 названы конкретными числами.
    • Явный список «вне объёма» называет минимум две вещи, которые проект сознательно не решает (например, кастомные vanity-домены, аналитические дашборды).
  2. 02Посчитай вслух

    Теперь преврати требования в числа, которые ограничивают архитектуру. Оцени записи в день и чтения в день из заявленного соотношения, переведи в средний и пиковый QPS, а затем выведи то, что определяет стоимость: сколько байт занимает одна запись, сколько записей накопится за пять лет, суммарный объём хранения и рабочий набор кэша, который поглотил бы горячий хвост чтений. Для самого короткого кода посчитай размер пространства ключей — сколько символов base-62 нужно, чтобы не закончились уникальные ссылки, — потому что именно этот расчёт диктует стратегию генерации ID. Запиши каждое допущение (средний URL — около 100 байт, год — примерно 31,5 млн секунд), чтобы ревьюер мог оспорить входные данные, а не только результат. Цель — не точный прогноз, а модель порядка величины, которая подсказывает, один сервер или тысяча серверов — правильная стартовая картина.

    Критерии готовности
    • В досье пиковый QPS, объём хранения за пять лет и размер рабочего набора кэша выведены из заявленного размера записи в байтах и явных допущений, каждый шаг показан.
    • Расчёт пространства ключей короткого кода показывает, сколько символов base-62 нужно, чтобы покрыть прогнозируемое число ссылок с запасом.
  3. 03Выбери модель данных под паттерн доступа

    Модель нагрузки уже подсказала форму работы: крошечная запись, подавляющий паттерн чтения по ключу и почти полное отсутствие реляционных соединений. Пусть это и определяет модель данных, а не привычка. Опиши запись (короткий код, длинный URL, владелец, время создания, опциональный срок истечения) и реши, каким будет первичный ключ, а затем обоснуй выбор хранилища по существу: key-value-хранилище или одна индексированная таблица читают по первичному ключу почти за O(1) и чисто шардируются по коду, тогда как богатая реляционная схема покупает тебе соединения, которые ты никогда не выполнишь. Спроектируй API под это: путь записи, который чеканит код, и путь чтения, который представляет собой одиночный поиск, возвращающий редирект. Отнесись осознанно к самому редиректу: 301 кэшируется навсегда и дёшев, но лишает тебя подсчёта кликов, а 302 заставляет каждый переход возвращаться к тебе; этот выбор — настоящий продуктовый компромисс, а не деталь. Опиши форматы запроса и ответа и статус-коды для несчастливых путей (неизвестный код, истёкшая ссылка).

    Критерии готовности
    • В досье описаны схема записи, первичный ключ и выбор движка хранения, обоснованный паттерном чтения по ключу (а не просто «возьмём Postgres»).
    • Раздел API определяет эндпоинты создания и разворачивания со статус-кодами для неизвестных и истёкших кодов и явно выбирает 301 или 302 с обоснованием.
  4. 04Найди то, что ломается первым

    Проект под нагрузкой хорош ровно настолько, насколько понятно, где он падает. Проследи путь редиректа по своей высокоуровневой диаграмме и найди компонент, который насыщается первым при вычисленном тобой пиковом QPS, — для сократителя это почти всегда путь чтения, долбящий хранилище, или конкуренция за то, что чеканит новые коды. Назови его точно, а затем точно устрани. Стандартное решение для чтения — кэш перед хранилищем, рассчитанный на твой рабочий набор, но кэш вынуждает принимать реальные решения: какая политика вытеснения, какой TTL и что происходит при промахе или холодном старте — лавина запросов (cache stampede) на популярном коде может оказаться хуже, чем кэш вовсе. Если узкое место — генерация ID, сопоставь стратегии: счётчик, раздаваемый диапазонами, хэш с обработкой коллизий или предгенерированные ключи из пула, — и выбери одну, помня про её поведение при отказе. Закончи новым узким местом, которое обнажает твоё исправление, ведь устранение одного всегда вскрывает следующее.

    Критерии готовности
    • В досье назван главный узел насыщения при пиковой нагрузке, объяснено, почему он насыщается первым, и предложено конкретное исправление с собственными оговорками (например, размер кэша, защита от лавины, политика вытеснения).
    • Оно называет следующее узкое место, которое обнажает исправление, показывая, что проект понят глубже одного слоя.
  5. 05Опиши, как оно отказывает и как растёт

    Сеньорскую работу оценивают по несчастливому пути. Напиши раздел про режимы отказа: что происходит, когда падает узел кэша, когда реплика основного хранилища отстаёт, когда гаснет зона доступности и когда трафик на одну вирусную ссылку концентрируется на одном шарде (проблема горячего ключа). Для каждого случая назови радиус поражения и плавную деградацию: редирект переживёт устаревший кэш, но чеканка совершенно нового кода, возможно, должна отказывать «закрыто», лишь бы не рискнуть дубликатом. Затем честно напиши раздел про масштабирование: объясни, как путь чтения масштабируется горизонтально за балансировщиком и CDN, как ты шардируешь пространство ключей по коду, чтобы рост сводился к добавлению шардов, и где проект упирается в стену, которую большая машина не пробьёт. Свяжи это с требованиями из первого этапа: цель доступности 99,99% — это бюджет примерно в 52 минуты простоя в год, и каждый перечисленный режим отказа тратит из него.

    Критерии готовности
    • В досье есть раздел про режимы отказа, охватывающий как минимум потерю узла кэша, горячий ключ/горячий шард и отказ зоны, каждый — с радиусом поражения и стратегией деградации.
    • Раздел про масштабирование объясняет горизонтальное масштабирование чтения и шардирование по коду и называет один предел, за который архитектура не масштабируется без переработки.
  6. 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 кэша, доля ошибок на пути записи, перекос шардов), чьи дашборды сказали бы тебе, что проект держится или вот-вот сломается.

Навыки

scoping functional vs non-functional requirementsback-of-envelope capacity estimationchoosing a data model and storage engine for an access patterndesigning a read- or write-optimized APIlocating and removing the real bottleneckreasoning about failure modes and graceful degradationwriting a defensible tradeoff argument

Рекомендуемый стек

Excalidraw or draw.io (diagrams)Markdown or Google Docs (dossier)a spreadsheet for the capacity model