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 Аналитический пайплайн принимает миллиарды событий в сутки от продуктовых SDK/бэкендов, буферизует их в Kafka, обрабатывает потоково (Flink/Spark Structured Streaming) и пакетно, складывает в колоночное OLAP-хранилище (ClickHouse/Druid/BigQuery) и обслуживает дашборды и ad-hoc аналитику. Ключевые оси решений: stream vs batch (latency vs полнота/точность), lambda vs kappa (две кодовые базы vs reprocess из лога), exactly-once vs at-least-once + дедупликация, raw vs pre-aggregated (rollups). Главная единица параллелизма — партиции Kafka; главные узкие места — skew/hot partition, конкуренция query vs ingest в OLAP и стоимость хранения/retention. ## Требования **Functional** - Приём событий разных типов (page_view, click, purchase, custom) с произвольными атрибутами. - Near-real-time метрики (DAU/MAU, воронки, retention) с задержкой секунды–минуты. - Точные пакетные пересчёты (биллинг, отчётность) с гарантией полноты. - Произвольные ad-hoc запросы аналитиков по сырым/полусырым данным. - Возможность переиграть (reprocess) исторические данные при изменении логики обработки. - Эволюция схемы событий без простоя. **Non-functional** - Throughput: устойчиво принимать пиковые ~1–2M events/s. - Latency: stream-метрики p99 < 10 s end-to-end; batch — часы. - Durability: 0 потерь принятых событий (ack только после persist в Kafka). - Availability: ingestion 99.99% (приём важнее обработки — лучше отложить обработку, чем потерять событие). - Доступность данных: stream-слой может терять точность, batch-слой — источник истины. - Стоимость под контролем (это часто доминирующий критерий: hot/cold tiers, retention, rollups). - Идемпотентность: повторная доставка не искажает агрегаты. ## Оценки нагрузки Возьмём крупный продукт: 100M DAU, в среднем 100 событий на пользователя в сутки. - **Events/day**: 100M × 100 = **10 млрд событий/сутки (10^10)**. - **Средний events/s**: 10^10 / 86400 ≈ **115k events/s**. - **Пик** (×8 от среднего, дневной профиль + всплески): ≈ **~1M events/s**, проектируем headroom до 2M. - **Средний размер события**: сырой JSON ~1 KB; после нормализации/упаковки в Avro ~300–400 B. **Throughput Kafka** - Сырой входящий поток: 10^10 × 1 KB = **10 TB/сутки** до сжатия. - Со сжатием (lz4/zstd, ~4–5x на JSON-подобных данных): **~2–2.5 TB/сутки** на диск. - С репликацией RF=3: **~6–7.5 TB/сутки** реального диска в кластере. - Bandwidth на пике: 1M ev/s × 1 KB = **~1 GB/s** входящего трафика (× RF на репликацию = ~3 GB/s межброкерного). - Партиции: при целевых ~50 MB/s на партицию (consume) и потоке ~1 GB/s → нужно **порядка 200–500 партиций** на основной топик (плюс запас под consumer-параллелизм Flink). **Storage** - **Raw за год** (Kafka retention обычно 3–7 дней; «raw» долгоживущий хранится в объектном сторадже/data lake, Parquet+zstd): 10 TB/сутки × 365 ≈ 3.65 PB/год до сжатия; в Parquet+zstd ~0.7–1 PB/год. - **Aggregated за год** (rollups по минуте/часу/дню, размерности: гео, платформа, версия и т.п.): типично 1–3 порядка меньше raw → **единицы–десятки TB/год**, что и обслуживает 95% дашбордовых запросов. - **Kafka retention**: 7 дней × 2.5 TB/сутки × RF3 ≈ **~52 TB** на «горячий» лог (буфер для reprocess и медленных консьюмеров). Вывод: raw нужен дёшево и в data lake; OLAP держит агрегаты + ограниченное окно raw для drill-down. ## Архитектура ``` ┌─────────────────────── Batch layer ───────────────────────┐ │ │ ┌──────────┐ HTTPS/gRPC ┌────────────────┐ ┌─────────┐ ┌──────────────┐ ┌──────────────┐ │ │ Producers │ ─────────────▶ │ Collector / │──▶│ Kafka │──▶│ Spark / batch │─▶│ Data Lake │ │ │ SDK/web/ │ batched │ Gateway │ │ (topics │ │ (hourly/daily │ │ (S3/GCS, │ │ │ mobile/ │ │ - auth │ │ partit. │ │ ETL, recompute) │ Parquet) │ │ │ backend │ │ - validate │ │ + Schema│ └──────┬───────┘ └──────┬───────┘ │ └──────────┘ │ - enrich │ │ Registry│ │ │ │ ▲ │ - rate-limit │ │ Avro) │ │ batch views │ raw │ │ sampling/ │ - buffer/ack │ └────┬────┘ ▼ ▼ │ │ config └────────────────┘ │ ┌──────────────────────────────────┘ │ │ │ │ ┌──────▼──────┐│ ┌─────────────────────┐ │ │ Stream ││ │ OLAP Store │ ┌──────────┐ └──────────────────────────────────────── │ processing │┼▶│ ClickHouse / Druid / │──▶│Dashboards│ │ (Flink) ││ │ BigQuery (columnar) │ │ /BI/ API │ │ window/agg ││ │ rollups + raw window │ └──────────┘ │ dedup/EOS ││ └─────────────────────┘ ▲ └──────────────┘│ ▲ │ Speed layer ───┘ └── ad-hoc queries ─────┘ ``` **Компоненты** - **Producers / SDK**: клиентские и серверные SDK. Локальная буферизация и батчинг (снижает RPS), retry с backoff, идемпотентные ключи (event_id/UUID). Поддержка серверного семплинга через конфиг (для дешёвого high-volume трафика). - **Collector / Gateway** (stateless, на Go): терминирует HTTPS/gRPC, аутентификация (API key/HMAC), валидация против схемы, обогащение (geo по IP, user-agent parsing, server timestamp), rate limiting, и — критично — **подтверждает приём только после успешной записи в Kafka** (durability boundary). Горизонтально масштабируется за L7 LB. - **Kafka**: durable буфер и единый журнал. Разделяет ingest и обработку (backpressure isolation). Schema Registry + Avro для контролируемой эволюции схемы. Топики по доменам событий; партиционирование по ключу (см. ниже). - **Stream processing (Flink)**: speed layer. Stateful windowing, агрегации, дедупликация, обогащение из side-inputs, exactly-once в OLAP/Kafka через checkpoints. Низкая задержка, приблизительная полнота (late events). - **Batch (Spark)**: пересчёт по полным данным из data lake, тяжёлые джойны, backfill, source of truth для отчётности. Запускается по расписанию (hourly/daily). - **Data Lake (S3/GCS, Parquet)**: дешёвое долговременное хранение raw для reprocess и ad-hoc через Trino/Athena/Spark. - **OLAP store (ClickHouse/Druid/BigQuery)**: колоночное хранилище для дашбордов; держит rollups (быстрые запросы) + скользящее окно raw (drill-down). - **Dashboards/BI/API**: Grafana/Superset/собственный API поверх OLAP. ## Ключевые решения и trade-offs **Ingestion — Kafka** - **Партиционирование**: ключ = user_id/session_id даёт упорядоченность per-user и стабильную дедупликацию, но создаёт **skew** при «китах» (боты, нагруженные tenant'ы). Альтернатива — random/round-robin: идеальный баланс, но теряем per-key порядок (тогда дедуп и упорядочивание решаются ниже по стеку). Компромисс: композитный ключ (tenant_id + hash bucket) или sticky-партиционирование с детектором горячих ключей. - **Schema Registry + Avro**: компактнее JSON, строгая схема, контроль совместимости (BACKWARD/FORWARD). Продюсер шлёт schema_id, не саму схему. Эволюция без простоя: добавление полей с default'ами — backward-compatible. Альтернатива Protobuf (лучше для gRPC), JSON Schema (читаемо, но дорого по объёму). - **Backpressure**: Kafka = буфер, поглощающий всплески; обработчики читают в своём темпе. Если консьюмеры отстают — растёт lag, но данные не теряются (в пределах retention). На gateway — rate limit и load shedding (сначала режем семплируемый трафик, биллинговые события не дропаем). Acks=all + min.insync.replicas=2 для durability. **Stream (Flink) vs Batch (Spark): latency vs completeness** - Stream: задержка секунды, но к моменту закрытия окна часть событий ещё «в пути» (late/out-of-order) → приблизительный результат. - Batch: видит все данные за период → точный результат, но задержка часы. - На практике stream даёт оперативную картину, batch — авторитетную; дашборд показывает stream-значение, перетираемое batch-значением (reconciliation). **Lambda vs Kappa** - **Lambda**: batch layer (точность, полнота) + speed layer (свежесть) + serving layer (мёрж). Минус — **дублирование логики** в двух стеках (Spark + Flink), дрейф между ними, двойная стоимость поддержки. - **Kappa**: только stream. Пересчёт = переигрывание лога с нужного offset через новый job. Одна кодовая база. Требует: достаточный retention в Kafka (или tiered storage в lake), детерминированной обработки, версионирования job'ов. Минус — тяжёлый backfill за большие периоды (надо прогнать весь лог через стрим). Современный тренд — kappa, если retention/стоимость reprocess приемлемы; lambda оправдан, когда batch-вычисления принципиально дешевле/точнее (сложные джойны по годовым данным). **Exactly-once, watermarks, late/out-of-order, windowing** - **Exactly-once (EOS)** в Flink: барьерный чекпойнтинг (Chandy–Lamport) + транзакционный sink (Kafka transactions, идемпотентная запись в ClickHouse через дедуп-ключ). По сути «effectively-once»: at-least-once доставка + идемпотентность стейта. - **Watermarks**: оценка «времени события, до которого мы уже всё видели». Окно закрывается, когда watermark проходит его конец. Allowed lateness — грейс-период на доопоздавшие; что позже — в side output (dead-letter) или корректируется batch'ем. - **Out-of-order**: event-time обработка (по timestamp события), не processing-time. Bounded-out-of-orderness watermark с дельтой (например, 30 s). - **Windowing**: tumbling (непересекающиеся, для агрегатов по интервалам), sliding (скользящие, дороже по стейту), session (по неактивности, для сессий). **OLAP columnar store** - **ClickHouse**: предельная скорость на агрегациях, MergeTree, материализованные представления для rollups, дешёвый self-hosted; слабее на частых апдейтах/джойнах высокой кардинальности, eventual consistency реплик. - **Druid**: заточен под real-time ingest + low-latency timeseries дашборды, встроенный rollup на инжесте; сложнее в эксплуатации. - **BigQuery**: serverless, бесконечный масштаб, separation storage/compute; pay-per-query (риск дорогих сканов), вендор-лок. - Общий принцип: колоночное хранение + сжатие + партиционирование по времени + сортировка по частым предикатам. **Pre-aggregation / rollups vs raw** - Rollups (агрегаты по минуте/часу + размерности) уменьшают объём на 1–3 порядка и дают суб-секундные дашборды. Минус — фиксируют набор размерностей: нельзя задать вопрос, под который нет rollup'а; высокая кардинальность (user_id) плохо роллапится. - Raw нужен для drill-down и непредвиденных вопросов, но дорог и медленнее. Гибрид: rollups в OLAP для дашбордов + raw в lake (и короткое окно raw в OLAP) для ad-hoc. **Идемпотентность и дедупликация** - Каждое событие несёт уникальный event_id. Источники дублей: retry продюсера, at-least-once Kafka, переигрывание после сбоя. - Дедуп в стриме: stateful по (event_id) с TTL-окном (например, 24 ч) — окно ограничивает размер стейта, но дубли за пределами окна проскакивают. - Дедуп в OLAP: ReplacingMergeTree (ClickHouse) по ключу, дедуп при мёрже (eventual). Для точных счётчиков — батч-дедуп по полному датасету в lake. - Для approximate distinct (uniq users) — HyperLogLog: компактно и мёржится, ценой ~1–2% погрешности. ## Масштабирование и узкие места - **Партиции Kafka — единица параллелизма**: число консьюмеров в группе (≈ слотов Flink) не больше числа партиций. Под throughput и параллелизм закладывают сотни партиций; переразбиение «на лету» дорого (меняет распределение ключей и порядок) — лучше с запасом изначально. - **Hot partition / skew**: один «кит» перегружает партицию → лаг, неравномерная утилизация, отставание стейта в Flink. Лечение: композитный/бакетированный ключ, salting горячих ключей, отдельный топик/пул для высоконагруженных tenant'ов, детектор горячих ключей на gateway. - **OLAP query vs ingest**: непрерывный высокочастотный ingest конкурирует с тяжёлыми аналитическими запросами за CPU/IO/merge. Решения: разделение нод на ingest и query (или storage/compute separation как в BigQuery/Snowflake), батчинг вставок (вставлять крупными блоками, не по одному событию — иначе деградация MergeTree), async materialized views, отдельные реплики для read-heavy дашбордов. - **Stateful stream scaling**: стейт Flink (окна, дедуп) растёт с кардинальностью и lateness. RocksDB state backend, incremental checkpoints, TTL на стейт. Ребалансировка стейта при rescale — через savepoint. - **Retention / cost**: трёхуровневое хранение — hot (OLAP, дни–недели, дорого/быстро), warm (lake Parquet, месяцы–годы, дёшево), cold/archive (Glacier). Агрессивные rollups + TTL на raw в OLAP. Tiered storage в Kafka переносит старые сегменты в объектный сторадж, удешевляя длинный retention для kappa-reprocess. - **Скейл gateway**: stateless, линейно за LB; узкое место — сериализация/валидация (CPU). Батчинг от SDK снижает RPS и накладные расходы на соединение. ## Вопросы на собеседовании **В:** Чем отличаются lambda и kappa архитектуры и когда выбрать каждую? **О:** Lambda держит отдельные batch (точность/полнота) и speed (свежесть) слои с мёржем в serving; цена — дублирование логики в двух стеках и их дрейф. Kappa — только стрим, пересчёт через переигрывание лога. Kappa выбираем при достаточном retention/tiered storage и детерминированной обработке (меньше кода, один источник истины); lambda — когда batch принципиально дешевле или точнее на больших окнах (тяжёлые годовые джойны), а reprocess всего лога через стрим неподъёмен. **В:** Как добиться exactly-once при записи из Flink в OLAP? **О:** Строго exactly-once в распределёнке недостижим; делаем «effectively-once» = at-least-once доставка + идемпотентность. В Flink — барьерный чекпойнтинг для консистентного стейта плюс транзакционный или идемпотентный sink: Kafka transactions, либо запись в ClickHouse с дедуп-ключом (ReplacingMergeTree) / upsert по event_id. Сбой и повтор не меняют результат. **В:** Что такое watermark и как обрабатывать опоздавшие события? **О:** Watermark — оценка прогресса event-time: «событий с временем меньше W мы больше не ждём». По нему закрываются окна. Bounded-out-of-orderness задаёт допустимую дельту опоздания. Allowed lateness даёт грейс на доопоздавшие (окно пересчитывается); ещё более поздние идут в side output / dead-letter или корректируются batch-слоем. Обработка строго по event-time, не processing-time. **В:** Как выбрать ключ партиционирования в Kafka и чем грозит неудачный выбор? **О:** Ключ по user_id/session_id даёт per-key порядок и стабильную дедупликацию, но при «китах» создаёт hot partition: лаг, перекос утилизации, отставание стейта. Random балансирует, но теряет порядок. Компромисс — композитный ключ (tenant + hash-bucket), salting горячих ключей, выделенный топик для тяжёлых tenant'ов. Число партиций задаём с запасом — переразбиение меняет распределение и дорого. **В:** Stream даёт 1.0M уникальных пользователей, batch за тот же период — 1.02M. Это баг? **О:** Не обязательно. Stream закрывает окна по watermark и не видит опоздавшие/out-of-order события, плюс может использовать approximate distinct (HyperLogLog, ~1–2% погрешности). Batch считает по полным данным и точно. Расхождение в пределах единиц процентов ожидаемо; serving-слой перетирает stream-значение batch-значением (reconciliation). Баг — если расхождение растёт во времени или превышает ожидаемую погрешность. **В:** Зачем pre-aggregation/rollups, и какие у них ограничения? **О:** Rollups сжимают данные на 1–3 порядка и дают суб-секундные дашборды. Ограничение — они фиксируют набор размерностей: вопрос без заранее заготовленного rollup'а не ответить, а высококардинальные поля (user_id) роллапятся плохо. Поэтому гибрид: rollups для дашбордов + raw в lake (и короткое окно raw в OLAP) для drill-down и непредвиденной аналитики. **В:** Как гарантировать, что принятое событие не потеряется? **О:** Durability boundary — на gateway: ack клиенту только после успешной записи в Kafka с acks=all и min.insync.replicas=2 (выдерживает падение одного брокера). До Kafka — клиентский retry с идемпотентным event_id. После Kafka обработчики отстают, но не теряют данные в пределах retention; при сбое стрима — restart с чекпойнта. Раннее долговременное хранение raw в lake страхует от ошибок логики обработки (reprocess). **В:** Откуда берутся дубли и как с ними бороться на каждом уровне? **О:** Дубли — от retry продюсера, at-least-once Kafka и переигрывания после сбоев. Уровни: продюсер ставит уникальный event_id; стрим дедуплицирует по event_id в stateful-окне с TTL (ограничивает стейт, но пропускает дубли вне окна); OLAP дедуплицирует через ReplacingMergeTree/upsert (eventual); batch делает точный дедуп по полному датасету. Для approximate-метрик — HLL, устойчивый к дублям по природе. **В:** Что делать, когда тяжёлые аналитические запросы тормозят ingest в OLAP? **О:** Разделить ответственность: отдельные ноды/реплики под ingest и под query, либо хранилище с separation of storage/compute. Вставлять крупными батчами, а не по событию (иначе деградация MergeTree от мелких партов). Async materialized views для rollup'ов, отдельные read-реплики под дашборды, кэширование частых запросов и партиционирование/сортировка под предикаты. ## На что копают на senior+ - **Durability boundary** — понимаешь ли ты, в какой именно момент событие считается «принятым» (ack после Kafka), и что acks/ISR значат на практике при падении брокера. - **Event-time vs processing-time** и осознанная работа с watermarks/lateness — не «просто окна», а компромисс корректность/задержка и куда деваются опоздавшие. - **Честность про exactly-once**: кандидат, говорящий «exactly-once из коробки», слабее того, кто объясняет effectively-once = at-least-once + идемпотентность и где именно достигается идемпотентность. - **Skew** как первоклассная проблема: умеешь ли заранее проектировать против hot partition, а не лечить постфактум. - **Стоимость как design driver**: tiered storage, rollups vs raw, retention, separation compute/storage — на больших объёмах архитектуру диктует бюджет, и senior это учитывает явно с цифрами. - **Reprocess/backfill** как сценарий, а не послемыслие: можно ли переиграть историю при смене логики, чего это стоит в kappa vs lambda, версионирование джобов. - **Reconciliation** stream и batch: как два слоя сходятся в одном дашборде без двойного счёта и как объясняются расхождения бизнесу. - **Schema evolution**: совместимость Avro (backward/forward), как катить изменение схемы без простоя продюсеров и консьюмеров. - Способность **обосновать выбор OLAP** под конкретный профиль нагрузки (real-time ingest vs ad-hoc vs cost), а не назвать любимый инструмент.