Как масштабировать сервис коротких ссылок и настроить балансировку нагрузки
Что проверяют
Классика system design: умеете ли вы начать с оценки нагрузки и увидеть, что сервис почти целиком состоит из чтения. Следом спрашивают, как генерировать короткий код без коллизий на нескольких инстансах и что будет, если упадёт кэш.
Как отвечать
Начните с цифр: 100 млн новых ссылок в месяц дают около 40 записей в секунду, а переходов обычно в десятки и сотни раз больше. Отсюда вывод: масштабировать нужно прежде всего путь редиректа.
- stateless-сервис на Go: инстансы без локального состояния, их можно добавлять горизонтально. Узкое место почти всегда хранилище, а не
net/http; - балансировка: L7 через nginx, HAProxy, Envoy или облачный балансировщик, round robin или least connections, health checks на
/healthz, graceful shutdown черезhttp.Server.Shutdown, чтобы выкатка не рвала запросы. В Kubernetes это Service, Ingress и HPA; - генерация кода: base62 от числового ID. Счётчик раздаётся блоками (инстанс берёт диапазон, например, в 1000 значений) или используется ID в духе Snowflake. Усечённый хэш от URL даёт коллизии, их придётся проверять;
- хранилище: доступ по ключу. Подойдут PostgreSQL с шардированием по хэшу кода, Cassandra или ScyllaDB, реплики снимают нагрузку чтения;
- кэш: Redis перед базой, ссылки почти не меняются, hit rate высокий. Самые горячие держите ещё и в локальном LRU внутри процесса;
- код ответа: 301 кэшируется браузером и снимает повторные запросы, 302 оставляет каждый переход вам и нужен, если считаете статистику;
- аналитика: клики пишутся асинхронно в Kafka и оттуда в ClickHouse, а не в основную базу на пути редиректа.
Где спотыкаются
Сразу рисуют микросервисы и Kubernetes, не оценив нагрузку. Интервьюер ждёт обоснования, а не набора технологий.
Генерируют код случайно без проверки уникальности или держат глобальный счётчик в одной базе, который становится единой точкой отказа.
Не думают про падение Redis: без защиты от лавины промахов (singleflight из golang.org/x/sync, прогрев, TTL с разбросом) база ляжет следом за кэшем.