Schemat rozmowy technicznej o projektowaniu systemów
Przejść przez pięcioetapowy schemat RADIO (Requirements, API, Data, Infrastructure, Optimise) i przećwiczyć jego zastosowanie do skracacza adresów URL
Schemat rozmowy technicznej o projektowaniu systemów to bezpłatna lekcja Coding Interview Prep na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Coding Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Dlaczego projektowanie systemów ma znaczenie podczas rozmów kwalifikacyjnych
Rozmowy kwalifikacyjne dotyczące projektowania systemów sprawdzają umiejętność myślenia na dużą skalę — jak zaprojektowaliby Państwo Twitter, YouTube lub skracacz adresów URL dla miliardów użytkowników? W przeciwieństwie do zadań programistycznych z jedną poprawną odpowiedzią projektowanie systemów ma otwarty charakter: trzeba podejmować decyzje dotyczące kompromisów i uzasadniać je. Na stanowiskach senior i staff na tę część rozmowy przeznacza się wyłącznie 30–45 minut.
Osoby prowadzące rozmowę oceniają, czy potrafią Państwo doprecyzować wymagania, oszacować obciążenie, zaproponować architekturę wysokiego poziomu, omówić kluczowe komponenty oraz przedstawić kompromisy — a przy tym jasno się komunikować. Ustrukturyzowane podejście zapobiega chaotycznym dygresjom i gwarantuje omówienie wszystkich krytycznych aspektów.
# 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)Przegląd frameworka RADIO
Framework RADIO zapewnia powtarzalną, pięcioetapową strukturę na potrzeby każdej rozmowy kwalifikacyjnej dotyczącej projektowania systemów:
- R — Wymagania: funkcjonalne i niefunkcjonalne
- A — Projektowanie API: jakie operacje udostępnia system?
- D — Model danych: jakie dane są przechowywane i w jaki sposób?
- I — Infrastruktura: komponenty wysokiego poziomu (serwery, kolejki, pamięci podręczne)
- O — Optymalizacja: wąskie gardła, buforowanie, sharding, replikacja
Należy zawsze przechodzić przez te kroki w podanej kolejności, ale wracać do wcześniejszych etapów i je doprecyzowywać, gdy pojawią się nowe informacje. Na każdą fazę należy przeznaczyć mniej więcej tyle samo czasu. Nie należy od razu rysować schematu komponentów bez wcześniejszego doprecyzowania wymagań.
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')Krok R: Doprecyzowanie wymagań
Nie należy rozpoczynać projektowania bez doprecyzowania wymagań. Należy zapytać o wymagania funkcjonalne (co robi system) oraz wymagania niefunkcjonalne (skalę, opóźnienia i dostępność). W przypadku skracacza adresów URL:
- Funkcjonalne: skracanie adresu URL, przekierowanie do oryginalnego adresu URL, opcjonalna obsługa własnych aliasów i czasu wygaśnięcia
- Niefunkcjonalne: ile adresów URL dziennie? Czy przeważają odczyty, czy zapisy? Jakie są wymagania dotyczące dostępności (99.9% czy 99.99%)? Jakie opóźnienie jest akceptowalne?
Jawne określenie założeń świadczy o dojrzałości. Osoby prowadzące rozmowę często przedstawiają celowo nieprecyzyjne wymagania, aby sprawdzić, czy zadają Państwo właściwe pytania. Dwie minuty poświęcone na doprecyzowanie pozwalają uniknąć projektowania niewłaściwego systemu.
# 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)Krok R: Szacowanie zgrubne
Po określeniu wymagań należy oszacować przepustowość. Pokazuje to, że potrafią Państwo rozumować o skali przed zaproponowaniem rozwiązania. Kluczowe wartości do wyznaczenia to: liczba żądań na sekundę (RPS), ilość pamięci masowej potrzebnej dziennie lub rocznie, przepustowość sieci oraz pamięć wymagana do buforowania.
Należy używać zaokrąglonych wartości i swobodnie stosować przybliżenia. Osoby prowadzące rozmowę zwracają uwagę na rząd wielkości, a nie na dokładne liczby. Przykład dla skracacza adresów URL: 100M zapisów/dzień ÷ 86400 ≈ 1160 zapisów/s. 10B odczytów/dzień ÷ 86400 ≈ 115K odczytów/s. Każdy rekord URL ma około 500 bajtów: 100M × 500B = 50 GB/dzień, 18 TB/rok.
# 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}')Krok A: Projektowanie API
Należy zdefiniować zakres API — operacje udostępniane klientom i usługom wewnętrznym. Trzeba jasno określić metodę HTTP, ścieżkę endpointu, parametry żądania oraz format odpowiedzi. Stanowi to podstawę reszty projektu: wszystkie pozostałe elementy istnieją po to, aby implementować te interfejsy API.
W przypadku skracacza adresów URL dwa podstawowe interfejsy API to: (1) POST /shorten — tworzenie krótkiego adresu URL, (2) GET /{alias} — przekierowanie. Opcjonalnie: DELETE /{alias} — usunięcie, GET /{alias}/stats — dane analityczne. Należy określić kody odpowiedzi (201 Created, 301 Redirect, 404 Not Found).
# 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()Krok D: Model danych
Model danych definiuje, jakie dane są przechowywane i w jaki sposób. Należy zidentyfikować podstawowe encje i ich atrybuty. W przypadku skracacza adresów URL może to być tabela urls z kolumnami alias (klucz główny), long_url, created_at, expires_at oraz user_id. Opcjonalnie można dodać tabelę clicks na potrzeby analityki.
Wybór właściwego typu magazynu ma kluczowe znaczenie: relacyjna baza danych sprawdza się w przypadku ustrukturyzowanych danych i złożonych zapytań; magazyn klucz-wartość (Redis, DynamoDB) umożliwia wyszukiwanie aliasów w czasie O(1) na dużą skalę; magazyn obiektów (S3) służy do przechowywania dużych obiektów binarnych. W przypadku skracacza adresów URL magazyn klucz-wartość z kluczem będącym aliasem jest idealny do odczytów, a relacyjna baza danych — do zapisów i zarządzania.
# 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')Krok I: Infrastruktura wysokiego poziomu
Należy naszkicować wysokopoziomową infrastrukturę: które serwery odpowiadają za poszczególne zadania, jak dane przepływają między komponentami oraz z jakich usług zewnętrznych system korzysta. W przypadku skracacza adresów URL działającego na dużą skalę:
- Moduł równoważenia obciążenia: rozdziela ruch między repliki usług zapisu i odczytu
- Usługa zapisu: generuje alias, sprawdza jego unikalność, zapisuje dane w bazie i unieważnia pamięć podręczną
- Usługa odczytu/przekierowań: najpierw sprawdza pamięć podręczną Redis, a w przypadku braku danych odwołuje się do bazy
- Relacyjna baza danych: źródło prawdy (z replikami do odczytu)
- Klaster Redis: przechowuje w pamięci podręcznej najczęściej używane mapowania adresów URL, zapewniając odczyty w czasie poniżej milisekundy
# 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)')Krok O: Optymalizacja i obsługa wąskich gardeł
Faza optymalizacji obejmuje usuwanie wąskich gardeł i skalowanie systemu. W przypadku skracacza adresów URL najważniejsze kwestie to: opóźnienie przekierowań (umieszczenie Redis blisko użytkowników za pomocą CDN lub pamięci podręcznych na brzegu sieci), unikalność aliasów na dużą skalę (użycie centralnego serwera przydzielającego identyfikatory lub haszowania z wykrywaniem kolizji) oraz wąskie gardło związane z zapisami do bazy (zapisy zbiorcze lub asynchroniczne z użyciem kolejki).
Należy jawnie omówić kompromisy: przekierowania 301 zmniejszają obciążenie serwera, ale obniżają dokładność analityki; przekierowania 302 można śledzić, ale zwiększają opóźnienia. Długie czasy wygaśnięcia w pamięci podręcznej zmniejszają obciążenie bazy, ale grożą nieaktualnymi adresami URL. Pokazanie, że rozumieją Państwo te zależności, sygnalizuje myślenie na poziomie senior.
# 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()Generowanie aliasów: kodowanie Base62
Istotnym szczegółem technicznym skracaczy adresów URL jest sposób generowania krótkich, unikalnych aliasów. Standardowe podejście polega na użyciu automatycznie zwiększanego identyfikatora całkowitego z bazy danych i zakodowaniu go w systemie Base62 (cyfry 0-9, litery a-z, A-Z). Siedmioznakowy ciąg Base62 może reprezentować 62^7 ≈ 3,5 biliona unikalnych adresów URL — wystarczająco dużo na dziesięciolecia przy 100M adresów dziennie.
Takie rozwiązanie eliminuje kolizje (każdy identyfikator jest unikalny) i tworzy krótkie ciągi bezpieczne dla adresów URL. Mapowanie ID→alias jest deterministyczne i odwracalne. Wadą jest to, że sekwencyjne identyfikatory tworzą przewidywalne aliasy (co stanowi zagrożenie bezpieczeństwa). Przetasowanie alfabetu Base62 lub użycie przesunięcia licznika zmniejsza przewidywalność.
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}')Zastosowanie RADIO do projektowania kanału aktualności w stylu Twittera
Prześledźmy krótko zastosowanie RADIO do projektowania kanału aktualności w stylu Twittera, aby pokazać, że framework można uogólnić:
- R: Użytkownicy publikują tweety, obserwują innych i widzą kanał tweetów osób, które obserwują. Skala: 300M użytkowników, 500M tweetów dziennie, kanał musi ładować się w czasie <2 s.
- A:
POST /tweets,GET /feed,GET /timeline/{user_id} - D: tabela
tweets(id, user_id, content, created_at); tabelafollows(follower_id, followee_id); pamięć podręczna kanału dla każdego użytkownika - I: Usługa fan-out zapisuje nowe tweety w kanałach obserwujących (wstępnie obliczonych); Cassandra dla tabel obserwowania i tweetów intensywnie obciążonych zapisami; Redis dla pamięci podręcznych kanałów
# 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()Częste błędy podczas rozmów rekrutacyjnych dotyczących projektowania systemów
Należy unikać tych częstych błędów, które mogą pogrążyć rozmowę rekrutacyjną dotyczącą projektowania systemów:
- Przechodzenie od razu do rozwiązań: rysowanie schematów przed doprecyzowaniem wymagań sygnalizuje złe nawyki inżynierskie
- Brak estymacji: projektowanie bez znajomości skali jest zgadywaniem
- Przekombinowanie: projektowanie dla 1 miliarda użytkowników, gdy treść zadania mówi o 10 000, marnuje czas rozmowy
- Brak analizy kompromisów: każda decyzja ma zalety i wady; nieomówienie ich sugeruje powierzchowne zrozumienie tematu
- Milczenie: osoby rekrutujące muszą poznać tok rozumowania; należy opisywać podejmowane decyzje na bieżąco
# 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)Szybki test
Sprawdź swoją znajomość zagadnień Data Structures & Algorithms — Coding Interview Prep z tej lekcji.
Podsumowanie lekcji
W tej lekcji dowiedzieli się Państwo, że: framework RADIO porządkuje rozmowy rekrutacyjne dotyczące projektowania systemów według obszarów Requirements, API, Data Model, Infrastructure i Optimise, przed zaproponowaniem projektu należy zawsze doprecyzować wymagania funkcjonalne i niefunkcjonalne oraz oszacować zapotrzebowanie na zasoby, a także wyraźnie omawiać kompromisy — każda decyzja projektowa ma zalety i wady, które osoby rekrutujące oczekują usłyszeć. W następnej części omówimy wybór między magazynami SQL i NoSQL na podstawie wzorców dostępu, spójności i skali.
Często zadawane pytania
Czy lekcja „Schemat rozmowy technicznej o projektowaniu systemów” jest bezpłatna?
Tak — pełny tekst „Schemat rozmowy technicznej o projektowaniu systemów” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Coding Interview Prep, przejdź na CoddyKit PRO. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Schemat rozmowy technicznej o projektowaniu systemów”?
Przejść przez pięcioetapowy schemat RADIO (Requirements, API, Data, Infrastructure, Optimise) i przećwiczyć jego zastosowanie do skracacza adresów URL Ćwiczysz Coding Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Coding Interview Prep?
Nie wymagamy żadnego doświadczenia. Coding Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.
Ile czasu zajmuje lekcja „Schemat rozmowy technicznej o projektowaniu systemów”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Coding Interview Prep?
Tak. Każda lekcja Coding Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Schemat rozmowy technicznej o projektowaniu systemów
- Skalowalne przechowywanie danych: SQL a NoSQL
- Buforowanie, CDN-y i równoważenie obciążenia
- Projektowanie ogranicznika przepustowości i kanału Twittera