Senior Go Interview Prep - Core Go: https://go.vbloher.org/docs/01-core-go/ - Механика defer в Go: https://go.vbloher.org/docs/01-core-go/defer/ - Встраивание структур и интерфейсов (Embedding): https://go.vbloher.org/docs/01-core-go/embedding/ - Ошибки в Go: error, wrapping, errors.Is/As/Join: https://go.vbloher.org/docs/01-core-go/errors/ - Дженерики в Go (1.18+): https://go.vbloher.org/docs/01-core-go/generics/ - Интерфейсы в Go: https://go.vbloher.org/docs/01-core-go/interfaces/ - Устройство map в Go: https://go.vbloher.org/docs/01-core-go/maps/ - panic / recover: механика, раскрутка стека и runtime-паники: https://go.vbloher.org/docs/01-core-go/panic-recover/ - Указатели в Go: https://go.vbloher.org/docs/01-core-go/pointers/ - Рефлексия в Go (reflect): https://go.vbloher.org/docs/01-core-go/reflection/ - Внутреннее устройство слайсов в Go: https://go.vbloher.org/docs/01-core-go/slices/ - Строки, руны и байты в Go: https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/ - Система типов Go: defined types, alignment, memory layout: https://go.vbloher.org/docs/01-core-go/type-system/ - Concurrency: https://go.vbloher.org/docs/02-concurrency/ - sync/atomic: https://go.vbloher.org/docs/02-concurrency/atomic/ - Буферизованные vs небуферизованные каналы: https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/ - Канал vs Mutex: когда что выбрать: https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/ - Каналы: устройство hchan: https://go.vbloher.org/docs/02-concurrency/channels/ - Утечки горутин, дедлоки, livelock, starvation: https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/ - sync.Cond: https://go.vbloher.org/docs/02-concurrency/cond/ - context: https://go.vbloher.org/docs/02-concurrency/context/ - Горутины: жизненный цикл, стоимость, стек: https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/ - sync.Mutex и sync.RWMutex: https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/ - sync.Once: https://go.vbloher.org/docs/02-concurrency/once/ - Паттерны конкурентности: https://go.vbloher.org/docs/02-concurrency/patterns/ - Race Detector (гонки данных и -race): https://go.vbloher.org/docs/02-concurrency/race-detector/ - Планировщик GMP: https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/ - select: https://go.vbloher.org/docs/02-concurrency/select/ - sync.WaitGroup: https://go.vbloher.org/docs/02-concurrency/waitgroup/ - Runtime и память: https://go.vbloher.org/docs/03-runtime-memory/ - Паттерны аллокаций и снижение давления на GC: https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/ - Escape Analysis: когда переменная убегает в кучу: https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/ - Сборщик мусора Go: concurrent tri-color mark-sweep: https://go.vbloher.org/docs/03-runtime-memory/gc/ - Тюнинг GC: GOGC и GOMEMLIMIT: https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/ - GOMAXPROCS: параллелизм планировщика и проблема контейнеров: https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/ - Утечки горутин (goroutine leaks): https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/ - Утечки памяти в Go (несмотря на GC): https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/ - Модель памяти Go (Go Memory Model): happens-before и синхронизация: https://go.vbloher.org/docs/03-runtime-memory/memory-model/ - pprof: профилирование CPU, памяти и блокировок в Go: https://go.vbloher.org/docs/03-runtime-memory/pprof/ - Execution Tracer и runtime/trace: тайминги вместо агрегатов: https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/ - Стек vs Куча: где живут данные в Go: https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/ - Тестирование: https://go.vbloher.org/docs/04-testing/ - testify, assert/require и golden files: https://go.vbloher.org/docs/04-testing/assertions-testify/ - Бенчмарки в Go: https://go.vbloher.org/docs/04-testing/benchmarks/ - Покрытие, -race и флаки-тесты: https://go.vbloher.org/docs/04-testing/coverage-race/ - Нативный fuzzing в Go (1.18+): https://go.vbloher.org/docs/04-testing/fuzzing/ - Интеграционные тесты, testcontainers-go, TestMain: https://go.vbloher.org/docs/04-testing/integration-testcontainers/ - Моки, стабы и тестируемость: https://go.vbloher.org/docs/04-testing/mocks/ - Table-driven тесты, subtests и параллельность: https://go.vbloher.org/docs/04-testing/table-driven/ - Backend: https://go.vbloher.org/docs/05-backend/ - Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты: https://go.vbloher.org/docs/05-backend/auth-authz/ - Graceful Shutdown HTTP/gRPC сервера в Go: https://go.vbloher.org/docs/05-backend/graceful-shutdown/ - gRPC: типы RPC, интерсепторы, контекст, метаданные, error model: https://go.vbloher.org/docs/05-backend/grpc/ - JWT (JSON Web Token): https://go.vbloher.org/docs/05-backend/jwt/ - Middleware-паттерн в Go: https://go.vbloher.org/docs/05-backend/middleware/ - net/http: Server, Handler, ServeMux, таймауты, Client и контекст: https://go.vbloher.org/docs/05-backend/net-http/ - OAuth2: роли, grant types, OIDC, токены и типовые ошибки: https://go.vbloher.org/docs/05-backend/oauth2/ - OpenAPI/Swagger, code generation, contract-first vs code-first, валидация: https://go.vbloher.org/docs/05-backend/openapi/ - Protocol Buffers: схемы, wire format, эволюция и совместимость: https://go.vbloher.org/docs/05-backend/protobuf/ - REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки: https://go.vbloher.org/docs/05-backend/rest/ - Сети и протоколы: https://go.vbloher.org/docs/06-networking/ - Пулы соединений: http.Transport, БД, утечки: https://go.vbloher.org/docs/06-networking/connection-pooling/ - DNS: записи, резолвинг, кэширование, DNS в Go: https://go.vbloher.org/docs/06-networking/dns/ - Версии HTTP: 1.1, 2, 3: https://go.vbloher.org/docs/06-networking/http-versions/ - TCP/IP: модель, транспорт и что важно бэкендеру: https://go.vbloher.org/docs/06-networking/tcp-ip/ - TLS: handshake, сертификаты, mTLS, производительность: https://go.vbloher.org/docs/06-networking/tls/ - UDP и надёжность поверх UDP: https://go.vbloher.org/docs/06-networking/udp/ - WebSocket: upgrade, фреймы, масштабирование: https://go.vbloher.org/docs/06-networking/websocket/ - Базы данных: https://go.vbloher.org/docs/07-databases/ - Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer: https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/ - Взаимоблокировки (Deadlocks) в PostgreSQL: https://go.vbloher.org/docs/07-databases/deadlocks/ - Индексы в PostgreSQL: https://go.vbloher.org/docs/07-databases/indexes/ - Уровни изоляции транзакций в PostgreSQL: https://go.vbloher.org/docs/07-databases/isolation-levels/ - MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat: https://go.vbloher.org/docs/07-databases/mvcc/ - Обзор NoSQL и Redis: https://go.vbloher.org/docs/07-databases/nosql-redis/ - Партиционирование таблиц в PostgreSQL: https://go.vbloher.org/docs/07-databases/partitioning/ - Архитектура PostgreSQL: https://go.vbloher.org/docs/07-databases/postgresql-architecture/ - Планирование и оптимизация запросов в PostgreSQL: https://go.vbloher.org/docs/07-databases/query-planning/ - Репликация в PostgreSQL: https://go.vbloher.org/docs/07-databases/replication/ - Шардирование (горизонтальное масштабирование): https://go.vbloher.org/docs/07-databases/sharding/ - Транзакции в PostgreSQL и Go (database/sql, pgx): https://go.vbloher.org/docs/07-databases/transactions/ - Распределённые системы: https://go.vbloher.org/docs/08-distributed-systems/ - CAP теорема: https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/ - Circuit Breaker: https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/ - Консенсус и Raft: репликация состояния в присутствии отказов: https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/ - Модели согласованности: https://go.vbloher.org/docs/08-distributed-systems/consistency/ - Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once: https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/ - Eventual Consistency: https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/ - Идемпотентность в распределённых системах: https://go.vbloher.org/docs/08-distributed-systems/idempotency/ - Apache Kafka: https://go.vbloher.org/docs/08-distributed-systems/kafka/ - Transactional Outbox: https://go.vbloher.org/docs/08-distributed-systems/outbox/ - RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka: https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/ - Ретраи: backoff, jitter, budgets и идемпотентность: https://go.vbloher.org/docs/08-distributed-systems/retries/ - Saga Pattern: https://go.vbloher.org/docs/08-distributed-systems/saga/ - Observability: https://go.vbloher.org/docs/09-observability/ - Grafana: https://go.vbloher.org/docs/09-observability/grafana/ - Метрики: RED, USE, Golden Signals: https://go.vbloher.org/docs/09-observability/metrics/ - OpenTelemetry: https://go.vbloher.org/docs/09-observability/opentelemetry/ - Prometheus: https://go.vbloher.org/docs/09-observability/prometheus/ - SLI / SLO / SLA: https://go.vbloher.org/docs/09-observability/slo-sli/ - Структурированное логирование (slog): https://go.vbloher.org/docs/09-observability/structured-logging/ - Distributed Tracing: https://go.vbloher.org/docs/09-observability/tracing/ - System Design: https://go.vbloher.org/docs/10-system-design/ - Analytics Pipeline: https://go.vbloher.org/docs/10-system-design/analytics-pipeline/ - Chat System: https://go.vbloher.org/docs/10-system-design/chat/ - Фреймворк System Design интервью: https://go.vbloher.org/docs/10-system-design/framework/ - Notification Service: https://go.vbloher.org/docs/10-system-design/notification-service/ - Order Service: https://go.vbloher.org/docs/10-system-design/order-service/ - Payment Service: https://go.vbloher.org/docs/10-system-design/payment-service/ - Rate Limiter: https://go.vbloher.org/docs/10-system-design/rate-limiter/ - URL Shortener: https://go.vbloher.org/docs/10-system-design/url-shortener/ - DevOps: https://go.vbloher.org/docs/11-devops/ - CI/CD: пайплайны, стадии, стратегии деплоя: https://go.vbloher.org/docs/11-devops/cicd/ - Облака (AWS / GCP) для бэкендера: https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/ - Docker для Go-разработчика: https://go.vbloher.org/docs/11-devops/docker/ - GitHub Actions и GitLab CI: https://go.vbloher.org/docs/11-devops/github-gitlab-ci/ - Kubernetes для Go-разработчика: https://go.vbloher.org/docs/11-devops/kubernetes/ - Terraform / Infrastructure as Code: https://go.vbloher.org/docs/11-devops/terraform/ - Алгоритмы: https://go.vbloher.org/docs/12-algorithms/ - Типовые алгоритмические задачи и паттерны: https://go.vbloher.org/docs/12-algorithms/common-problems/ - Асимптотическая сложность (Big-O): https://go.vbloher.org/docs/12-algorithms/complexity/ - Структуры данных в Go: https://go.vbloher.org/docs/12-algorithms/data-structures/ - Специфика live-coding на Go: https://go.vbloher.org/docs/12-algorithms/go-specifics/ - Behavioral: https://go.vbloher.org/docs/13-behavioral/ - Конфликты, разногласия и работа со стейкхолдерами: https://go.vbloher.org/docs/13-behavioral/conflicts/ - Как проходит senior-интервью: этапы, оценка, оффер: https://go.vbloher.org/docs/13-behavioral/interview-flow/ - Лидерство и менторство: https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/ - Типовые поведенческие вопросы для Senior: https://go.vbloher.org/docs/13-behavioral/senior-questions/ > Модуль: System Design · Уровень: Senior ## TL;DR System design интервью оценивает не «знание решения», а **способ мышления**: как ты переводишь размытые требования в конкретные числа, как обосновываешь выбор компонентов и где честно проговариваешь trade-offs. Senior-кандидата отличает не количество названных технологий, а умение управлять временем, задавать уточняющие вопросы и связывать решение с бизнес-ограничениями. Работающий фреймворк на 45 минут: ``` requirements → estimations → API → data model → high-level → deep dive → scale → trade-offs (5') (5') (3') (3') (8') (12') (5') (4') ``` Главные провалы кандидатов: сразу рисуют боксы без требований, не считают цифры (QPS/storage), не делают deep dive, перечисляют технологии без обоснования и молчат про trade-offs. Senior всегда говорит «зависит от...» и явно называет, от чего именно. --- ## Теория ### Почему именно фреймворк Задача намеренно открытая («Спроектируйте Twitter»). Без структуры кандидат либо тонет в деталях одного компонента, либо растекается по верхам. Фреймворк — это контейнер для мысли: он гарантирует, что за 45 минут ты покроешь весь стек от требований до масштабирования и оставишь интервьюеру материал для оценки. Произноси шаги вслух — интервьюер должен видеть управляемый процесс, а не поток сознания. --- ### Шаг 1. Requirements (≈5 мин) Разделяй два класса требований. **Functional requirements** — что система делает (use cases): - «Пользователь публикует твит», «читает ленту», «подписывается». - Сужай scope. Скажи: «Я сфокусируюсь на трёх core-сценариях: post tweet, home timeline, follow. Notifications, search, DM — вне scope, верно?» Это сразу показывает senior-уровень: ты управляешь объёмом, а не пытаешься объять всё. **Non-functional requirements** — каким система должна быть: - **Availability vs Consistency** — что важнее? Для ленты соцсети допустима eventual consistency (твит появится через секунду — ок). Для баланса счёта — strong consistency обязательна. - **Latency** — целевой p99. «Лента должна отдаваться за < 200 ms p99». - **Throughput** — пиковый QPS. - **Durability** — допустима ли потеря данных? Для аналитических событий — да, для платежей — нет. - **Read/Write ratio** — критично для архитектуры. Соцсети обычно 100:1 ... 1000:1 в пользу чтения. - **Consistency model** — strong / eventual / read-your-writes / monotonic reads. Senior-приём: спрашивай про **масштаб бизнеса до того, как считать**. «Сколько DAU? Это стартап или Twitter-scale?» Решение для 10k и 500M пользователей — принципиально разное. --- ### Шаг 2. Estimations / back-of-the-envelope (≈5 мин) Цель — обосновать архитектурные решения числами, а не точная бухгалтерия. Считай в порядках величины. **Базовые константы для прикидок:** - В сутках ≈ **86 400 с** ≈ 10^5 с (округляй для скорости). - 1 млн запросов/сутки ≈ **12 QPS** (1e6 / 86400). - Пик обычно **2–3×** среднего. **Пример: Twitter-like, 300M DAU.** QPS чтения ленты (предположим каждый юзер читает ленту 10 раз/день): ``` reads/day = 300e6 * 10 = 3e9 avg QPS = 3e9 / 1e5 = 30 000 QPS peak QPS = ~90 000 QPS (×3) ``` QPS записи твитов (каждый постит 2 раза/день): ``` writes/day = 300e6 * 2 = 6e8 avg QPS = 6e8 / 1e5 = 6 000 QPS peak = ~18 000 QPS ``` Read/Write ≈ 30000/6000 = **5:1** на уровне QPS, но если учитывать fan-out чтений (один твит читают тысячи подписчиков) — реальная асимметрия гораздо больше → это аргумент за read-оптимизированную архитектуру (precompute timeline). **Storage** (текст твита ≈ 300 байт + метаданные ≈ 200 байт ≈ 0.5 KB): ``` твитов/день = 6e8 storage/day = 6e8 * 0.5 KB = 300 GB/day за год = 300 GB * 365 ≈ 110 TB/year (только текст) ``` Медиа меняет картину на порядки: 10% твитов с картинкой по 200 KB → 6e7 * 200 KB ≈ 12 TB/day → ~4.4 PB/year. Вывод: медиа в blob storage + CDN, текст в БД. **Bandwidth** (egress на чтение ленты, 20 твитов × 0.5 KB = 10 KB на ответ): ``` peak read QPS * payload = 90 000 * 10 KB = 900 MB/s ≈ 7.2 Gbps ``` **Memory для кэша** (горячая выборка — кэшируем активные timelines, правило 80/20): ``` храним последние 20 твитов на активного юзера, активных ~20% = 60M 60e6 * 20 * 0.5 KB = 600 GB → шардированный Redis-кластер ``` **Latency numbers every programmer should know** (порядки, держи в голове): ``` L1 cache reference ~0.5 ns Branch mispredict ~5 ns L2 cache reference ~7 ns Mutex lock/unlock ~25 ns Main memory (RAM) reference ~100 ns Compress 1 KB (Zippy/Snappy) ~3 µs Send 1 KB over 1 Gbps network ~10 µs Read 1 MB sequentially from RAM ~250 µs Round trip within same datacenter ~500 µs Read 1 MB sequentially from SSD ~1 ms Disk seek (HDD) ~10 ms Read 1 MB sequentially from HDD ~20 ms Round trip CA ↔ Netherlands ↔ CA ~150 ms ``` Практические выводы из этих чисел: - RAM ≈ в 100 000× быстрее HDD-seek → кэш в памяти оправдан почти всегда. - Cross-DC round-trip 150 ms → синхронная георепликация убивает latency; для глобальных систем — async-репликация + geo-DNS. - SSD seek ≈ 100 µs (нет механики) против HDD 10 ms → для random access всегда SSD. - Один RTT внутри DC = 0.5 ms → минимизируй число последовательных сетевых хопов (N+1 запросов). **Правило senior:** каждое число должно вести к решению. Не считай ради счёта — связывай: «900 MB/s egress → нужен CDN и кэш, один сервер не вытянет». --- ### Шаг 3. API design (≈3 мин) Опиши контракт между клиентом и системой. Это фиксирует функциональные требования в конкретные эндпоинты. ``` POST /v1/tweets body: { text, media_ids[] } auth: Bearer → 201 { tweet_id, created_at } GET /v1/timeline/home?cursor=&limit=20 → 200 { tweets[], next_cursor } POST /v1/users/{id}/follow → 204 ``` Senior-нюансы: - **Пагинация курсором, а не offset.** Offset-пагинация (`?page=5`) ломается при вставках/удалениях и деградирует на больших offset (БД сканирует все пропущенные строки). Cursor (`?after=`) — стабильный и O(1) по индексу. - **Идемпотентность записи.** `POST /tweets` с `Idempotency-Key` в заголовке → защита от двойной отправки при retry. Обязательно для платежей и заказов. - **Versioning** (`/v1/`) с самого начала. - **REST vs gRPC vs GraphQL** — REST для публичных API, gRPC для internal service-to-service (бинарный protobuf, HTTP/2, стриминг, ниже latency), GraphQL когда клиентам нужна гибкая выборка полей (мобильные, под-/over-fetching). --- ### Шаг 4. Data model (≈3 мин) Опиши ключевые сущности, связи и где они хранятся. ``` User (user_id PK, name, created_at, ...) Tweet (tweet_id PK, author_id FK, text, created_at, media_ids[]) Follow (follower_id, followee_id) -- граф подписок Timeline(user_id, tweet_id, score) -- precomputed (Redis/Cassandra) ``` Решения, которые принимаются здесь: - **Выбор ключа партиционирования.** `Tweet` шардируем по `tweet_id` (равномерно) или по `author_id` (локальность чтения твитов автора, но риск hot-partition на знаменитостях). - **Денормализация** под паттерн чтения. В NoSQL дублируем `author_name` в `Tweet`, чтобы не делать JOIN на каждом чтении ленты. - **Граф подписок** — отдельное хранилище (специализированная graph DB или просто индексированная таблица с двумя направлениями `following` и `followers`). --- ### Шаг 5. High-level architecture (≈8 мин) Рисуй сверху вниз: клиент → edge → сервисы → данные. Начинай просто, усложняй по мере обсуждения. ``` ┌─────────┐ Clients ──►│ CDN │ (статика, медиа) └────┬────┘ │ ┌────▼─────┐ │ DNS / │ (geo-routing) │ GSLB │ └────┬─────┘ │ ┌──────▼───────┐ │ Load Balancer│ (L7) └──────┬───────┘ │ ┌──────────┼──────────┐ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌────────┐ │ API GW │ │ API GW │ │ API GW │ (auth, rate-limit) └───┬────┘ └───┬────┘ └───┬────┘ └──────────┼──────────┘ ┌───────┼────────┐ ▼ ▼ ▼ ┌─────────┐┌────────┐┌──────────┐ │ Tweet ││ Timeline││ User/Graph│ (stateless services) │ Service ││ Service ││ Service │ └────┬────┘└───┬────┘└─────┬─────┘ │ │ │ ┌────▼───┐ ┌──▼────┐ ┌────▼────┐ │ Tweet │ │ Redis │ │ Graph │ │ DB │ │ Cache │ │ DB │ │(shard) │ │(shard)│ │ │ └────────┘ └───────┘ └─────────┘ │ ┌────▼─────────────────┐ │ Message Queue (Kafka) │ ──► fan-out workers ──► Timeline cache └───────────────────────┘ ``` Опиши главный data flow словами: «При публикации твита `Tweet Service` пишет в БД, кладёт событие в Kafka. Fan-out workers разбирают событие и вставляют tweet_id в timeline-кэши подписчиков. При чтении ленты `Timeline Service` отдаёт готовый список из Redis — O(1), без вычислений на лету». **Fan-out on write vs on read** — классический trade-off этой задачи: - **Fan-out on write (push):** при публикации сразу раскладываем в кэши подписчиков. Чтение мгновенное. Но твит знаменитости с 100M подписчиков = 100M записей — «celebrity problem». - **Fan-out on read (pull):** лента собирается в момент запроса из твитов тех, на кого подписан. Дёшево на запись, дорого на чтение. - **Гибрид (senior-ответ):** push для обычных юзеров, pull для знаменитостей. На чтении мёржим precomputed timeline + свежие твиты celebrities. --- ### Шаг 6. Deep dive (≈12 мин) — самая важная часть Здесь интервьюер оценивает глубину. Выбери 1–2 компонента и копай. Обычно интервьюер сам направит («давай подробнее про timeline»). Если нет — предложи сам: «Хочешь, углубимся в fan-out или в шардирование БД?» Что демонстрировать в deep dive: - Конкретный алгоритм (consistent hashing, fan-out, rate-limiting через token bucket). - Обработку edge cases (hot partition, thundering herd, cache stampede). - Конкретные технологии с обоснованием. - Bottleneck identification: «Узкое место — fan-out write на знаменитостях. Решаем гибридом». --- ### Шаг 7. Scale (≈5 мин) Найди узкие места и устрани: - **Stateless-сервисы** → горизонтальное масштабирование за LB. - **БД** → read replicas (для read-heavy), затем sharding (когда не помещается/упирается в write). - **Кэш** → шардированный кластер, защита от stampede. - **Async** → тяжёлые операции (fan-out, нотификации, обработка медиа) в очередь. - **CDN** → весь статический/медиа egress. - **Multi-region** → geo-DNS + локальные реплики для latency и DR. --- ### Шаг 8. Trade-offs (≈4 мин) Резюмируй ключевые решения и их цену. Это то, что отделяет senior от middle. - «Выбрал eventual consistency для ленты → выигрыш в availability и latency, цена — твит может появиться у подписчика с задержкой в 1–2 с. Для соцсети приемлемо». - «Гибридный fan-out → сложнее код и merge на чтении, зато решает celebrity problem». --- ## Строительные блоки ### Load Balancer **L4 (transport, TCP/UDP)** vs **L7 (application, HTTP):** - **L4** — балансирует по IP:port, не смотрит в содержимое. Очень быстрый, низкий overhead, не терминирует TLS. Не умеет content-based routing. - **L7** — видит HTTP: маршрутизация по path/header/cookie, sticky sessions, TLS-терминация, retry, rate-limit. Дороже по CPU. Это nginx/Envoy/ALB. **Алгоритмы:** - Round-robin — по кругу. Прост, не учитывает нагрузку. - Weighted round-robin — учёт мощности нод. - Least connections — на ноду с наименьшим числом активных соединений (хорош при разной длительности запросов). - Least response time — least-conn + latency. - IP hash / consistent hash — стабильная привязка клиента к ноде (sticky без cookie). **Health checks:** active (LB периодически дёргает `/healthz`) и passive (LB наблюдает за реальными ответами, выкидывает ноду после N ошибок). Различай **liveness** (жив ли процесс) и **readiness** (готов ли принимать трафик — прогрет ли кэш, есть ли коннект к БД). Не отправляй трафик на ноду, которая live, но not ready. --- ### Cache **Стратегии:** - **Cache-aside (lazy loading):** приложение читает кэш; miss → читает БД → кладёт в кэш. Самая частая. Минус: первый запрос медленный, риск stale-данных. Запись идёт в БД + инвалидация ключа. - **Write-through:** запись идёт в кэш, кэш синхронно пишет в БД. Кэш всегда консистентен, но запись медленнее. Хорош для read-heavy с требованием свежести. - **Write-back (write-behind):** запись в кэш, асинхронный сброс в БД батчами. Очень быстрая запись, высокий риск потери данных при падении кэша. Для счётчиков/метрик. - **Read-through:** кэш сам подгружает из БД при miss (логика в кэш-слое, а не в приложении). **Eviction:** - **LRU** — выселяем давно не используемое. Дефолт, хорош для temporal locality. - **LFU** — выселяем редко используемое. Лучше когда есть стабильно «горячий» набор, но дороже по учёту. - **TTL** — по времени жизни; комбинируется с LRU/LFU. - **FIFO / random** — простые, реже подходят. **Инвалидация** («одна из двух сложных задач в CS»): - TTL — просто, но окно stale-данных. - Write-through invalidation — на каждую запись инвалидируем/обновляем ключ. - Event-based — БД/CDC публикует изменения, кэш-слой реагирует. **Проблемы и защита:** - **Cache stampede / thundering herd:** ключ протух → тысячи запросов одновременно бьют в БД. Лечат: (1) probabilistic early expiration, (2) request coalescing / single-flight (один поток грузит, остальные ждут — в Go `golang.org/x/sync/singleflight`), (3) lock на ключ при перезагрузке. - **Cache penetration:** запросы несуществующих ключей минуют кэш. Лечат cache-of-misses (negative caching) или Bloom filter. - **Hot key:** один ключ создаёт перегрев одной ноды. Лечат локальной репликой ключа / клиентским кэшем. **Redis** — основной выбор: in-memory, структуры данных (string, hash, sorted set для лент/leaderboard, set для подписок), pub/sub, TTL, репликация, Cluster mode (хэш-слоты, 16384). Single-threaded на команды → атомарность простых операций без локов, но осторожно с O(N)-командами (`KEYS`, большие `SMEMBERS`). --- ### CDN Кэширует статику и медиа на edge-узлах ближе к пользователю → снижает latency и разгружает origin. Pull (CDN сам тянет с origin при первом запросе) или push (заранее заливаем). - Кэшировать: картинки, видео, JS/CSS, иногда API-ответы для анонимов. - Управление: `Cache-Control`, `ETag`, версионирование URL (`app.a1b2c3.js`) для cache busting. - Senior-нюанс: cache invalidation на CDN медленная (purge — это операция на тысячах edge); поэтому версионируй URL, а не инвалидируй. --- ### Message Queue Развязывает producer и consumer: асинхронность, сглаживание пиков (buffering), отказоустойчивость, fan-out. **Kafka vs RabbitMQ:** | | Kafka | RabbitMQ | |---|---|---| | Модель | distributed log (партиции, offset) | broker с очередями (AMQP) | | Потребление | consumer сам двигает offset, реплей возможен | сообщение удаляется после ack | | Throughput | очень высокий (миллионы msg/s) | средний | | Порядок | в пределах партиции | в пределах очереди | | Routing | простой (топик/партиция) | богатый (exchanges: direct/topic/fanout/headers) | | Use case | event streaming, логи, аналитика, event sourcing | task queues, сложный routing, RPC | Senior-различие: Kafka — это **persistent log**, можно перечитать историю и иметь несколько независимых consumer groups; RabbitMQ — это **очередь задач**, сообщение «исчезает» после обработки. **Delivery semantics:** - **At-most-once** — может потеряться, не дублируется (fire-and-forget). Для метрик где потеря ок. - **At-least-once** — не теряется, но возможны дубли при retry. **Дефолт большинства систем.** Требует **идемпотентных** consumers (дедуп по message_id / upsert). - **Exactly-once** — без потерь и дублей. Дорого и узко применимо: Kafka даёт его только внутри Kafka (transactions + idempotent producer); end-to-end exactly-once на практике эмулируется через at-least-once + идемпотентность на стороне потребителя. Прочее: **DLQ** (dead-letter queue) для «ядовитых» сообщений после N retry; **backpressure** и consumer lag как ключевая метрика мониторинга; ordering гарантируется только внутри партиции → ключ партиционирования = то, по чему нужен порядок (например, по `user_id`). --- ### Database: SQL vs NoSQL **SQL (Postgres, MySQL):** - Строгая схема, ACID, транзакции, JOIN, сложные запросы. - Вертикальное масштабирование легко, горизонтальное (write) — сложно. - Когда: финансы, заказы, любая предметка с реляционными инвариантами и транзакциями. **NoSQL:** - **Key-value** (Redis, DynamoDB) — простой доступ по ключу, огромный throughput. - **Document** (MongoDB) — гибкая схема, вложенные документы. - **Wide-column** (Cassandra, HBase) — write-heavy, линейное масштабирование, tunable consistency. - **Graph** (Neo4j) — связи (соцграф, рекомендации). - Обычно: горизонтальное масштабирование из коробки, денормализация, eventual consistency, query-first моделирование (схема под паттерн доступа). - Когда: огромный объём, простые паттерны доступа, write-heavy, гибкая схема. Senior-формулировка: «SQL — strong consistency и гибкие запросы ценой сложного горизонтального масштабирования; NoSQL — масштаб и доступность ценой ослабленной консистентности и ограниченных запросов. Выбор диктуется паттернами доступа и требованиями к консистентности, а не модой». **Репликация:** - **Leader-follower (master-slave):** запись в лидера, чтение с реплик. Масштабирует чтение. Реплики асинхронны → **replication lag** → возможно чтение устаревших данных (нарушение read-your-writes). Лечат: критичные чтения с лидера, либо semi-sync репликация. - **Multi-leader:** запись в несколько лидеров (multi-region). Проблема — **write conflicts** (нужны LWW / vector clocks / CRDT). - **Leaderless (Dynamo-style):** запись/чтение в N нод, кворумы. Консистентность через **R + W > N** (например N=3, W=2, R=2). - **Sync vs async:** sync — durability/consistency ценой latency; async — быстро, но окно потери данных при падении лидера. **Sharding (партиционирование):** - **Range-based:** диапазоны ключей по шардам. Удобно для range-запросов, но риск hot-shard (например, шардирование по времени — весь свежий трафик в один шард). - **Hash-based:** `hash(key) % N`. Равномерно, но range-запросы невозможны, и **resharding при изменении N перетряхивает почти все ключи**. - **Directory-based:** lookup-таблица key→shard. Гибко, но сам lookup — единая точка отказа/узкое место. Проблемы шардирования: **cross-shard joins/transactions** (дорого — нужен distributed transaction или денормализация), **rebalancing**, **hotspots**. **Consistent hashing** — решает проблему resharding в hash-based. Ключи и ноды мапятся на кольцо (хэш-пространство). Ключ принадлежит первой ноде по часовой стрелке. При добавлении/удалении ноды перемещается только ~1/N ключей, а не все. ``` node A / \ key3 key1 | | node C ---- node B \ / key2 Добавили node D между A и B: переедут только ключи в дуге (A, D] — остальные на месте. ``` **Virtual nodes (vnodes):** каждая физическая нода представлена множеством точек на кольце → равномернее распределение и плавный rebalance (без vnodes одна нода может получить непропорционально большую дугу). Используется в Cassandra, DynamoDB. --- ### CAP и PACELC **CAP-теорема:** при **сетевом разделении (Partition)** распределённая система может сохранить либо **Consistency** (все узлы видят одни данные), либо **Availability** (каждый запрос получает ответ), но не оба. - **CP** — при partition жертвуем доступностью: отказываем в ответе, чтобы не отдать неконсистентные данные (HBase, etcd, ZooKeeper, традиционный RDBMS в кластере). - **AP** — при partition отвечаем, рискуя устаревшими данными (Cassandra, DynamoDB, Riak). - «CA без P» — миф для распределённых систем: сеть может разорваться всегда, P нельзя «не выбрать». CA — это про single-node. Важно: CAP касается только поведения **во время partition**. В нормальном режиме система не обязана жертвовать ничем — отсюда расширение: **PACELC:** *If Partition → choose between Availability and Consistency; Else (нормальный режим) → choose between Latency and Consistency.* - Даже без сбоев есть фундаментальный trade-off latency ↔ consistency: синхронная репликация на кворум = выше consistency, но выше latency. - Cassandra/Dynamo: **PA/EL** (доступность при partition, latency в норме). - Полностью консистентная БД: **PC/EC**. Senior-вывод: реальные системы предлагают **tunable consistency** (Dynamo/Cassandra — выбор R/W/consistency level на запрос), и ты выбираешь точку на спектре per-use-case, а не для всей системы целиком. --- ## Чеклист (тайм-бокс на 45 мин) ``` [ 0–5 мин ] REQUIREMENTS □ Уточнить functional (core use cases), сузить scope □ Non-functional: consistency vs availability, latency p99, durability □ Спросить масштаб бизнеса: DAU/MAU, read/write ratio □ Зафиксировать вслух scope с интервьюером [ 5–10 мин ] ESTIMATIONS □ QPS (avg + peak ×3), отдельно read и write □ Storage/day и /year (+ медиа отдельно) □ Bandwidth (egress на пике) □ Memory для кэша (80/20) □ Каждое число → архитектурный вывод [ 10–13 ] API □ Ключевые эндпоинты, сигнатуры □ Cursor-пагинация, идемпотентность записи, versioning □ REST/gRPC/GraphQL — обосновать [ 13–16 ] DATA MODEL □ Сущности, связи, ключи □ Partition key, денормализация под чтение [ 16–24 ] HIGH-LEVEL □ Диаграмма: client → CDN → LB → services → data □ Stateless services □ Главный data flow словами (read path + write path) [ 24–36 ] DEEP DIVE (главное!) □ Выбрать 1–2 компонента, копать алгоритмами □ Edge cases: hot partition, stampede, celebrity problem □ Назвать bottleneck и решение [ 36–41 ] SCALE □ Replicas → sharding, кэш-кластер, async-очереди, CDN, multi-region [ 41–45 ] TRADE-OFFS + WRAP-UP □ Резюме ключевых решений и их цены □ «Что бы улучшил с большим временем» ``` --- ## Частые ошибки кандидатов 1. **Прыгают к решению без требований.** Рисуют боксы на первой минуте. Сначала — requirements и scope. 2. **Не считают цифры.** «Будет много трафика» вместо «90k QPS на пике → нужен кэш + шардирование». Без оценок нельзя обосновать архитектуру. 3. **Считают ради счёта.** Цифры не привязаны к решениям. Каждое число должно вести к выводу. 4. **Нет deep dive.** Остаются на уровне коробок-стрелок. Senior обязан углубиться хотя бы в один компонент с алгоритмами. 5. **Buzzword-bingo.** «Поставим Kafka, Redis, Cassandra, Kubernetes» без обоснования зачем. Технология должна следовать из требования. 6. **Молчание про trade-offs.** Любое решение имеет цену. Не назвал цену — выглядишь как middle, который знает один правильный ответ. 7. **Over-engineering для несуществующего масштаба.** Multi-region active-active для стартапа на 10k юзеров. Масштабируй под реальные числа. 8. **Игнорируют consistency.** Не проговаривают модель консистентности и replication lag. 9. **Не управляют временем.** 30 минут на API, ноль на масштабирование. Следи за тайм-боксами. 10. **Не слушают интервьюера.** Игнорируют подсказки и направляющие вопросы. Интервью — это диалог. 11. **Забывают про single points of failure** и про мониторинг/observability. 12. **Не обсуждают failure modes:** что при падении ноды, сети, кэша, лидера БД. --- ## Вопросы на собеседовании **В:** В чём разница между functional и non-functional requirements, и почему non-functional определяют архитектуру? **О:** Functional — что система делает (use cases: «опубликовать твит»). Non-functional — каким она должна быть: availability, consistency, latency, throughput, durability. Именно non-functional диктуют архитектуру: требование strong consistency исключает eventual-репликацию; целевой latency < 100 ms заставляет кэшировать и держать данные близко к пользователю; read/write ratio 1000:1 толкает к precompute и read-репликам. Функциональность можно реализовать множеством способов — non-functional сужают выбор до конкретных компонентов. **В:** Прикиньте storage и QPS для системы на 100M DAU, каждый постит 1 раз/день, читает 20 раз/день. **О:** Writes/day = 100e6 → avg ≈ 100e6/86400 ≈ 1160 QPS, peak ×3 ≈ 3500. Reads/day = 2e9 → avg ≈ 23k QPS, peak ≈ 70k. Storage: пост ≈ 0.5 KB → 100e6 × 0.5 KB = 50 GB/day ≈ 18 TB/year (текст). Вывод: read-heavy (read/write ≈ 20:1) → кэш + read-реплики; 70k QPS чтения нельзя обслужить из одной БД → шардирование/кэш; медиа считаем отдельно и кладём в blob + CDN. **В:** Fan-out on write vs on read для ленты — что выберете? **О:** Гибрид. Fan-out on write (push) даёт мгновенное чтение, но ломается на знаменитостях (100M записей на один твит — celebrity problem). Fan-out on read (pull) дёшев на запись, дорог на чтение. Решение: push для обычных пользователей (precompute timeline в Redis), pull для знаменитостей. На чтении мёржим precomputed timeline с свежими твитами тех celebrities, на кого подписан юзер. Trade-off: сложнее код и merge-логика на чтении, зато оба паттерна нагрузки обслуживаются эффективно. **В:** Объясните CAP и PACELC. Почему «CA» — некорректный выбор для распределённой системы? **О:** CAP: при сетевом разделении выбираешь либо Consistency, либо Availability. P (возможность partition) нельзя «отключить» — сеть рвётся всегда, поэтому реальный выбор только между CP и AP; «CA» описывает лишь single-node систему. CAP описывает только режим partition. PACELC расширяет: при Partition — A vs C, иначе (Else) — Latency vs Consistency. То есть даже без сбоев синхронная репликация повышает консистентность ценой latency. Cassandra — PA/EL, строго консистентная БД — PC/EC. **В:** Что такое cache stampede и как с ним бороться? **О:** Популярный ключ протухает → лавина одновременных запросов бьёт в БД (thundering herd), которая может лечь. Защита: (1) request coalescing / single-flight — один поток грузит значение, остальные ждут результат (в Go — `singleflight`); (2) probabilistic early expiration — обновляем ключ чуть раньше TTL с вероятностью, растущей к моменту истечения; (3) distributed lock на перезагрузку ключа; (4) stale-while-revalidate — отдаём устаревшее значение, пока фоном обновляем. **В:** Зачем consistent hashing и что добавляют virtual nodes? **О:** При `hash(key) % N` изменение числа нод N перетряхивает почти все ключи (massive resharding). Consistent hashing мапит ключи и ноды на кольцо; ключ идёт к первой ноде по часовой стрелке. При добавлении/удалении ноды переезжает только ~1/N ключей. Проблема базового варианта — неравномерность: одна нода может получить большую дугу. Virtual nodes решают это: каждая физическая нода = много точек на кольце → равномерное распределение и плавный rebalance. Применяется в Cassandra, DynamoDB. **В:** Kafka или RabbitMQ — когда что? **О:** Kafka — distributed log: высокий throughput, persistence с возможностью реплея, несколько независимых consumer groups, порядок в пределах партиции. Для event streaming, аналитики, event sourcing, логов. RabbitMQ — broker с богатым routing (exchanges) и моделью «сообщение исчезает после ack». Для task queues, сложной маршрутизации, RPC. Ключевое отличие: Kafka хранит историю и позволяет перечитать, RabbitMQ — это очередь задач, где сообщение удаляется после обработки. **В:** At-least-once vs exactly-once delivery — что реально использовать? **О:** At-least-once — практический дефолт: сообщение не теряется, но возможны дубли при retry. Требует идемпотентных consumers — дедуп по message_id или upsert-операции, чтобы повтор не менял результат. Exactly-once дорого и узко: Kafka даёт его транзакционно только внутри Kafka; end-to-end exactly-once на практике строится как at-least-once + идемпотентность на стороне потребителя. То есть «exactly-once» обычно означает «at-least-once с дедупликацией». **В:** Чем L4-балансировщик отличается от L7 и когда выбирать какой? **О:** L4 балансирует по IP:port, не заглядывая в payload — очень быстрый, низкий overhead, но без content-based routing и TLS-терминации. L7 видит HTTP: маршрутизация по path/header/cookie, TLS-терминация, retry, rate-limit, sticky sessions — гибче, но дороже по CPU. L4 — для предельного throughput и не-HTTP протоколов; L7 — когда нужна логика на уровне приложения (API gateway, canary по заголовку, terminating TLS). **В:** Как обеспечить read-your-writes consistency при leader-follower репликации с lag? **О:** Проблема: записал в лидера, тут же читаешь с асинхронной реплики и не видишь своей записи. Решения: (1) читать критичные/собственные данные с лидера в течение окна после записи; (2) запоминать timestamp/LSN записи у клиента и читать только с реплики, догнавшей этот LSN (monotonic/consistent prefix read); (3) sticky-routing пользователя на одну реплику; (4) semi-synchronous репликация, где лидер ждёт ack хотя бы одной реплики перед подтверждением записи. --- ## На что копают на senior+ - **Trade-offs, а не решения.** Senior не отвечает «правильно», он называет варианты и их цену, и обосновывает выбор под конкретные требования. Фраза-маркер: «это зависит от X, поэтому я выбираю Y». - **Числа → решения.** Оценки не для галочки: каждое число должно влечь архитектурный вывод. Умение быстро прикинуть порядки (QPS, storage, bandwidth) на back-of-the-envelope. - **Failure modes и устойчивость.** Что при падении ноды/сети/лидера/кэша? SPOF, graceful degradation, retry с backoff и jitter, circuit breaker, idempotency, timeout budget. - **Consistency-нюансы.** Не просто «strong vs eventual», а read-your-writes, monotonic reads, consistent prefix, replication lag, tunable R/W/N кворумы. - **Bottleneck identification.** Умение показать пальцем на узкое место («fan-out на знаменитостях», «hot partition по времени») и предложить точечное лечение. - **Глубина в deep dive.** Конкретные алгоритмы (consistent hashing с vnodes, token bucket для rate-limit, single-flight, fan-out-гибрид), а не названия технологий. - **Эволюция системы.** Как решение масштабируется от 10k до 100M юзеров поэтапно; что менять и в каком порядке; когда НЕ масштабировать (избегать premature optimization). - **Data modeling под паттерн доступа.** Query-first для NoSQL, выбор partition key с учётом hotspots и cross-shard операций. - **Observability и операционка.** Метрики (p99 latency, consumer lag, cache hit rate, error rate), SLO/SLA, как деплоить без даунтайма, как делать миграции схемы на живой системе. - **Cost awareness.** Senior+ учитывает стоимость: PB-хранилища, cross-region трафик, гигантский Redis-кластер — всё это деньги, и решение должно быть экономически разумным. - **Управление коммуникацией.** Ведёт интервью как диалог, проговаривает предположения, реагирует на подсказки, держит тайм-боксы.