Структура собеседования по проектированию систем
Разберите пятишаговую структуру RADIO — Requirements, API, Data, Infrastructure, Optimise — и потренируйтесь применять её к сокращателю URL
«Структура собеседования по проектированию систем» — бесплатный урок DSA Interview Prep на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DSA Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DSA Interview Prep содержит 4 уроков всего.
Почему проектирование систем важно на собеседованиях
Собеседования по проектированию систем проверяют вашу способность мыслить в масштабе — как бы вы спроектировали Твиттер, YouTube или сервис сокращения URL для миллиардов пользователей? В отличие от задач на программирование с единственным правильным ответом, проектирование систем не имеет единственного решения: вам нужно принимать компромиссные решения и обосновывать их. На позициях уровня старшего и ведущего специалиста этому этапу посвящают исключительно 30–45 минут.
Интервьюеры оценивают, можете ли вы уточнить требования, оценить нагрузку, предложить архитектуру верхнего уровня, подробно разобрать ключевые компоненты и обсудить компромиссы — и при этом ясно излагать мысли. Структурированный подход не даёт вам уходить в сторону и помогает охватить все важные аспекты.
# System design is not about a single correct answer.
# Interviewers look for:
eval_criteria = [
'Ability to clarify requirements before designing',
'Back-of-envelope capacity estimation',
'High-level architecture with clear components',
'Data modelling and storage choice',
'Handling scalability (10x, 100x load)',
'Trade-off discussion (consistency vs availability, etc.)',
'Communication: talking through decisions as you make them',
]
for c in eval_criteria:
print('-', c)Обзор структуры RADIO
Структура RADIO задаёт повторяемую последовательность из пяти шагов для любого собеседования по проектированию систем:
- R — Требования: функциональные и нефункциональные
- A — Проектирование API: какие операции предоставляет система?
- D — Модель данных: какие данные хранятся и каким образом?
- I — Инфраструктура: компоненты верхнего уровня (серверы, очереди, кэши)
- O — Оптимизация: узкие места, кэширование, горизонтальное разделение данных, репликация
Всегда проходите эти шаги по порядку, но возвращайтесь к предыдущим и уточняйте их, когда появляются новые сведения. Уделяйте каждому этапу примерно одинаковое время. Никогда не переходите сразу к рисованию блоков, не уточнив сначала требования.
RADIO = {
'R': 'Requirements — What must the system do? What scale?',
'A': 'API — Define endpoints/operations the system exposes',
'D': 'Data Model — Entities, schemas, storage types',
'I': 'Infra — High-level architecture: servers, queues, caches, CDN',
'O': 'Optimise — Identify and address bottlenecks, trade-offs',
}
for step, desc in RADIO.items():
print(f'[{step}] {desc}')
print('\nTiming guide for a 45-min interview:')
print(' R: 5 min | A: 5 min | D: 10 min | I: 15 min | O: 10 min')Шаг R: уточнение требований
Никогда не начинайте проектирование, не уточнив требования. Спросите о функциональных требованиях (что делает система) и нефункциональных требованиях (масштаб, задержка, доступность). Для сервиса сокращения URL:
- Функциональные: сократить URL, перенаправить на исходный URL, при необходимости поддерживать пользовательские псевдонимы и срок действия
- Нефункциональные: сколько URL создаётся в день? Нагрузка в основном на чтение или на запись? Требование к доступности (99.9% или 99.99%)? Допустимая задержка?
Явное изложение предположений показывает зрелость. Интервьюеры часто намеренно дают расплывчатые требования, чтобы проверить, задаёте ли вы правильные вопросы. Две минуты на уточнение избавят вас от проектирования неправильной системы.
# Requirements questions for URL Shortener:
functional = [
'Shorten a given URL to a 7-character alias',
'Redirect short URL to original URL',
'Allow custom aliases (optional)',
'URL expiry (optional)',
'Analytics: click count per URL (optional)',
]
non_functional = [
'100 million new URLs per day (write: ~1160/sec)',
'10:1 read:write ratio => 11,600 redirects/sec',
'Redirects must be < 100ms p99 latency',
'99.99% availability (< 1 hr downtime/year)',
'URLs must be globally accessible',
]
print('Functional:')
for f in functional: print(' -', f)
print('\nNon-functional:')
for nf in non_functional: print(' -', nf)Шаг R: предварительная оценка ресурсов
После уточнения требований оцените необходимую ёмкость. Это показывает, что вы умеете рассуждать о масштабе до предложения решений. Основные показатели, которые нужно вывести: количество запросов в секунду (RPS), необходимый объём хранилища в день и год, пропускная способность и объём памяти для кэширования.
Используйте округлённые числа и смело делайте приблизительные оценки. Интервьюеров интересует порядок величины, а не точные значения. Пример для сервиса сокращения URL: 100 млн записей в день ÷ 86400 ≈ 1160 записей/с. 10 млрд чтений в день ÷ 86400 ≈ 115 тыс. чтений/с. Каждая запись URL ≈ 500 байт: 100 млн × 500 байт = 50 GB/день, 18 TB/год.
# Back-of-envelope for URL Shortener
writes_per_day = 100_000_000 # 100 million URLs/day
read_write_ratio = 100 # 100:1 read/write
reads_per_day = writes_per_day * read_write_ratio
bytes_per_url = 500 # url string + metadata
years_to_store = 5
print('=== Capacity Estimation ===')
print(f'Writes/sec: {writes_per_day / 86400:.0f}')
print(f'Reads/sec: {reads_per_day / 86400:,.0f}')
print(f'Storage/day: {writes_per_day * bytes_per_url / 1e9:.1f} GB')
print(f'Storage total ({years_to_store}y): {writes_per_day * bytes_per_url * 365 * years_to_store / 1e12:.1f} TB')
cache_hit_rate = 0.80
hot_urls = reads_per_day * (1 - cache_hit_rate)
print(f'\n80% cache hit rate: {cache_hit_rate*100}% of reads from cache')
print(f'DB reads/sec: {hot_urls / 86400:,.0f}')Шаг A: проектирование API
Определите набор API — операции, которые система предоставляет клиентам и внутренним службам. Чётко укажите метод HTTP, путь конечной точки, параметры запроса и формат ответа. Это задаёт основу остального проектирования: всё остальное существует для реализации этих API.
Для сервиса сокращения URL основными являются два API: (1) POST /shorten для создания короткого URL, (2) GET /{alias} для перенаправления. Дополнительно: DELETE /{alias} для удаления, GET /{alias}/stats для аналитики. Укажите коды ответа (201 Создано, 301 Перенаправление, 404 Не найдено).
# API Design for URL Shortener
apis = [
{
'method': 'POST',
'path': '/api/v1/shorten',
'request': '{"long_url": "https://...", "alias": "optional", "expires_at": "optional"}',
'response': '201 Created: {"short_url": "https://short.ly/abc1234", "alias": "abc1234"}',
},
{
'method': 'GET',
'path': '/{alias}',
'request': 'No body',
'response': '301 Redirect to long_url (or 404 Not Found)',
},
{
'method': 'GET',
'path': '/api/v1/{alias}/stats',
'request': 'Optional: date range query params',
'response': '200 OK: {"clicks": 42000, "unique_visitors": 15000}',
},
]
for api in apis:
print(f'{api["method"]} {api["path"]}')
print(f' Request: {api["request"]}')
print(f' Response: {api["response"]}')
print()Шаг D: модель данных
Модель данных определяет, что и как вы храните. Определите основные сущности и их атрибуты. Для сервиса сокращения URL: таблица urls с полями alias (первичный ключ), long_url, created_at, expires_at и user_id. Дополнительно можно использовать таблицу clicks для аналитики.
Крайне важно выбрать подходящий тип хранилища: реляционная БД для структурированных данных со сложными запросами; хранилище «ключ—значение» (Редис, DynamoDB) для поиска псевдонимов за O(1) при большом масштабе; объектное хранилище (S3) для больших двоичных объектов. Для сервиса сокращения URL идеально подходит хранилище «ключ—значение» с ключом в виде псевдонима для чтения, а для записи и управления можно использовать реляционную БД.
# Data model for URL Shortener
# Core table (PostgreSQL)
urls_schema = '''
CREATE TABLE urls (
alias VARCHAR(16) PRIMARY KEY, -- e.g., 'abc1234'
long_url TEXT NOT NULL,
user_id UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
click_count BIGINT DEFAULT 0
);
CREATE INDEX ON urls(user_id);
'''
# Cache layer (Redis) for hot reads
redis_schema = '''
alias => long_url # O(1) GET on cache hit
TTL = 24 hours (or until expiry)
Cache eviction: LRU
'''
print('PostgreSQL schema:')
print(urls_schema)
print('Redis cache:')
print(redis_schema)
print('Storage split: Redis for hot reads (~80%), PostgreSQL for writes and cold reads')Шаг I: инфраструктура верхнего уровня
Набросайте инфраструктуру верхнего уровня: какие серверы отвечают за какие задачи, как данные перемещаются между компонентами и какие внешние службы используются. Для масштабного сервиса сокращения URL:
- Балансировщик нагрузки: распределяет трафик между репликами служб записи и чтения
- Служба записи: генерирует псевдоним, проверяет уникальность, записывает данные в БД, инвалидирует кэш
- Служба чтения и перенаправления: сначала проверяет кэш Редис, а при промахе обращается к БД
- Реляционная БД: источник истины (с репликами для чтения)
- Кластер Редис: кэширует часто запрашиваемые соответствия URL для чтения менее чем за миллисекунду
# ASCII architecture sketch
architecture = '''
Clients
|
[Load Balancer]
/ \\
[Write API] [Read/Redirect API]
| | |
[Alias Gen] [Redis Cache]
| |
[PostgreSQL]---->[Read Replicas]
Alias Generation:
- Base62 encoding of auto-incrementing ID
- 62^7 = 3.5 trillion unique URLs (enough for 5 years at 100M/day)
- OR random 7-char Base62 with collision check
'''
print(architecture)
print('Key design decisions:')
print(' - Read/Write split: separate services for scalability')
print(' - Cache-aside pattern: read from Redis, fallback to DB')
print(' - 301 vs 302 redirect: 301 cached by browser (less load), 302 always hits server (analytics)')Шаг O: оптимизация и устранение узких мест
Этап оптимизации устраняет узкие места и позволяет масштабировать систему. Для сервиса сокращения URL основные проблемы таковы: задержка перенаправления (разместите Редис ближе к пользователям, используя CDN или пограничные кэши), уникальность псевдонимов при большом масштабе (используйте централизованный сервер выдачи идентификаторов или хеширование с обнаружением коллизий) и узкое место при записи в БД (пакетные или асинхронные записи через очередь).
Явно обсуждайте компромиссы: перенаправления 301 снижают нагрузку на сервер, но ухудшают точность аналитики; перенаправления 302 можно отслеживать, но они увеличивают задержку. Кэширование с длительным сроком действия снижает нагрузку на БД, но может привести к использованию устаревших URL. Демонстрация понимания этих противоречий говорит о мышлении опытного специалиста.
# Key optimisation discussion points
optimisations = {
'Read latency': [
'Redis cluster in multiple regions (CDN-edge caching)',
'301 redirect for non-tracked URLs (client caches)',
'302 redirect when click analytics are needed',
],
'Write throughput': [
'Async writes: accept request, enqueue to Kafka, batch-commit to DB',
'Ticket server (centralised ID generator) to avoid UUID collision',
'Or: hash(user_id + timestamp + random) with retry on collision',
],
'Availability': [
'Multi-AZ PostgreSQL with automatic failover',
'Redis Sentinel or Redis Cluster for HA cache',
'Health checks + circuit breaker on each service',
],
'Storage': [
'Partition urls table by hash(alias) for horizontal scaling',
'Archive expired URLs to cold storage (S3)',
],
}
for area, points in optimisations.items():
print(f'{area}:')
for p in points: print(f' - {p}')
print()Генерация псевдонимов: кодирование Base62
Важная техническая деталь сервисов сокращения URL — генерация коротких уникальных псевдонимов. Стандартный подход: взять целочисленный ID с автоинкрементом из базы данных и закодировать его в Base62 (цифры 0–9, буквы a–z и A–Z). Строка Base62 длиной 7 символов может представить 62^7 ≈ 3,5 триллиона уникальных URL — этого достаточно на десятилетия при 100 млн URL в день.
Такой подход не приводит к коллизиям (каждый ID уникален) и создаёт короткие строки, безопасные для URL. Сопоставление ID→псевдоним детерминировано и обратимо. Компромисс заключается в том, что последовательные ID создают предсказуемые псевдонимы (это проблема безопасности). Перемешайте алфавит Base62 или используйте смещение счётчика, чтобы снизить предсказуемость.
import string
BASE62_CHARS = string.digits + string.ascii_lowercase + string.ascii_uppercase
BASE = 62
def encode_base62(num):
if num == 0:
return BASE62_CHARS[0]
result = ''
while num:
result = BASE62_CHARS[num % BASE] + result
num //= BASE
return result
def decode_base62(s):
result = 0
for c in s:
result = result * BASE + BASE62_CHARS.index(c)
return result
# Generate aliases for IDs 1, 100, 1000, 10^9
for id_ in [1, 100, 1000, 1_000_000, 10**9, 3_521_614_606207]:
alias = encode_base62(id_)
decoded = decode_base62(alias)
print(f'ID {id_:20,} => alias "{alias}" (len={len(alias)}) => decoded={decoded}')Применение RADIO при проектировании ленты Твиттера
Кратко применим RADIO к проектированию новостной ленты, подобной ленте Твиттера, чтобы показать универсальность структуры:
- R: пользователи публикуют записи, подписываются на других пользователей и видят ленту записей тех, на кого подписаны. Масштаб: 300 млн пользователей, 500 млн записей в день, лента должна загружаться за <2 с.
- A:
POST /tweets,GET /feed,GET /timeline/{user_id} - D: таблица
tweets(id, user_id, content, created_at); таблицаfollows(follower_id, followee_id); кэш ленты для каждого пользователя - I: служба веерного распространения записывает новые записи в ленты подписчиков (предварительно вычисленные); Кассандра для таблиц подписок и записей с преобладанием операций записи; Редис для кэшей лент
# Fan-out on Write vs Fan-out on Read trade-off
fan_out_strategies = {
'Fan-out on Write (Push)': {
'How': 'When user A posts, immediately write to all followers feeds',
'Pro': 'O(1) feed read — feed is precomputed',
'Con': 'Celebrities with 10M followers => 10M writes per post; very slow write',
'Best for': 'Users with few followers (regular users)',
},
'Fan-out on Read (Pull)': {
'How': 'When user reads feed, query followees tweets and merge',
'Pro': 'Writes are fast (one DB write per tweet)',
'Con': 'Feed read is slow: must query all N followees',
'Best for': 'Celebrity accounts (few reads per post)',
},
'Hybrid': {
'How': 'Push for regular users, pull for celebrities',
'Pro': 'Balances read and write cost',
'Con': 'More complex implementation',
'Best for': 'Production systems like Twitter',
},
}
for strategy, details in fan_out_strategies.items():
print(f'{strategy}:')
for k, v in details.items(): print(f' {k}: {v}')
print()Распространённые ошибки на собеседованиях по проектированию систем
Избегайте этих распространённых ошибок, из-за которых собеседования по проектированию систем часто идут наперекосяк:
- Переход к решениям без уточнений: рисование блоков до прояснения требований говорит о слабых инженерных привычках
- Отсутствие оценок: проектирование без понимания масштаба превращается в гадание
- Избыточное проектирование: проектирование на 1 миллиард пользователей, когда в условии указано 10 000, тратит время собеседования
- Отсутствие компромиссов: у каждого выбора есть преимущества и недостатки; если не упоминать их, это говорит о поверхностном понимании
- Молчание: интервьюерам нужно слышать ход Ваших мыслей; проговаривайте решения по мере их принятия
# Checklist: before you stop talking, verify you covered:
checklist = [
'[ ] Asked clarifying questions about scale and constraints',
'[ ] Made capacity estimates (RPS, storage, bandwidth)',
'[ ] Defined the API surface clearly',
'[ ] Described the data model and storage choices',
'[ ] Drew a high-level architecture with named components',
'[ ] Identified the main bottleneck and proposed a solution',
'[ ] Discussed at least one trade-off explicitly',
'[ ] Verified the design meets the stated requirements',
]
for item in checklist:
print(item)Быстрая проверка
Проверьте, насколько Вы поняли концепции «Структуры данных и алгоритмы — подготовки к собеседованию по программированию» из этого урока.
Итоги урока
В этом уроке Вы узнали: фреймворк RADIO структурирует собеседования по проектированию систем по разделам «Требования», API, «Модель данных», «Инфраструктура» и «Оптимизация», всегда нужно уточнять функциональные и нефункциональные требования и оценивать ёмкость системы до предложения варианта проектирования, а также явно обсуждать компромиссы — у каждого проектного решения есть преимущества и недостатки, которые интервьюеры ожидают услышать от Вас. Далее мы рассмотрим, как выбирать между SQL- и NoSQL-хранилищами на основе шаблонов доступа, согласованности и масштаба.
Часто задаваемые вопросы
Урок «Структура собеседования по проектированию систем» бесплатный?
Да — полный текст урока «Структура собеседования по проектированию систем» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DSA Interview Prep, подпишись на CoddyKit PRO. Курс DSA Interview Prep содержит 4 уроков всего.
Чему я научусь в уроке «Структура собеседования по проектированию систем»?
Разберите пятишаговую структуру RADIO — Requirements, API, Data, Infrastructure, Optimise — и потренируйтесь применять её к сокращателю URL Ты практикуешь DSA Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DSA Interview Prep?
Предыдущий опыт не требуется. DSA Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Структура собеседования по проектированию систем»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DSA Interview Prep?
Да. Каждый урок DSA Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Структура собеседования по проектированию систем
- Масштабируемое хранение данных: SQL и NoSQL
- Кэширование, CDN и балансировка нагрузки
- Проектирование ограничителя частоты и ленты Twitter