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/ > Модуль: Runtime и память · Уровень: Senior+ ## TL;DR `runtime/trace` (execution tracer) пишет поток событий планировщика, GC, syscalls и блокировок с наносекундными таймстампами — в отличие от pprof, который даёт агрегированную статистику. Он отвечает на вопрос «почему конкретная горутина простояла 8 мс именно в этот момент», а не «куда в среднем уходит CPU». С Go 1.21 трейсер переписан на низкооверхедный формат (snapshot-free, ~1-2% overhead), а Go 1.25 принёс `trace.FlightRecorder` — кольцевой буфер для post-mortem диагностики редких событий. Дополняется неинвазивной диагностикой через `GODEBUG` (`gctrace`, `schedtrace`, `inittrace`). ## Теория ### Чем execution tracer отличается от pprof pprof — это **семплирующий профайлер**: раз в ~10 мс (CPU profile) snapshot стеков, потом агрегация. Он отвечает на «где горит CPU суммарно». Execution tracer — это **event log**: каждое значимое событие рантайма пишется в буфер с таймстампом, ничего не теряется и не усредняется. | Аспект | pprof (CPU/heap/block/mutex) | execution tracer | |---|---|---| | Модель данных | агрегат / семплы | поток событий с таймстампами | | Ось времени | нет (только суммы) | да — видно «когда» | | Планировщик (G/M/P) | не видно | полностью виден | | GC фазы (STW, mark assist) | косвенно | пофазно с таймингами | | Syscalls / блокировки | block/mutex profile (агрегат) | конкретное событие на конкретной горутине | | Latency одной горутины | нет | да — её жизненный цикл | | Overhead | низкий (семплинг) | выше, но с 1.21 ~1-2% | | Объём данных | небольшой | большой (МБ/сек под нагрузкой) | Главное правило: **pprof говорит ЧТО потребляет ресурс, tracer говорит ПОЧЕМУ и КОГДА что-то задержалось.** Классический кейс для трейсера — хвостовые задержки (p99 latency), которые pprof не объясняет, потому что они вызваны не «горячим кодом», а планированием: горутина была runnable, но Р был занят, или попала под STW, или ждала syscall. ### Сбор трейса: три способа **1. Программно через `runtime/trace`:** ```go package main import ( "os" "runtime/trace" ) func main() { f, err := os.Create("trace.out") if err != nil { panic(err) } defer f.Close() if err := trace.Start(f); err != nil { panic(err) } defer trace.Stop() // ... рабочая нагрузка ... } ``` **2. Через тесты/бенчмарки:** ```bash go test -trace=trace.out -bench=. ./... ``` **3. Через HTTP-эндпоинт (самый частый в проде):** ```go import ( "net/http" _ "net/http/pprof" // регистрирует /debug/pprof/* включая /trace ) func main() { go func() { // ВАЖНО: эндпоинт даёт полный доступ к диагностике — закрывайте его // от внешнего мира (отдельный порт, mTLS, ACL). http.ListenAndServe("localhost:6060", nil) }() // ... } ``` Снять трейс за 5 секунд: ```bash curl -o trace.out 'http://localhost:6060/debug/pprof/trace?seconds=5' go tool trace trace.out # откроет браузер с UI ``` ### go tool trace: какие view и что в них искать `go tool trace` поднимает локальный веб-сервер. Ключевые разделы: - **View trace by proc / by thread** — таймлайн событий по процессорам (P) или OS-потокам (M). Здесь видно, как горутины мигрируют между P, где STW-паузы (вся строка проваливается), где GC mark workers. - **Goroutine analysis** — для каждого типа горутины разбивка времени: Execution, Scheduler wait (runnable, но не запущена — главный индикатор нехватки P/CPU), Sync block (каналы/мьютексы), Block syscall, GC sweeping, GC pause. - **Network blocking / Syscall blocking / Scheduler latency profiles** — это flame-graph'ы, но построенные из событий трейса (можно скачать как pprof). - **User-defined tasks / regions** — ваши собственные tasks/regions (см. ниже). - **Minimum mutator utilization (MMU)** — кривая: какую долю CPU приложение реально отдаёт «полезной работе» (mutator) на окне заданной длины. Провал MMU = GC съедает CPU. Что искать на senior-уровне: - Длинные полосы **Scheduler wait** = недостаток P (`GOMAXPROCS`) или CPU throttling (cgroups в k8s). - Регулярные узкие вертикальные провалы по всем P = **STW GC паузы**. - Горутина блокируется на канале, в то время как producer спит = проблема backpressure. - Много **Syscall blocking** + рост числа M = блокирующие syscalls плодят потоки. ### User tasks / regions / log: связь домена с трейсом Трейсер сам по себе не знает про ваши «запросы». Аннотации связывают бизнес-логику с событиями рантайма. ```go import ( "context" "runtime/trace" ) func handleRequest(ctx context.Context, id string) error { // Task — логическая операция, может пересекать горутины, // привязана к context. Имеет начало и конец. ctx, task := trace.NewTask(ctx, "handleRequest") defer task.End() trace.Logf(ctx, "request", "id=%s", id) // событие-метка с категорией // Region — участок ВНУТРИ одной горутины (не пересекает границы). region := trace.StartRegion(ctx, "db.query") rows, err := queryDB(ctx) region.End() if err != nil { return err } // Идиома: defer-friendly region defer trace.StartRegion(ctx, "render").End() return render(rows) } ``` Различия, которые любят спрашивать: | Примитив | Границы | Назначение | Привязка | |---|---|---|---| | `Task` | может охватывать много горутин | сквозная операция (запрос) | через `context.Context` | | `Region` | строго в одной горутине | фаза работы внутри горутины | через ctx (но локальна) | | `Log/Logf` | точечное событие | метка/значение в потоке | через ctx | В `go tool trace` → **User-defined tasks** появляется распределение latency ваших задач, и можно кликнуть на самый медленный экземпляр, провалиться в его таймлайн и увидеть, на чём именно он стоял. `trace.IsEnabled()` позволяет не платить за форматирование, когда трейс выключен. ### Overhead и переписанный трейсер (Go 1.21+) До 1.21 трейсер имел заметный overhead и неприятную особенность: при старте трейса делался **STW-снапшот всех горутин**, что давало паузу, растущую с числом горутин (проблема для сервисов с сотнями тысяч G). Формат был «монолитным» и плохо стримился/парсился. Go 1.21 принёс переписанный трейсер ([proposal #60773](https://go.dev/issue/60773)): - Overhead снижен примерно до **~1-2%** (раньше мог быть 10-20%+). - Формат стал **партиционированным** (generations): трейс делится на самодостаточные куски, можно парсить потоково, без удержания всего файла в памяти. - Убрана глобальная STW при старте на весь набор горутин — синхронизация per-P, паузы стали короткими и не зависящими от числа G. - Появился публичный парсер `golang.org/x/exp/trace` (позже `internal/trace` v2), на котором и построен FlightRecorder. Практический вывод: на современном Go execution tracing достаточно дёшев, чтобы держать его наготове в проде (особенно через flight recorder), а не только включать «когда уже всё сломалось». ### GODEBUG: неинвазивная диагностика без перекомпиляции `GODEBUG` — переменные рантайма, читаются при старте, выводят диагностику в stderr. Не требуют изменения кода. ```bash GODEBUG=gctrace=1,schedtrace=1000,inittrace=1 ./app ``` Полезные ключи: | GODEBUG | Что делает | |---|---| | `gctrace=1` | строка на каждый GC цикл (разбор ниже) | | `schedtrace=1000` | каждые 1000 мс — сводка планировщика | | `scheddetail=1` | вместе со `schedtrace` — детально по каждому P/M/G | | `allocfreetrace=1` | stacktrace на каждую аллокацию/освобождение (ОЧЕНЬ дорого, только дебаг; удалён в новых версиях) | | `inittrace=1` | тайминг и аллокации каждого `init()` пакета | | `madvdontneed=1` | возвращать память ОС через `MADV_DONTNEED` (агрессивнее, видно в RSS); на Linux по умолчанию `MADV_FREE` | | `asyncpreemptoff=1` | выключить асинхронную вытеснимость горутин (диагностика зависаний) | ### Разбор строки gctrace=1 по полям Пример строки: ``` gc 42 @8.301s 3%: 0.018+1.2+0.004 ms clock, 0.29+0.51/2.1/0+0.064 ms cpu, 41->42->22 MB, 43 MB goal, 0 MB stacks, 0 MB globals, 16 P ``` Разбор каждого поля: - **`gc 42`** — порядковый номер цикла GC с момента старта программы. - **`@8.301s`** — время от старта программы до начала этого цикла. - **`3%`** — суммарная доля CPU, потраченная на GC с момента старта (накопительно). Это тот самый GC CPU overhead; устойчивый рост к 25%+ — тревога (GC borrows до 25% CPU как hard limit мягко, плюс mark assist). - **`0.018+1.2+0.004 ms clock`** — wall-clock время по фазам: - `0.018` — **STW sweep termination** (завершение sweep предыдущего цикла, мир остановлен). - `1.2` — **concurrent mark and scan** (маркировка идёт параллельно с приложением). - `0.004` — **STW mark termination** (финальная остановка мира). Вот эти два STW-числа (первое и последнее) — и есть ваши GC-паузы. Здесь паузы ~0.022 мс суммарно — отлично. - **`0.29+0.51/2.1/0+0.064 ms cpu`** — CPU-время (суммарно по всем ядрам) по тем же фазам: - `0.29` — STW sweep termination CPU. - `0.51/2.1/0` — concurrent mark CPU, разбитое на: **assist** (mutator-горутины принудительно помогают GC, когда аллоцируют быстрее, чем GC успевает), **background** (выделенные GC-воркеры), **idle** (воркеры на простаивающих P). Большой `assist` = приложение аллоцирует слишком агрессивно и тормозит само себя. - `0.064` — STW mark termination CPU. - **`41->42->22 MB`** — heap в трёх точках: **до GC** (heap при старте mark, 41) → **в пике на момент mark termination** (42) → **live heap после GC** (22, выжившие объекты). Разница 42→22 = собранный мусор. - **`43 MB goal`** — целевой размер heap для триггера следующего GC, вычисляется как `live * (1 + GOGC/100)`. При `GOGC=100` и live=22 цель ~44 MB. - **`0 MB stacks`** — память под стеки горутин, просканированная GC. - **`0 MB globals`** — размер просканированных глобальных переменных. - **`16 P`** — число процессоров (`GOMAXPROCS`), участвовавших в этом GC. Если строка заканчивается на `(forced)` — это GC, вызванный вручную через `runtime.GC()` или `debug.FreeOSMemory()`. Связь с `GOGC` и `GOMEMLIMIT`: `goal` напрямую зависит от `GOGC`. Если выставлен `GOMEMLIMIT`, рантайк может запускать GC чаще (goal искусственно снижается), чтобы не превысить лимит — в gctrace это видно как уменьшение `goal` и рост частоты gc#. ### Разбор schedtrace ``` SCHED 1003ms: gomaxprocs=8 idleprocs=6 threads=12 spinningthreads=1 idlethreads=4 runqueue=0 [0 0 1 0 0 0 0 0] ``` - `gomaxprocs=8` — число P. - `idleprocs=6` — сколько P сейчас простаивают (нет работы). Много idle при «тормозит» = работа не параллелится. - `threads=12` — всего M (OS threads), созданных рантаймом. - `spinningthreads` — M, активно ищущие работу (spinning перед сном). - `runqueue=0` — длина глобальной очереди runnable-горутин. Рост = голодание по P. - `[0 0 1 0 0 0 0 0]` — длины **локальных** runqueue каждого P. Перекос (одна большая, остальные нули) = плохой work-stealing/локальность. С `scheddetail=1` каждая строка раскрывается до состояния каждого G/M/P (status, очереди, привязки). ### Flight Recorder (Go 1.25): post-mortem трейсинг Проблема: редкое событие (раз в день p99.99 latency spike) поймать обычным трейсом нельзя — не знаешь, когда включать, а держать запись постоянно = терабайты. Решение — кольцевой буфер: трейсер пишет в память постоянно, но хранит только последние N секунд / M байт. Когда происходит «интересное» событие — снимаем содержимое буфера. ```go import ( "os" "runtime/trace" ) func main() { fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{ MinAge: 5 * time.Second, // хранить минимум столько недавней истории MaxBytes: 10 << 20, // ограничение буфера }) if err := fr.Start(); err != nil { panic(err) } defer fr.Stop() // ... работа; где-то в обработчике детектим аномалию ... if latency > slo { f, _ := os.Create("incident.trace") // WriteTo сбрасывает текущее содержимое кольцевого буфера на диск if _, err := fr.WriteTo(f); err != nil { log.Println(err) } f.Close() } } ``` Ключевые свойства: - Можно держать включённым в проде (overhead тот же ~1-2% низкооверхедного трейсера). - `WriteTo` даёт валидный трейс-файл, открываемый обычным `go tool trace`. - `MinAge` / `MaxBytes` — компромисс глубины истории против памяти; реально буфер хранит `>= MinAge`, но не больше `MaxBytes`. - Идеален для записи «что было ДО» падения/спайка, чего обычный реактивный `trace.Start` не даёт. До 1.25 этот паттерн делали руками поверх `golang.org/x/exp/trace.FlightRecorder` (экспериментальный API с ~Go 1.22). ## Подводные камни / gotchas - **`/debug/pprof/trace` без защиты = дыра.** Эндпоинт раскрывает структуру выполнения и даёт нагрузку на сбор. Не вешайте `net/http/pprof` на публичный listener; используйте отдельный internal-порт. - **Объём трейса.** Под высокой нагрузкой трейс растёт на мегабайты в секунду. `seconds=60` на нагруженном сервисе = сотни МБ, которые `go tool trace` парсит в память. Снимайте короткие окна (2-5 сек). - **`go tool trace` и старые трейсы.** Формат менялся (особенно в 1.21). Трейс, снятый на одной версии Go, корректно открывается соответствующей версией `go tool trace`. Не смешивайте. - **Regions не пересекают горутины.** Если запустить `StartRegion` в одной горутине, а `End()` вызвать в другой — поведение некорректно. Для сквозной семантики используйте `Task`. - **`allocfreetrace=1` парализует приложение** — stacktrace на каждую аллокацию. Только на игрушечных нагрузках; в свежих версиях Go удалён. - **`gctrace` показывает накопительный CPU %**, а не мгновенный. Низкий процент в начале и рост со временем — нормально для прогрева; смотрите тренд. - **`madvdontneed` и метрики RSS.** На Linux Go по умолчанию использует `MADV_FREE` — освобождённая память остаётся в RSS, пока ОС не понадобится. Мониторинг по RSS «врёт», пока не выставите `madvdontneed=1` (ценой производительности). Это частая причина ложных алертов «утечка памяти». - **Трейс не заменяет pprof.** Для «куда уходит CPU суммарно» трейс неудобен и дорог — это работа CPU-профайла. Инструменты комплементарны. - **Включённый трейсер влияет на тайминги.** Хоть overhead и мал, в микробенчмарках он искажает результаты; не трейсьте то, что измеряете на наносекундах. ## Вопросы на собеседовании **В:** Когда execution tracer нужен, а pprof бессилен? **О:** Когда вопрос содержит «когда» и «почему задержалось», а не «сколько суммарно». pprof агрегирует и не имеет оси времени — он покажет горячие функции, но не объяснит хвостовую latency, вызванную планированием: горутина была runnable, но все P заняты (Scheduler wait), попала под STW-паузу GC, ждала разблокировки канала/мьютекса или висела в syscall. Трейсер пишет каждое такое событие с таймстампом, поэтому можно взять конкретный медленный запрос и увидеть его полный жизненный цикл по времени. Также только трейсер показывает поведение планировщика (миграция G между P, work-stealing, рост числа M) и пофазные тайминги GC. **В:** Разберите строку gctrace: `gc 5 @0.1s 1%: 0.02+0.5+0.01 ms clock, ... 4->5->2 MB, 8 MB goal, ... 8 P`. **О:** Это 5-й цикл GC, начавшийся через 0.1с после старта; на GC ушёл 1% CPU накопительно. Тайминги clock — три фазы: STW sweep termination 0.02 мс, concurrent mark 0.5 мс, STW mark termination 0.01 мс; реальные паузы приложения — первое и последнее число (~0.03 мс суммарно). Heap: 4 МБ на старте mark, 5 МБ в пике на mark termination, 2 МБ живых объектов после сборки (собрано 3 МБ). Goal 8 МБ — порог триггера следующего GC, при GOGC=100 это примерно live*(1+GOGC/100) = 2*2 ≈ 4, но с учётом накладных и round-up рантайм взял 8. 8 P — столько процессоров участвовало. **В:** Чем Task отличается от Region? **О:** Task — логическая сквозная операция (например, HTTP-запрос), которая может пересекать границы горутин и привязана к `context.Context`; создаётся `trace.NewTask`, имеет начало и `task.End()`. Region — это участок работы строго внутри одной горутины (фаза вроде «db.query»), не пересекает горутины, создаётся `trace.StartRegion(ctx, ...)`. В UI Task даёт распределение latency операции и связь событий, разбросанных по горутинам; Region детализирует, на что ушло время внутри одной горутины. Log/Logf — точечные метки в потоке. **В:** Что изменилось в трейсере в Go 1.21? **О:** Трейсер переписали: overhead упал примерно до 1-2% (раньше десятки процентов в плохих случаях); убрали глобальную STW-паузу при старте трейса, которая раньше росла с числом горутин (синхронизация стала per-P); формат стал партиционированным на самодостаточные generations, что позволяет потоковый парсинг без загрузки всего файла в память, и появился стабильный публичный парсер. Практическое следствие — трейсинг стал достаточно дёшев для постоянного использования в проде, на чём построен flight recorder. **В:** Что такое FlightRecorder и зачем он, если есть trace.Start? **О:** `trace.FlightRecorder` (Go 1.25) — кольцевой буфер в памяти, в который трейсер пишет постоянно, храня только последние N секунд/M байт. `trace.Start` реактивен: чтобы поймать событие, надо знать момент заранее. Редкий спайк (p99.99 раз в сутки) так не поймать — а держать полную запись нельзя из-за объёма. FlightRecorder решает это: он всегда «крутится» с малым overhead, а когда код детектирует аномалию, вызывается `WriteTo`, сбрасывающий текущий буфер в валидный трейс-файл. Главное преимущество — он сохраняет то, что было ДО события, чего реактивный подход не даёт. **В:** Сервис тормозит, idleprocs в schedtrace высокий, runqueue растёт. Диагноз? **О:** Противоречие кажущееся: высокий `idleprocs` (P простаивают) при растущей `runqueue` (есть готовые горутины) обычно означает, что горутины не могут попасть на P — например, из-за блокировок на общем мьютексе/канале (готовы, но сериализованы), либо из-за того, что горутины часто уходят в блокирующие syscalls и рантайм не успевает раскидать работу, либо CPU throttling в cgroups (P формально есть, но ядро не даёт квоту — рантайм видит P свободными, а реального CPU нет). Проверяю трейсом: Goroutine analysis покажет, где время — Sync block (мьютекс), Syscall block, или Scheduler wait. Если в k8s — сверяю `GOMAXPROCS` с CPU limit. **В:** Почему RSS не падает после освобождения памяти, и при чём тут madvdontneed? **О:** На Linux рантайм Go по умолчанию отдаёт страницы ядру через `MADV_FREE`: страницы помечаются как освобождаемые, но ядро забирает их физически только под давлением памяти, поэтому RSS остаётся высоким. Это не утечка — память доступна ОС. Чтобы RSS отражал реальное потребление сразу, ставят `GODEBUG=madvdontneed=1`, переключая на `MADV_DONTNEED` (страницы возвращаются немедленно, ценой более дорогих будущих аллокаций). Частая причина ложных алертов про утечку при мониторинге по RSS. **В:** Как снять трейс с прод-сервиса и на что смотреть в go tool trace? **О:** Через `net/http/pprof` на закрытом порту: `curl -o t.out 'http://localhost:6060/debug/pprof/trace?seconds=3'`, окно короткое из-за объёма. Открываю `go tool trace t.out`. Смотрю: Goroutine analysis — разбивку времени по типам горутин (Execution vs Scheduler wait vs Sync/Syscall block); View by proc — STW-паузы (синхронные провалы по всем P) и миграции; MMU — не съедает ли GC мутатор; User tasks — распределение latency моих операций, проваливаюсь в самый медленный экземпляр и смотрю его таймлайн. Network/Syscall blocking профили скачиваю как pprof для flame-graph. **В:** Что показывает `inittrace=1` и зачем это на проде? **О:** Выводит по строке на каждый пакет: время выполнения его `init()` и сколько он нааллоцировал. Используется для диагностики медленного старта сервиса (важно для cold start, k8s readiness, FaaS) — часто оказывается, что какой-то пакет в `init()` читает конфиг/строит большие таблицы/коннектится к сети, раздувая время запуска. Это видно только так, потому что init выполняется до main и обычным профайлером не покрывается удобно. ## На что копают на senior+ - **Модель данных трейсера.** Понимаете ли вы, что это event log с per-P буферами, а не семплинг; почему отсюда берётся overhead и объём; что значит партиционирование на generations и почему оно убрало STW при старте. - **Фазы GC и их отражение в gctrace/трейсе.** Sweep termination, concurrent mark, mark termination, что из них STW; что такое mark assist и почему рост assist — симптом избыточных аллокаций; связь goal с GOGC и GOMEMLIMIT. - **Pacer и тонкая настройка.** Как `GOGC`/`GOMEMLIMIT` сдвигают goal и частоту GC, как это читать построчно в gctrace, когда GOMEMLIMIT приводит к death spiral (GC крутится постоянно у лимита). - **Планировщик через призму schedtrace/трейса.** Состояния G, work-stealing, spinning threads, рост M из-за блокирующих syscalls, влияние cgroups CPU limit на восприятие GOMAXPROCS (и почему automaxprocs/GOMAXPROCS-by-default в 1.25 важны). - **Latency-аналитика по конкретной горутине.** Способность взять p99-запрос, через user tasks провалиться в его таймлайн и атрибутировать задержку к scheduler wait / GC / блокировке, а не к коду. - **FlightRecorder в проде.** Когда применять, как выбирать MinAge/MaxBytes, чем отличается от continuous profiling, как интегрировать с алертами/триггерами на SLO-нарушение. - **Комплементарность инструментов.** Чёткое понимание, что pprof, execution trace, GODEBUG и runtime/metrics решают разные задачи, и умение выбрать правильный для конкретного симптома, а не «включить всё». - **Overhead-осознанность.** Где трейсер искажает измерения, почему нельзя трейсить наносекундные бенчи, как оценить стоимость постоянного трейсинга для конкретного сервиса.