Перейти к содержимому

Production Profiling в Go

Зачем знать: Production-системы сталкиваются с проблемами, которые невозможно воспроизвести локально: memory regression после релиза, tail latency p99, CPU spikes, goroutine leaks. Profiling в production — это не роскошь, а необходимость для Middle 2+ инженера. Continuous profiling позволяет видеть деградацию в реальном времени, а не получать ticket “сервис тормозит”. Знание pprof, flame graphs, trace tool и eBPF-based профайлеров отличает senior engineer от middle.


  1. Базовая концепция (повторение)
  2. Production-практики
  3. Gotchas (10+)
  4. Реальные кейсы
  5. Вопросы (30)
  6. Practice (5-8)
  7. Источники

pprof — это инструмент профилирования из Google, встроенный в Go стандартную библиотеку. Он использует sample-based profiling: периодически снимает стек-трейсы и агрегирует их по времени/объёму.

Виды профилей в Go:

ПрофильЧто измеряетКогда использовать
cpuCPU time per functionВысокая загрузка CPU, hot loops
heapMemory allocations (live + cumulative)Memory regressions, OOM
goroutineВсе живые goroutines + стекиGoroutine leaks, deadlocks
blockВремя блокировки на synchronizationLock contention, channels
mutexContention на mutexLock contention, sync.Mutex hotspots
threadcreateOS thread creationsThreading anomalies
allocsCumulative allocations с момента стартаTotal allocation rate
import (
_ "net/http/pprof"
"net/http"
)
func main() {
go func() {
// Профайлер endpoint
http.ListenAndServe("localhost:6060", nil)
}()
// ... основной код
}

После запуска доступны endpoints:

  • http://localhost:6060/debug/pprof/ — index
  • http://localhost:6060/debug/pprof/heap — snapshot heap
  • http://localhost:6060/debug/pprof/profile?seconds=30 — CPU profile 30 секунд
  • http://localhost:6060/debug/pprof/goroutine — goroutine dump
  • http://localhost:6060/debug/pprof/trace?seconds=10 — execution trace
Окно терминала
# Скачать и открыть CPU профиль
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
# Heap snapshot
go tool pprof http://localhost:6060/debug/pprof/heap
# Web visualization
go tool pprof -http=:8080 cpu.pprof

Внутри pprof CLI:

  • top — top функции по cost
  • top -cum — cumulative cost (включая callees)
  • list FuncName — source-level annotation
  • web — открыть call graph в браузере
  • peek FuncName — посмотреть callers и callees
  • traces — все собранные стек-трейсы

Главное правило: profiling endpoints не должны быть public.

heap profile — overhead ~1%, безопасно держать включённым всегда. ✅ goroutine profile — практически бесплатно, очень полезно для leak detection. ✅ CPU profile on-demand — overhead ~5% при активном profiling, иначе 0. ✅ allocs profile — то же что heap, но cumulative.

⚠️ block profile — требует runtime.SetBlockProfileRate(N). При rate=1 overhead значителен. Использовать только при investigation. ⚠️ mutex profile — требует runtime.SetMutexProfileFraction(N). При rate=1 высокий overhead.

// Только для активного исследования contention:
runtime.SetBlockProfileRate(1000) // 1 sample per 1000ns blocked
runtime.SetMutexProfileFraction(100) // 1 из 100 contention events

Анти-паттерн:

http.ListenAndServe(":8080", nil) // pprof доступен ВСЕМ

Production-паттерн:

// Public API на одном порту
go func() {
publicMux := http.NewServeMux()
publicMux.HandleFunc("/api/", apiHandler)
http.ListenAndServe(":8080", publicMux)
}()
// Admin (pprof, metrics, health) на другом
go func() {
adminMux := http.NewServeMux()
adminMux.HandleFunc("/debug/pprof/", pprof.Index)
adminMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
adminMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
adminMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
adminMux.HandleFunc("/debug/pprof/trace", pprof.Trace)
adminMux.Handle("/metrics", promhttp.Handler())
// Bind на localhost или internal interface
http.ListenAndServe("127.0.0.1:6060", adminMux)
}()

В Kubernetes admin port не expose-ить через Service, а только kubectl port-forward:

Окно терминала
kubectl port-forward pod/myapp-xyz 6060:6060
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/heap

Если admin port всё же доступен по сети (например, internal network), добавить auth:

func basicAuth(handler http.Handler, user, pass string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
u, p, ok := r.BasicAuth()
if !ok || u != user || subtle.ConstantTimeCompare([]byte(p), []byte(pass)) != 1 {
w.Header().Set("WWW-Authenticate", `Basic realm="admin"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
handler.ServeHTTP(w, r)
})
}
adminServer := &http.Server{
Addr: ":6060",
Handler: basicAuth(adminMux, os.Getenv("ADMIN_USER"), os.Getenv("ADMIN_PASS")),
}

Для serious enterprise — mTLS:

tlsConfig := &tls.Config{
ClientCAs: caPool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
adminServer.TLSConfig = tlsConfig
adminServer.ListenAndServeTLS(certFile, keyFile)

Континуальное профилирование — это сбор профилей непрерывно (каждые 10-30 секунд) и хранение их в time-series базе. Это позволяет:

  • Сравнивать профили “до релиза” и “после”
  • Находить regression автоматически
  • Видеть тренды (memory growth, CPU drift)

Open source, теперь часть Grafana Labs.

import "github.com/grafana/pyroscope-go"
func main() {
pyroscope.Start(pyroscope.Config{
ApplicationName: "myapp.production",
ServerAddress: "http://pyroscope:4040",
Logger: pyroscope.StandardLogger,
ProfileTypes: []pyroscope.ProfileType{
pyroscope.ProfileCPU,
pyroscope.ProfileAllocObjects,
pyroscope.ProfileAllocSpace,
pyroscope.ProfileInuseObjects,
pyroscope.ProfileInuseSpace,
},
Tags: map[string]string{
"region": os.Getenv("REGION"),
"version": os.Getenv("VERSION"),
},
})
// ... основной код
}

Pyroscope UI показывает:

  • Flame graph по времени
  • Diff между двумя временными окнами
  • Sandwich view (focus on function)

Альтернатива Pyroscope, использует eBPF для system-wide profiling (включая Go).

  • Storage в S3-compatible
  • Поддержка system-wide profiling (не только Go)
  • parca-agent работает через eBPF без модификации Go binary

Коммерческий, интегрирован с APM. Импорт:

import "gopkg.in/DataDog/dd-trace-go.v1/profiler"
profiler.Start(
profiler.WithService("myapp"),
profiler.WithEnv("prod"),
profiler.WithVersion("v1.2.3"),
profiler.WithProfileTypes(
profiler.CPUProfile,
profiler.HeapProfile,
profiler.GoroutineProfile,
profiler.MutexProfile,
profiler.BlockProfile,
),
)

Continuous profiling SaaS от создателей Parca. eBPF-based.

Типичные настройки:

  • Sampling rate: 10 секунд CPU profile каждую минуту (или непрерывно)
  • Retention: 7-30 дней
  • Cost: профиль ~1MB сжатый, на 1000 хостов × 10K профилей/день = ~10TB/месяц

Flame graph придумал Brendan Gregg (Netflix). Это визуализация sample-based profiling:

  • X-axis (горизонталь): ширина = доля времени (НЕ время по порядку!)
  • Y-axis (вертикаль): глубина call stack (вниз = caller, вверх = callee)
  • Цвет: обычно произвольный, для группировки

Reading rules:

  1. Самые широкие функции внизу = top-level entry points
  2. Самые широкие функции наверху = “hot leaves” — где реально тратится время
  3. Pyramid shape (узкая вершина) = функция вызывает много других
  4. Plateau (плоская верхушка) = функция сама делает работу (hot leaf)

Сравнение двух профилей:

  • Красный (+): медленнее в новой версии
  • Синий (-): быстрее в новой версии
Окно терминала
# pprof diff
go tool pprof -base=old.pprof new.pprof
(pprof) web
  • speedscope.app — отличный online viewer, можно загрузить .pprof
  • flamegraph.com — Brendan Gregg’s
  • Pyroscope / Parca / Grafana — встроены
  • go tool pprof -http=:8080 — встроенный viewer
Окно терминала
# Сделать snapshot до изменений
curl -o before.pprof http://localhost:6060/debug/pprof/heap
# ... выкат новой версии, дать прогреться
# Сделать после
curl -o after.pprof http://localhost:6060/debug/pprof/heap
# Сравнить
go tool pprof -base=before.pprof after.pprof
(pprof) top
(pprof) list NewFunction

-base показывает delta между профилями, что критично для regression hunting.

(pprof) list myPackage.MyFunc
Total: 30s
ROUTINE ======================== myPackage.MyFunc
2.5s 5.0s (flat, cum) 16.66% of Total
. . 42:func MyFunc(items []Item) {
. . 43: for _, item := range items {
500ms 1.0s 44: if expensiveCheck(item) { <-- 1s here
. . 45: ...
2.0s 4.0s 46: result = process(item) <-- 4s here
. . 47: ...

flat = время именно в этой строке. cum = cumulative с callees.

(pprof) peek myFunc
5.00s 100% | caller1
2.00s 40% | caller2
... myPackage.myFunc
3.00s 60% | childFunc1
1.00s 20% | childFunc2

Показывает, кто вызывает функцию и кого она вызывает (с весами).

Heap profile имеет 4 представления:

Sample typeОписание
inuse_spaceПамять, занятая live объектами (NOT GC’d) — default
inuse_objectsКоличество live объектов
alloc_spaceОбщая память аллоцированная (включая уже GC’d)
alloc_objectsОбщее количество аллокаций
Окно терминала
go tool pprof -inuse_space heap.pprof # текущее использование
go tool pprof -alloc_space heap.pprof # cumulative allocations
go tool pprof -alloc_objects heap.pprof # для allocation rate

Интерпретация:

  • inuse_space high → memory leak или большой working set
  • alloc_space >> inuse_space → много аллокаций которые GC собирает (GC pressure!)
  • alloc_objects high → много мелких аллокаций (GC pressure)
Окно терминала
# Snapshot всех goroutines
curl -o goroutines.pprof http://localhost:6060/debug/pprof/goroutine?debug=2

debug=2 даёт human-readable stacks вместо .pprof формата.

В goroutine profile:

  • 1000+ goroutines с одинаковым стеком = leak
  • Goroutines заблокированы на chan receive, select, sync.Mutex.Lock — кандидаты
  • Goroutines stuck на net.Read — потенциально hanging connection
Окно терминала
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/goroutine

В web UI можно увидеть call graph с количеством goroutines на каждом узле.

import "go.uber.org/goleak"
func TestMain(m *testing.M) {
goleak.VerifyTestMain(m)
}
// или per-test
func TestSomething(t *testing.T) {
defer goleak.VerifyNone(t)
// ... test logic
}

После теста goleak проверяет, что не осталось “лишних” goroutines.

Показывает где goroutines блокируются (channels, sync.Cond, time.Sleep, etc.).

runtime.SetBlockProfileRate(1) // record EVERY blocking event
// или
runtime.SetBlockProfileRate(1000000) // 1 sample per 1ms blocked time (cheaper)

⚠️ Gotcha: Параметр — это rate in nanoseconds. 1 = всё, что блокируется ≥1ns, 0 = выключено.

Окно терминала
curl -o block.pprof http://localhost:6060/debug/pprof/block
go tool pprof block.pprof
runtime.SetMutexProfileFraction(100) // 1 из 100 contention events

Параметр — fraction (не rate). 100 = 1%, 1 = все события.

Когда использовать:

  • Подозрение на lock contention (рост latency без CPU)
  • Высокий goroutines blocked в metrics
  • Tail latency p99 в разы выше p50

⚠️ НЕ держать постоянно включёнными в production — overhead значительный.

eBPF (extended Berkeley Packet Filter) — это технология Linux ядра для запуска sandboxed программ внутри kernel. Используется для:

  • Network monitoring (Cilium)
  • Security (Falco)
  • Profiling (Parca, Pixie, Polar Signals)

Преимущества для profiling:

  • Не требует модификации Go binary
  • Низкий overhead
  • System-wide (видит и kernel, и user-space)

Open source observability platform (CNCF), купленный New Relic.

  • Auto-instrumentation через eBPF
  • Goroutine tracking
  • HTTP/gRPC tracing без кода
  • Latency analysis per endpoint

Установка в k8s:

Окно терминала
px deploy

eBPF-based system-wide profiler. Запускается как DaemonSet в k8s, профилирует ВСЕ процессы на ноде.

# k8s DaemonSet
kind: DaemonSet
metadata:
name: parca-agent
spec:
template:
spec:
containers:
- name: parca-agent
image: ghcr.io/parca-dev/parca-agent:latest
securityContext:
privileged: true # для eBPF

Главная проблема eBPF profiling для Go — stack unwinding. Go использует виртуальные стеки goroutines, и без debug info kernel не может пройти стек.

Решение:

  • Frame pointers (Go 1.20+): включены по умолчанию на Linux amd64. Это даёт fast stack unwinding.
  • DWARF unwinding: медленнее, но работает везде. Парсит .eh_frame секцию ELF.

Проверить frame pointers в binary:

Окно терминала
go build -gcflags="-l" -ldflags="-X main.X=1" myapp.go # default with FP
readelf -S myapp | grep eh_frame # должен быть

BCC (BPF Compiler Collection) — Python frontend для eBPF.

# bcc script для подсчёта goroutines
from bcc import BPF
bpf_text = """
int trace_newproc(struct pt_regs *ctx) {
bpf_trace_printk("New goroutine created!\\n");
return 0;
}
"""
b = BPF(text=bpf_text)
b.attach_uprobe(name="myapp", sym="runtime.newproc", fn_name="trace_newproc")
b.trace_print()

go tool trace показывает execution trace — точную хронологию событий runtime.

import "runtime/trace"
func main() {
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()
// ... код
}

Или через HTTP:

Окно терминала
curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
go tool trace trace.out

Открывается в браузере, доступны views:

  • Goroutine analysis — все goroutines с их state changes
  • GC events — когда был GC, сколько занял
  • Network blocking profile — где блокировались на сети
  • Sync blocking profile — блокировки на mutex/channels
  • Scheduler latency profile — задержки шедулера
  • User-defined regions — кастомные annotations
import "runtime/trace"
ctx, task := trace.NewTask(ctx, "handleRequest")
defer task.End()
trace.WithRegion(ctx, "parseInput", func() {
parseInput(r)
})
trace.WithRegion(ctx, "queryDB", func() {
db.Query(...)
})

В trace viewer видны эти регионы — можно понять, какая часть запроса тормозит.

import "runtime/trace"
fr := trace.NewFlightRecorder()
fr.SetPeriod(10 * time.Second) // храним последние 10s событий
fr.Start()
defer fr.Stop()
// При event (например, slow query):
if duration > threshold {
f, _ := os.Create("incident.trace")
fr.WriteTo(f)
f.Close()
}

Flight Recorder хранит ring buffer trace-событий. Когда происходит инцидент — дампит последние N секунд. Идеально для редких anomaly.

Окно терминала
# Attach к live процессу
dlv attach $(pidof myapp)
(dlv) goroutines
(dlv) goroutine 42
(dlv) stack
(dlv) locals

⚠️ Delve останавливает процесс во время debug. Не использовать на live traffic без warning.

// На admin endpoint
http.HandleFunc("/admin/stack", func(w http.ResponseWriter, r *http.Request) {
buf := make([]byte, 1<<20)
n := runtime.Stack(buf, true) // true = all goroutines
w.Write(buf[:n])
})

Эквивалент /debug/pprof/goroutine?debug=2, но можно встроить кастомно.

При инциденте:

  1. curl /debug/pprof/heap > heap.pprof — текущая память
  2. curl /debug/pprof/goroutine?debug=2 > goroutines.txt — все стеки
  3. curl /debug/pprof/profile?seconds=30 > cpu.pprof — что грузит CPU
  4. curl /debug/pprof/trace?seconds=5 > trace.out — runtime events

Анализировать локально:

Окно терминала
go tool pprof -http=:8080 heap.pprof
go tool trace trace.out

Самая частая ошибка. import _ "net/http/pprof" регистрирует handlers в http.DefaultServeMux. Если потом http.ListenAndServe(":8080", nil) — pprof доступен на public порту.

Исправление: всегда использовать explicit ServeMux.

3.2. ⚠️ CPU profile прерывает только goroutines в Go-коде

Заголовок раздела «3.2. ⚠️ CPU profile прерывает только goroutines в Go-коде»

CPU profiling в Go использует SIGPROF который собирает sample у goroutine в момент сигнала. Но если goroutine в syscall (например, blocked на read из сети) — она не получает SIGPROF корректно. Поэтому network-heavy код может выглядеть “холодным” в CPU profile.

Использовать block profile или trace для blocking-bound кода.

alloc_space — это total allocated since start. Если 1GB аллоцировалось, но GC всё собрал — inuse_space будет low, но alloc_space high. Это GC pressure, не leak.

По умолчанию runtime.MemProfileRate = 512*1024 (1 sample per 512KB). Маленькие аллокации (по 64 байта) могут не попасть в профиль.

Понизить для большей точности (ценой overhead):

runtime.MemProfileRate = 1 // record ALL (DANGEROUS!)
runtime.MemProfileRate = 4096 // 1 per 4KB (умеренно)

3.5. ⚠️ Goroutine profile показывает только активные

Заголовок раздела «3.5. ⚠️ Goroutine profile показывает только активные»

В goroutine профиле нет завершённых goroutines. Если leak уже произошёл и goroutine упала — profile её не покажет.

Для исторического анализа использовать metrics:

go func() {
for range time.Tick(10 * time.Second) {
count := runtime.NumGoroutine()
metrics.Gauge("goroutines").Set(float64(count))
}
}()

SetBlockProfileRate(1) записывает КАЖДОЕ событие блокировки. На busy server — это миллионы событий в секунду, overhead 20%+.

Использовать sampling: SetBlockProfileRate(10000) = 1 sample per 10μs.

SetMutexProfileFraction(N) — это fraction, не rate. 1 = записывать ВСЕ contention events. 100 = 1%. Часто путают и пишут 1 думая “выключено”.

runtime.SetMutexProfileFraction(0) // выключено
runtime.SetMutexProfileFraction(100) // 1% событий
runtime.SetMutexProfileFraction(1) // 100% событий (НЕ "выключено"!)

mmap, cgo allocations, syscall buffers — НЕ в heap profile. Если RSS процесса растёт, а heap не растёт — смотреть:

  • cgo
  • mmap
  • thread stacks
  • runtime overhead

Использовать runtime/metrics:

samples := []metrics.Sample{
{Name: "/memory/classes/total:bytes"},
{Name: "/memory/classes/heap/objects:bytes"},
{Name: "/memory/classes/heap/unused:bytes"},
{Name: "/memory/classes/os-stacks:bytes"},
}
metrics.Read(samples)

.pprof файлы хранят symbol info. Если профилировать binary версии X, а анализировать go tool pprof версии Y — symbols могут не совпадать. Анализировать тем же go version, что и собран binary.

Continuous profiling сам по себе профилируется в результатах. Обычно ~2-3% CPU. На critical-path сервисах можно увеличить sampling interval (30s вместо 10s).

runtime/trace пишет ВСЁ. На busy server 1 секунда trace = 100MB+. Поэтому trace используется только короткими интервалами (1-10 секунд).

3.12. ⚠️ Flame graph не показывает порядок выполнения

Заголовок раздела «3.12. ⚠️ Flame graph не показывает порядок выполнения»

X-axis — это proportion of time, не временная последовательность. Функции, которые выполнялись последовательно в разных стеках, могут стоять рядом или далеко в зависимости от sorting.

Для chronology — использовать go tool trace.

3.13. ⚠️ pprof “missing samples” на goroutines с короткой жизнью

Заголовок раздела «3.13. ⚠️ pprof “missing samples” на goroutines с короткой жизнью»

Если goroutine живёт <10ms (рough sample interval 10ms для CPU), она может вообще не попасть в CPU profile.

Компилятор inlines функции, и они могут не появиться в стеке профиля. Disable inlining для debug:

Окно терминала
go build -gcflags="-l" myapp.go

(Но это не для production!)


Cloudflare писали в блоге, как они отлавливают memory regression после релиза:

  1. До деплоя: snapshot heap profile
  2. После деплоя (30 мин прогрев): snapshot
  3. go tool pprof -base=before.pprof after.pproftop
  4. Видят: новая функция parseHeaderV2 аллоцирует 300MB больше
  5. list parseHeaderV2 → находят []byte(s) в hot path → fix через unsafe.Slice

Lesson: continuous profiling с baseline снимает 80% memory regression в первые часы после релиза.

Discord (на Go был один из первых сервисов, потом переписали на Rust) находил p99 latency spikes:

  1. Просто метрики показывают p99 = 200ms при p50 = 5ms
  2. CPU profile — ничего не показывает (CPU usage 30%)
  3. Block profile показывает: 80% времени p99 запросов — в sync.RWMutex.Lock
  4. Issue: один writer блокирует тысячи readers
  5. Решение: sharded map (consistent hashing)

Lesson: для latency outliers — block/mutex profile, не CPU.

Uber engineering blog описывал случай:

  1. Релиз — CPU usage скачет с 40% до 90%
  2. CPU profile показывает: 50% времени в runtime.mapaccess1_fast64
  3. list myFunc → видят горячий цикл с m[k] access
  4. Issue: использовали map[int64]struct{...} для большого set, GC pressure + cache misses
  5. Решение: roaring.Bitmap для integer sets

Lesson: runtime.* функции в top профиля → копать в сторону маp/slice/GC overhead.

Twitch писали о goroutine leak в чате:

  1. Алёрт: goroutines > 100000 (нормально 5000)
  2. goroutine?debug=2 snapshot → 95K goroutines в одном стеке: chan receive в subscriptionHandler
  3. Issue: при дисконнекте subscriber канал не закрывался, goroutine ждала вечно
  4. Решение: select с <-ctx.Done() для exit на cancel

Lesson: регулярно мониторить runtime.NumGoroutine() как key metric.

Реальный случай (не привязан к компании): app использовал ~2GB RSS стабильно, потом начал расти до 4GB за неделю.

  1. Heap profile inuse_space стабильный 500MB
  2. RSS растёт. Что?
  3. runtime/metrics показывает /memory/classes/os-stacks:bytes растёт
  4. Issue: goroutine leak, stacks занимают 1.5GB
  5. NumGoroutine() подтверждает: 500K goroutines

Lesson: RSS != heap. Смотреть полную картину памяти.

С Go 1.23+ команда Anthropic (вымышленный пример, но реалистичный) использует Flight Recorder для p99.9:

  • 99% запросов <10ms
  • 0.1% запросов >1s — но воспроизвести нельзя
  • Flight Recorder в background, при duration > 500ms → dump trace
  • Анализ trace показывает: STW pause GC в момент медленных запросов
  • Tuning: уменьшили GOGC с 100 до 50, p99.9 упал на 60%

Lesson: Flight Recorder — для редких events, которые нельзя воспроизвести.


  1. Какие виды pprof профилей существуют в Go и для чего каждый из них?
  2. В чём разница между inuse_space и alloc_space в heap profile?
  3. Какой overhead у CPU profiling в production?
  4. Почему нельзя выставлять public порт с /debug/pprof/ endpoints?
  5. Как настроить отдельный admin port для pprof в production?
  6. Что такое runtime.SetBlockProfileRate и какие значения параметра?
  7. В чём отличие SetMutexProfileFraction от SetBlockProfileRate?
  8. Когда использовать block profile, а когда mutex?
  9. Как сравнить два heap snapshot через pprof CLI?
  10. Что показывает differential flame graph?
  11. Как читать flame graph? Что значит ширина и высота?
  12. Что такое continuous profiling? Назовите 2-3 инструмента.
  13. Чем Pyroscope отличается от Parca?
  14. Что такое eBPF и зачем для Go profiling?
  15. Что такое frame pointers в Go и зачем они для eBPF?
  16. Как обнаружить goroutine leak?
  17. Что делает uber-go/goleak?
  18. Как анализировать goroutine?debug=2 output?
  19. Что такое Flight Recorder в Go 1.23+ и для чего?
  20. В чём отличие go tool trace от pprof?
  21. Что такое user regions в trace и как их использовать?
  22. Какие view’ы доступны в go tool trace?
  23. Почему в CPU profile не видны функции, заблокированные на syscall?
  24. Как runtime.MemProfileRate влияет на heap profile?
  25. Что такое off-heap память и как её измерить?
  26. Где смотреть OS stack memory если RSS растёт без alloc growth?
  27. Как использовать runtime/metrics для мониторинга памяти?
  28. Опишите алгоритм debug memory regression после релиза.
  29. Опишите алгоритм debug tail latency p99.
  30. Безопасно ли использовать delve attach в production?

Создать HTTP сервер с:

  • Public API на :8080
  • Admin endpoint (pprof, /metrics, /health) на 127.0.0.1:6060
  • Basic auth на admin endpoint
  • Опционально mTLS

Подключить Pyroscope (или Parca в docker-compose) и Go-приложение:

  • Профилировать CPU и heap
  • Запустить нагрузку (k6, vegeta)
  • Сделать релиз с регрессией (добавить ненужную аллокацию)
  • Увидеть в Pyroscope diff между “до” и “после”

Написать приложение с искусственным goroutine leak (например, не закрывающийся channel). Найти через:

  • runtime.NumGoroutine() мониторинг
  • goroutine?debug=2 snapshot
  • uber-go/goleak в тесте

Сделать сервис с sync.Mutex contention (например, single mutex на global map). Включить block + mutex profile, найти hot spot, переделать на sync.Map или sharded map. Сравнить latency p50/p99 до и после.

Реализовать handler с длинной операцией (~500ms) в 1 из 1000 запросов. Использовать Flight Recorder для capture trace при duration > 200ms. Проанализировать в go tool trace.

Развернуть Parca-agent в minikube (или kind). Запустить Go-приложение с нагрузкой. Получить flame graph через Parca без модификации Go binary.

Снять два heap snapshot (до/после операции). Через go tool pprof -base= найти топ-5 функций, увеличивших аллокации.

Добавить trace.WithRegion в HTTP handler для каждой стадии: parse, db query, render. Запустить trace 5 секунд, проанализировать в go tool trace где основное время.


  1. Go Diagnostics — официальная документация: https://go.dev/doc/diagnostics
  2. net/http/pprof package docshttps://pkg.go.dev/net/http/pprof
  3. Brendan Gregg, Flame Graphshttp://www.brendangregg.com/flamegraphs.html — оригинальная теория.
  4. Pyroscope documentationhttps://grafana.com/oss/pyroscope/ — continuous profiling.
  5. Parcahttps://www.parca.dev/ — open source continuous profiling.
  6. Pixie Labshttps://docs.px.dev/ — eBPF-based observability.
  7. Felix Geisendörfer bloghttps://www.flixien.dev/ — Go profiler internals от автора многих pprof улучшений.
  8. Dave Cheney, “High Performance Go Workshop”https://dave.cheney.net/high-performance-go-workshop — глубокое введение в Go performance.
  9. Cloudflare blog: “Go memory ballast”https://blog.cloudflare.com/ — case studies.
  10. Russ Cox, “Profiling Go Programs”https://go.dev/blog/pprof — оригинальный анонс pprof.
  11. uber-go/goleakhttps://github.com/uber-go/goleak — testing для goroutine leaks.
  12. Go 1.23 Flight Recorder proposalhttps://github.com/golang/go/issues/63185