На system design собеседовании важно связать требования с устройством сервиса и объяснить, чем вы платите за выбранные свойства. Один и тот же продукт можно спроектировать по-разному, если меняются нагрузка, допустимые потери данных или требования к удалению. Разберём сервис коротких ссылок: он принимает длинный адрес, выдаёт короткий ключ и перенаправляет посетителя. Все показатели ниже — учебные допущения, а не статистика или архитектура Get Offer.
Зафиксируйте требования и границы кейса
Основные операции: создать ссылку, открыть её, отключить или удалить ссылку владельца. Для первой версии предположим авторизованное создание и публичный переход по ключу. Не включаем в обязательный минимум пользовательские домены, произвольные псевдонимы, точную географическую аналитику и редактирование назначения. Если интервьюер считает одну из этих функций обязательной, нужно пересмотреть модель данных и правила кеширования, а не добавить её в конце одной стрелкой на схеме.
Сразу уточним свойства: нельзя возвращать успешное создание, если запись ещё может исчезнуть без допустимого по договорённости восстановления; переходы должны оставаться быстрыми при большом числе чтений; отключённые ссылки не должны бесконечно жить в кеше. Конкретные целевые p95, доступность и время восстановления согласуем с интервьюером. Они не следуют автоматически из слова «высокая нагрузка». Для учебного обсуждения разделим создание, переходы и сбор статистики по важности.
- Допустимы ли только HTTP и HTTPS назначения?
- Нужен ли срок действия и можно ли менять исходный адрес?
- Что означает удаление: скрыть ссылку или физически убрать данные?
- Должна ли блокировка вступать в силу немедленно?
- Нужна ли точная статистика каждого перехода или достаточно приблизительной?
- Какая потеря данных и какой простой допустимы при отказе региона?
Посчитайте нагрузку из заданных чисел
Принимаем 1 млн новых ссылок и 100 млн переходов в сутки. В сутках 86 400 секунд. Средняя скорость создания равна 1 000 000 / 86 400, то есть примерно 11,6 записи в секунду. Средняя скорость переходов — 100 000 000 / 86 400, примерно 1 157,4 запроса в секунду. Отношение чтений к созданиям составляет 100:1. Именно оно мотивирует отдельное обсуждение пути чтения и кеша.
Среднее не определяет пик. Вводим отдельное учебное предположение: пиковая нагрузка в десять раз выше средней для обеих операций. Получаем около 116 созданий и 11 574 переходов в секунду после округления вверх. Это гипотеза для расчёта, которую в реальном сервисе заменяют наблюдениями. Число экземпляров приложения нельзя вывести только из этих RPS: нужны измерения времени обработки, ресурсов и поведения зависимостей.
Величина | Расчёт | Учебная оценка |
|---|---|---|
Создания в среднем | 1 000 000 / 86 400 | 11,6 в секунду |
Переходы в среднем | 100 000 000 / 86 400 | 1 157,4 в секунду |
Переходы в пик | 100 000 000 / 86 400 × 10 | 11 574 в секунду, округлено вверх |
Новые записи за год | 1 000 000 × 365 | 365 млн |
Полезные данные за год | 365 млн × 300 байт | 109,5 ГБ в десятичных единицах |
Оценка 300 байт на запись — ещё одно явное допущение: средний URL, ключ и служебные поля. Длинные адреса могут увеличить её. При трёх полных копиях полезные данные займут 328,5 ГБ, но это не размер дисков для закупки. Индексы, версии строк, журналы, резервные копии и запас свободного места считаются отдельно. Год из 365 дней выбран для упражнения; retention может радикально изменить итог.
Опишите API и наблюдаемые результаты
POST /links принимает исходный URL и необязательный срок действия, а также ключ идемпотентности. Владелец определяется из проверенной сессии, а не из произвольного поля запроса. После фиксации записи сервер возвращает 201 и короткий адрес. Повтор запроса с тем же ключом и тем же содержимым возвращает прежний результат; тот же ключ с другим содержимым требует явной ошибки, например 409. Это защищает от создания нескольких ссылок после сетевого таймаута.
GET /r/{key} находит активную запись и возвращает перенаправление с заголовком Location. Для учебного сервиса выбираем временный редирект 302 и Cache-Control: no-store на ответе браузеру: это облегчает управление блокировкой и изменениями политики. Публичное постоянное кеширование 301 потребовало бы других гарантий. Неизвестный ключ возвращает 404. Для истёкшей ссылки можно также использовать 404, если не хотим раскрывать историю её существования.
DELETE /links/{key} проверяет владельца и помечает запись отключённой. Повторное удаление не должно случайно создавать новую ссылку или падать из-за уже выполненной операции. В API стоит различать ошибки валидации, отсутствие доступа, ограничение частоты и временную недоступность. Ответ «попробуйте снова» без стабильного кода затруднит клиенту выбор между исправлением входа и безопасным повтором.
Выберите модель данных и способ выдачи ключей
Минимальная запись содержит key, destination, owner_id, created_at, expires_at и disabled_at. Первичный ключ обеспечивает уникальность key. Для списка ссылок пользователя пригодится индекс по owner_id и created_at; он не нужен непосредственно для перенаправления. Ключи идемпотентности можно хранить отдельно с владельцем, хешем запроса и результатом. Уникальное ограничение должно соответствовать области действия ключа: обычно это пара владелец плюс ключ запроса.
Рассмотрим случайный ключ из восьми символов алфавита длиной 62. Пространство содержит 62 в восьмой степени, то есть 218 340 105 584 896 вариантов. Это большое число не отменяет коллизии. Генерируем кандидат криптографически стойким генератором, пытаемся вставить запись и при нарушении уникальности повторяем с новым ключом. Проверка «сначала SELECT, потом INSERT» без ограничения в базе не защищает от двух одновременных созданий.
Альтернатива — последовательный числовой идентификатор с кодированием base62. Тогда проще обеспечить уникальность, но адреса становятся предсказуемыми, а выдача диапазонов требует отдельного решения при распределённой записи. Хеш полного URL не всегда удобен: одинаковое назначение может принадлежать разным владельцам и иметь разные сроки. На интервью выберите один вариант, объясните компромисс и не объявляйте длину ключа свойством безопасности доступа.
Разделите путь записи и горячий путь перехода
Балансировщик распределяет запросы между экземплярами приложения без локального состояния пользователя. Обработчик создания проверяет вход, резервирует ключ и фиксирует транзакцию в основной базе. Только затем отвечает клиенту. При сетевом обрыве после фиксации клиент восстанавливает результат через идемпотентность. Если запись в кеш не удалась, создание всё равно может завершиться: база остаётся источником истины, а первый переход заполнит кеш.
Обработчик перехода использует cache-aside: читает Redis, при промахе загружает активную запись из базы и кеширует результат на ограниченное время. TTL не должен выходить за expires_at. Для популярных ключей полезны ограничение параллельных загрузок и небольшой случайный разброс TTL, чтобы множество экземпляров не пошло в базу одновременно. Кеширование отсутствующих ключей может уменьшить нагрузку от перебора, но требует короткого срока и ограничения памяти.
Не делайте точную синхронную запись аналитики обязательным шагом каждого перехода без продуктового требования. Событие можно отправить в очередь и обработать отдельно. Здесь возникает новая договорённость: допустимо ли потерять событие при отказе очереди? Если нет, понадобится надёжное промежуточное сохранение, которое увеличит задержку и стоимость пути чтения. Ответ зависит от того, это приблизительная продуктовая статистика или оплачиваемый по кликам контракт.
Обсудите отключение ссылок и отказ кеша
Удаление из базы само по себе не убирает старую запись из Redis. Для первой версии можно согласовать окно распространения отключения, например до 60 секунд, и ограничить TTL этим окном; при успешном удалении дополнительно инвалидировать ключ. Если требование — немедленная блокировка, одного TTL недостаточно. Нужен путь проверки актуального запрета или подтверждённая инвалидация всех мест чтения. Нельзя одновременно обещать строгую мгновенную блокировку и бесконтрольную выдачу устаревшего кеша.
При падении Redis приложение может читать из базы, но это не означает, что база выдержит весь пиковый трафик. Проверьте такой режим нагрузочным тестом, ограничьте параллелизм запросов и установите короткие таймауты. Если зависимость не справляется, управляемая ошибка 503 лучше бесконечного роста очереди в памяти. Для популярных ссылок локальный ограниченный кеш уменьшит давление, но добавит ещё одно место, из которого надо удалять отключённые записи.
Реплики базы помогают распределить чтение, однако задержка репликации может сделать только что созданную ссылку временно невидимой. Возможные решения: короткое чтение с primary после промаха, заполнение кеша после commit или явный контракт eventual consistency. Каждое решение проверяем на гонках. Переключение primary требует отдельно обсудить подтверждение записи и допустимую потерю данных: наличие реплики ещё не доказывает нулевой RPO.
Добавьте безопасность, наблюдаемость и план роста
Разрешайте только ожидаемые схемы URL и отклоняйте некорректные адреса. Если сервис лишь возвращает Location, ему не обязательно загружать содержимое назначения. Появление предпросмотра или проверки страницы добавляет риск серверных запросов к внутренним адресам и требует отдельной защиты. Предусмотрите ограничения создания, механизм жалоб и отключение вредоносных ссылок. Короткий случайный ключ не заменяет авторизацию для приватного содержимого.
Следите за p50/p95/p99 времени перехода, долей ошибок, частотой промахов кеша, временем запросов к базе, числом повторов генерации ключа и задержкой очереди. Для бизнес-аналитики отделяйте реальные переходы от проверок доступности и роботов настолько, насколько это позволяет согласованная методика. В журналах не обязательно сохранять полный URL с персональными параметрами; используйте ключ и технический идентификатор запроса, соблюдая сроки хранения.
Начните с решения, которое можно проверить: несколько экземпляров приложения, PostgreSQL, ограниченный кеш и наблюдаемость. Шардирование и несколько регионов добавляйте при измеренном ограничении или прямом требовании. Завершите ответ списком неизвестных: профиль пиков, распределение популярности ключей, срок хранения, требования к блокировкам и точность аналитики. Такой список показывает, какие данные действительно изменят архитектуру.
Как проверить расчёты и подготовить ответ
- Пересчитайте средние скорости из суточных чисел и отдельно подпишите коэффициент пика.
- Объясните, какие расходы не вошли в оценку 109,5 ГБ.
- Пройдите создание и переход по схеме, назвав источник истины.
- Смоделируйте коллизию ключа, таймаут после commit и недоступность Redis.
- Выберите одно требование для изменения, например мгновенную блокировку, и покажите влияние на чтение.
Ограничения уникальности и внешние ключи можно сверить с документацией PostgreSQL. Документация подтверждает свойства конкретного механизма; целевые задержки и достаточность выбранных ресурсов подтверждаются испытанием вашей реализации. В учебном ответе явно отделяйте эти два вида доказательств.