Спроектируйте на Python сервис сокращения ссылок: API, хранилище, кэш, масштабирование.
Короткий ответ
- Начать с требований: RPS, соотношение чтение/запись, latency
- FastAPI + uvicorn-воркеры, stateless-приложение
- Код ссылки: base62 от счётчика или случайный с проверкой коллизий
- PostgreSQL как источник истины, Redis-кэш на горячие редиректы
- Редирект 301/308 против 302/307 — вопрос кэширования и статистики
- Клики считать асинхронно: очередь, а не запись в БД на редиректе
- Горизонтальное масштабирование за балансировщиком
Stateless-сервис на FastAPI с PostgreSQL как источником истины, Redis-кэшем на чтение и асинхронным подсчётом кликов масштабируется горизонтально под читающую нагрузку.
Как сказать вслух
пример ответаСначала я уточню нагрузку: такой сервис почти всегда читающий — редиректов на порядки больше, чем созданий. Дальше беру FastAPI с несколькими воркерами, Postgres как основное хранилище и Redis как кэш горячих ссылок, чтобы редирект не ходил в базу. Код генерирую как base62 от счётчика или случайную строку с обработкой коллизий. Клики на редиректе пишу не в базу напрямую, а в очередь, и агрегирую фоном. Приложение stateless, поэтому масштабируется просто добавлением инстансов за балансировщиком.
Подробный ответ
Основной ответ
Требования: предположим 100:1 чтение/запись, редирект должен укладываться в десятки миллисекунд. API: POST /links (принимает URL, опционально кастомный алиас и TTL, отдаёт короткий код) и GET /{code} с редиректом. Генерация кода: base62 от монотонного ID (коротко, но предсказуемо) или 7-8 случайных символов с UNIQUE-ограничением и ретраем на коллизию. Хранилище: PostgreSQL (code PK, url, owner, created_at, expires_at); чтение закрывается Redis-кэшем cache-aside с TTL, негативное кэширование защищает от перебора несуществующих кодов. Редирект: 307/302, если нужна статистика на каждый переход, иначе браузер закэширует 301 и кликов не будет видно. Счётчики кликов — событие в очередь (Redis Stream, Kafka) и фоновый консьюмер с батч-агрегацией. Масштабирование: stateless FastAPI за балансировщиком, реплики Postgres на чтение, мониторинг p99 и hit-rate кэша.
Ключевые моменты
- Читающий профиль нагрузки. Архитектуру диктует редирект: кэш перед БД, минимальная работа на горячем пути, никакой синхронной записи.
- Генерация кода. Счётчик+base62 даёт короткие коды, но раскрывает объёмы; случайный код с ретраем на UNIQUE — компромисс по умолчанию.
- Семантика редиректа. 301 кэшируется браузером навсегда — быстро, но теряется аналитика; 302/307 держит трафик на сервисе.
- Асинхронная аналитика. Инкремент в БД на каждый редирект станет узким местом; события в очередь и батчевая агрегация решают это.
Практический контекст
На системном интервью для Python-разработчика смотрят на структуру рассуждения: уточнение требований, оценка нагрузки, явные компромиссы, а не заученная схема. Ожидают уверенного владения своим стеком — воркеры uvicorn, пул соединений, транзакции, устройство кэша — и честных ответов про узкие места: что умрёт первым и как это увидеть в метриках.
Частые ошибки
- Начинают рисовать компоненты, не уточнив нагрузку и соотношение чтение/запись
- Выбирают 301 и теряют статистику переходов из-за кэширования в браузере
- Пишут счётчик кликов синхронно в Postgres на каждом редиректе