Runtime & GC Best Practices
Production tuning workflow with metrics and load tests.
Search across all documentation pages
Production tuning workflow with metrics and load tests.
These rules distill the Runtime & GC section: measure first, tune knobs with headroom, and fix retainers before chasing micro-optimizations.
gctrace and pprof artifacts.GOGC, GOMEMLIMIT, alloc hot spots) per experiment.GOGC./gc/cpu:fraction-of-cpu-time and go_memstats_heap_alloc_bytes. Alert on sustained drift, not single spikes.NumGoroutine and StackInuse with request rate. Separate heap leaks from goroutine stack growth.GODEBUG=gctrace=1 during load tests. Store output with deploy tags for comparison.go test -bench with benchmem on hot packages. Catch alloc regressions in CI.GOMEMLIMIT to ~85-90% of pod memory limit. Leave headroom for stacks and off-heap memory.GOMAXPROCS to CPU limit when not automatic. Avoid invisible throttle with excess Ps.FreeOSMemory only after rare batch jobs. Not a substitute for leak fixes.GOMEMLIMIT over aggressive low GOGC in Kubernetes. CPU starvation hurts latency more than soft caps help.GOGC only with before/after p99 and gc_cpu metrics. One knob per canary.GOGC=off outside isolated benchmarks. Document any exception with time bounds.debug.SetGCPercent changes if used dynamically. Rare, observable, reversible.make loops before tuning GC. Allocation rate drives mark CPU.go build -gcflags="-m" on proven hot paths. Verify escapes match expectations.sync.Pool only for short-lived scratch buffers. Never pool objects with hidden state.map retainers.time.After in loops with reusable timers. Prevent timer heap leaks.context.Context and select on Done() in loops. Prevent parked goroutine leaks.Shutdown(ctx) with bounded timeout on HTTP/gRPC stop. Drain workers before exit.Close() over runtime.SetFinalizer for handles. Finalizers are diagnostic-only.Heap alloc gauge, goroutine count, GC CPU fraction, and process RSS.
Add gctrace capture in load tests before changing knobs.
Yes for cgroup-limited deployments (Kubernetes, Cloud Run, ECS).
Bare-metal with ample RAM may rely on defaults initially but should still measure.
When you have CPU headroom and must shrink peak heap for colocation or latency outliers tied to large heaps.
Validate with load tests.
Green Tea lowers mark CPU for many workloads in Go 1.26.
Re-profile after upgrade; knob strategy stays the same.
Steps to capture heap and goroutine profiles, NumGoroutine graph, recent deploy diff, cache config, and current GOGC/GOMEMLIMIT env.
No.
It adds latency noise and hides leaks temporarily.
Use dedicated diagnostic jobs instead.
Frameworks do not change GC mechanics.
Their middleware patterns affect goroutine lifetime and request-scoped allocations - profile your handlers.
Long-lived controllers leak badly without ctx cancel and Stop on informers.
Apply goroutine and cache caps aggressively in operators.
Every Go minor upgrade, monthly for tier-1 services, and after any change to caches or concurrency limits.
Partially.
Combine bodyclose, noctx, govet, and race detector CI with runtime metrics.
Linters do not replace profiles.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026