Основы и подход к задаче¶
System design на собесе — это не «знаешь ли ты Kafka», а «умеешь ли ты думать по шагам». Здесь мы фиксируем универсальный каркас, по которому ты будешь проходить любую задачу: TinyURL, Twitter, Uber или загадочный «спроектируй нам сервис рекламных кампаний».
Что вообще такое system design¶
🧩 Простыми словами. Это инженерный планинг до кодинга. Берёшь требования, прикидываешь нагрузку, рисуешь компоненты (API, БД, кэш, очередь), показываешь как они общаются, и обосновываешь почему именно так. Цель — чтобы система делала то, что нужно, выживала под нагрузкой, и переживала отказы.
⚙️ Под капотом. Системный дизайн всегда про trade-offs:
- Latency vs throughput. Хочешь быстро на запрос или много запросов в сек?
- Consistency vs availability. Готов ждать пока все реплики синхронизируются?
- Cost vs reliability. Один датацентр дешевле, два — надёжнее.
- Complexity vs flexibility. Монолит проще, микросервисы гибче (и больнее).
Хороший ответ всегда явно проговаривает trade-off: «беру eventual consistency, потому что лента — это не банковский баланс, расхождение в 1 секунду нормальное».
💥 Зачем это нужно. В работе — чтобы не переписывать сервис через полгода, когда он не выдержал нагрузку. На собесе — потому что это основной сигнал зрелости инженера.
Каркас ответа: 4 шага¶
graph LR
R[1. Уточнить<br/>требования] --> A[2. Дизайн API]
A --> D[3. Схема данных]
D --> S[4. Масштаб<br/>и узкие места]
S -.iterate.-> R
Шаг 1. Уточнить требования (5–10 минут)¶
Самая важная часть. Кандидаты, которые сразу рисуют, проваливают собес даже если архитектура нарисована правильно — потому что не показали инженерное мышление.
Functional requirements (что система делает):
- Какие действия пользователь выполняет? (создать tweet, открыть feed, лайкнуть)
- Какие гарантии? (доставка сообщения, порядок, дедупликация)
- Что в скоупе, а что нет? («подписки и DM» — да; «реклама» — нет)
Non-functional requirements (как хорошо она это делает):
- DAU/MAU — сколько активных пользователей в день/месяц.
- RPS — сколько запросов в секунду на API. Read RPS и Write RPS отдельно.
- Latency — p50/p99 каких эндпоинтов. Feed = 200 ms p99, поиск = 500 ms.
- Consistency — strong или eventual? (см. consistency.md)
- Availability — 99.9% (8.7 ч даунтайма в год) или 99.99% (52 минуты)?
- Storage — сколько ГБ/ТБ растёт за год. Считаем
users × items × bytes. - География — один регион или multi-region.
Capacity estimation на коленке:
Пиши ОЦЕНКУ, не точные цифры. Никто не ждёт от тебя 100% правильного ответа, ждут логику.
Шаг 2. Дизайн API¶
Контракт между клиентом и сервером. Тут показываешь, что понимаешь предметную область.
POST /tweets
Body: { text: string, attachments?: []url }
Response: { id: int64, created_at: ts }
GET /feed?cursor=...&limit=20
Response: { items: [...], next_cursor: string }
POST /tweets/:id/likes (idempotent — повтор не дублирует лайк)
Чек-лист для каждого эндпоинта:
- HTTP метод (или gRPC unary/stream).
- Контракт ввода/вывода.
- Идемпотентность для write-операций (см. resilience.md).
- Авторизация: кто может звать.
- Rate limit: есть ли (и каков).
Шаг 3. Схема данных¶
Здесь решаешь: какие сущности, в какой БД, с какими индексами.
tweets(id PK, user_id, text, created_at, ...)
index on (user_id, created_at DESC) -- для профиля
followers(user_id, follower_id)
index on (user_id), index on (follower_id)
likes(tweet_id, user_id, created_at) -- дублирующая (tweet+user) PK
Минимально:
- Что primary key, какие unique constraints.
- Какие индексы (для каких запросов).
- Что в SQL, что в NoSQL/KV/blob.
- Какая денормализация (для скорости — да, для согласованности — больно).
Шаг 4. Масштаб и узкие места¶
Самая вкусная часть для middle/senior. Разворачиваешь схему в распределённую систему.
graph TB
User --> CDN[CDN: статика]
User --> LB[Load Balancer]
LB --> APIa[API instance 1]
LB --> APIb[API instance 2]
APIa --> Cache[(Redis cache)]
APIa --> DB[(Postgres primary)]
APIa --> Q[Kafka events]
DB --> R1[Read replica]
DB --> R2[Read replica]
Q --> Worker[Async worker:<br/>fan-out, counters]
Worker --> DB
Что обязательно проговорить:
- LB перед API (round-robin, healthcheck).
- Cache для горячих ключей (Redis cluster, TTL).
- Read replicas или sharding для роста read RPS.
- Очередь для асинхронных вещей (counter, fan-out, email).
- Bottlenecks: где первое узкое место (обычно — write на основной БД).
- Failure modes: что если упал кэш? что если очередь забилась?
🛠 Применение в Go-проекте¶
В Go-сервисе живая архитектура примерно такая:
// cmd/api/main.go
func main() {
cfg := loadConfig()
db, _ := pgxpool.New(ctx, cfg.PostgresDSN)
rdb := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
kw := kafka.NewWriter(cfg.KafkaBrokers, "tweet-events")
h := &Handler{
repo: &Repo{db: db},
cache: &Cache{rdb: rdb, ttl: 5 * time.Minute},
bus: &Bus{w: kw},
}
srv := &http.Server{
Addr: cfg.Addr,
Handler: withMiddleware(h.Routes()),
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
Сюда дальше добавляются: middleware (request_id, metrics, auth), graceful shutdown, circuit breakers на внешние вызовы, tracing. Это и есть «правильный шаблон Go-сервиса в проде».
🔥 Пример сценария: «спроектируй сервис коротких ссылок»¶
sequenceDiagram
participant U as User
participant LB
participant API
participant C as Redis
participant DB as Postgres
U->>LB: POST /shorten {url}
LB->>API: forward
API->>DB: insert + return short_code
API->>C: SET code → url, TTL 1h
API-->>U: 201 {short: bm.ru/abc}
U->>LB: GET /abc
LB->>API: forward
API->>C: GET abc
alt cache hit
C-->>API: url
else miss
API->>DB: select where code=abc
DB-->>API: url
API->>C: SET abc → url
end
API-->>U: 302 Redirect
Это уже почти полный ответ на собесе, разверни его до уровня подходящего тебе: case-studies.md разбирает TinyURL подробнее.
❌ Типичные ошибки¶
- Сразу рисуют схему, не уточнив RPS и storage. → Интервьюер ставит галочку «не задаёт вопросы».
- Перегружают первую итерацию. Не нужен Kafka на 100 RPS, не нужен sharding на 10 ГБ. Ставь только когда обоснуешь.
- «Возьмём NoSQL» без объяснения. → Какой именно? Почему не Postgres? Что за query patterns?
- Игнорят failure modes. Что если упадёт основной мастер? Replica lag — что читатели увидят старые данные?
- Не считают capacity. «Будет много пользователей». Сколько?
- Не упоминают monitoring. Без RED-метрик это не production-ready.
🤖 Что спрашивает AI-ментор¶
- С чего ты начинаешь ответ на system design?
- Какие 5 nfr (нефункциональных требований) ты должен уточнить в первые 10 минут?
- Что входит в capacity estimation для read-heavy сервиса?
- Зачем рисовать API до схемы данных?
- Назови три типовых bottleneck'а в монолитном веб-сервисе и как их обходить.
📊 Уровни глубины¶
L1 (junior). Можешь нарисовать API + Postgres + Redis + LB. Понимаешь, что такое RPS, latency, индекс. Не путаешь cache и persistence.
L2 (middle). Делаешь capacity estimation. Знаешь когда добавить read replicas vs sharding. Различаешь sync vs async вызов. Помнишь про идемпотентность.
L3 (senior). Сначала спрашиваешь про SLA и бюджет. Сравниваешь варианты с trade-offs (sharding by hash vs by range). Знаешь свои failure modes и плановые incidents (replica lag, hot key, schema migration). Можешь сразу сказать «это займёт 3 quarter, упрёмся в команду из 4 человек».
📝 Подумай¶
- Тебе говорят «спроектируй сервис, где пользователи постят короткие видео». Сформулируй 5 уточняющих вопросов в первые 5 минут.
- У тебя 50 млн DAU, каждый постит в среднем 1 видео в неделю по 10 МБ. Прикинь storage за год. Где будешь хранить?
- Какие три компонента архитектуры обязательно появятся в ответе на любую задачу middle-уровня?
Ответ
- (a) read- или write-heavy? (b) сколько RPS на загрузку и просмотр? (c) какой acceptable latency на старт воспроизведения? (d) длина видео и максимальный размер? (e) география — один регион или мульти?
- 50e6 × 1/7 × 7 × 52 × 10 МБ ≈ 2.6 ПБ/год. В Postgres такое не положишь — blob storage (S3/MinIO), а в Postgres только метаданные (id, owner, url).
- Load balancer + application + database. Дальше — cache + queue, но эти три обязательны.
Дальше: scaling.md — как растягивать сервис под нагрузку.