Задача 1 из 10
Сократитель ссылок
Спроектируйте сервис коротких ссылок вроде bit.ly: пользователь отдаёт длинный URL и получает короткий, а переход по короткому ведёт на исходный адрес.
Функциональные требования
- Создать короткую ссылку по длинному URL
- Переход по короткой ссылке — редирект на исходный URL
- Необязательный срок жизни ссылки
- Счётчик переходов по ссылке
Нефункциональные требования
- 100 млн ссылок в базе, +100 млн в год
- Чтений в ~100 раз больше, чем записей: 10 000 переходов/с против 100 созданий/с
- Редирект укладывается в 50 мс на p99
- Ссылки живут годами, потеря ссылки недопустима
- Счётчик переходов может отставать на минуты — это нормально
Нарисуйте архитектуру
Не гонитесь за красотой: на собеседовании от схемы нужно, чтобы по ней было видно путь запроса и где лежат данные. Сначала нарисуйте сами — эталон и разбор ниже.
Блок из палитры — добавить. Тащите мышкой или пальцем. Двойной клик по названию — переименовать. «Связь» — щёлкнуть по одному блоку, потом по другому: получится стрелка.
Схема сохраняется в этом браузере — вкладку можно закрыть и вернуться позже.
1 С чего начать, если ступор
- Сначала уточните требования и прикиньте объём: 100 млн × ~500 байт ≈ 50 ГБ — это влезает в одну БД, шардирование пока не нужно.
- Решите, как генерировать ключ: счётчик в base62 или случайные 7 символов с проверкой коллизии. У обоих вариантов есть цена — назовите её.
- Разделите горячий путь (редирект) и холодный (создание). У них разные требования и разная нагрузка.
- Подумайте, что попадает в кэш и что происходит при промахе.
- Счётчик переходов не должен стоять в пути редиректа.
2 Эталонная схема
Горячий путь — это client → балансировщик → API → кэш. В базу ходим только при промахе, а переход отправляем в очередь и считаем отдельно, чтобы аналитика не удлиняла редирект.
Это один из рабочих вариантов, а не единственно верный. Если у вас иначе, но вы можете объяснить почему — на собеседовании это ровно то, что нужно.
3 Разбор: что должно прозвучать
Требования и оценка объёма
- Уточнили соотношение чтений и записей — именно оно определяет всю архитектуру
- Прикинули объём данных (~50 ГБ) и вывод: шардирование на старте не нужно
- Посчитали нагрузку: 10k RPS на редирект — это про кэш, а не про базу
Генерация короткого ключа
- Счётчик в base62: ключи короткие и без коллизий, но предсказуемые и счётчик — узкое место (лечится диапазонами на инстанс)
- Случайные 7 символов base62: непредсказуемо, но нужна проверка коллизии, то есть лишний поход в БД
- Уникальный индекс по коду в БД как последний рубеж от гонки
Чтение и кэш
- Кэш code → URL с TTL, промах — поход в БД и прогрев
- Ссылки почти неизменяемы, поэтому инвалидация нужна только при удалении и смене срока жизни
- Горячие ссылки: их немного, они и так лежат в кэше — но стоит вспомнить про thundering herd на прогреве
Редирект: 301 или 302
- 301 браузер кэширует — повторные переходы до вас не доедут, статистика развалится
- 302 (или 307) даёт корректный счётчик ценой лишнего запроса — для такого сервиса обычно выбирают его
- Это любимый уточняющий вопрос: покажите, что понимаете trade-off, а не заучили ответ
Счётчик переходов
- Синхронный UPDATE счётчика на каждом переходе убьёт базу на 10k RPS
- Пишем событие в очередь, воркер агрегирует и обновляет статистику пачками
- Отставание на минуты допустимо — это было в нефункциональных требованиях
Куда копать дальше, если спросят
- Что ломается при отказе кэша целиком (и переживёт ли это база)
- Как удалять просроченные ссылки, не блокируя базу
- Когда всё-таки понадобится шардирование и по какому ключу
- Защита от абьюза: лимиты на создание, проверка ссылок на вредоносность
Следующая задача: Хранилище и раздача картинок
Файлы не должны ходить через ваше приложение. Задача про объектное хранилище, CDN и асинхронную обработку.