Как хранить маппинг между длинным и коротким URL
Что проверяют
Мини-задача системного дизайна с поправкой на Go: хранение соответствия кода и адреса, генерация без коллизий и конкурентный доступ. Если вы начнёте с map, интервьюер почти наверняка спросит, что будет при одновременных запросах и при двух репликах сервиса.
Как отвечать
Сразу разделите слои: map годится для прототипа, а в продакшене хранилище это база, перед ней кеш.
- таблица:
id bigintиз последовательности,codeс уникальным индексом,long_url,created_at,expires_at; - код из счётчика:
idкодируется в Base62, семь символов дают около 3,5 триллиона вариантов. Коллизий нет, но коды последовательны и перебираются, поэтому их перемешивают обратимым преобразованием; - несколько инстансов: общий счётчик даёт последовательность PostgreSQL, либо каждый инстанс заранее берёт себе диапазон id и раздаёт его из памяти;
- случайный код: байты из
crypto/rand, вставка сON CONFLICT (code) DO NOTHING, проверкаRowsAffected()и повтор при нуле.math/randпредсказуем, для кодов не годится; - кеш: Redis через
go-redis, ключ это код, значение адрес, плюс TTL. Чтений здесь обычно намного больше, чем записей.
Если map всё же живёт в памяти, например как локальный кеш, её защищают sync.RWMutex: одновременная запись в обычную map роняет процесс с fatal error: concurrent map writes, и recover это не перехватывает. Прогон тестов с флагом -race такое находит.
Обработчик на стандартной библиотеке: маршрут GET /{code} в http.ServeMux, код достаётся через r.PathValue("code"), ответ http.Redirect со статусом 302, если нужна статистика переходов. Ответ 301 браузер закеширует и перестанет к вам ходить.
Где спотыкаются
Увеличивают общий counter++ из нескольких горутин без мьютекса или sync/atomic. Два запроса получают одинаковый номер и одинаковый код.
Путают направления в двух картах longToShort и shortToLong и ищут по короткому коду в карте длинных адресов. Тест на обратное преобразование ловит это за минуту.
Проверяют занятость кода отдельным запросом перед вставкой. От гонки защищает только уникальный индекс, а проверка до вставки лишь открывает для неё окно.