🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
🔧 AI Nachrichten Erstellen Sie mit Google Gemini Music eigene Songs per KI(07.09.2026 um 08:00 Uhr)
🕵️ Sicherheitslücken0patch liefert drei Jahre Support für Microsoft Office 2021 - BornCity(07.09.2026 um 00:15 Uhr)
🔧 AI Nachrichten ChatGPT showing blank screen [Fix](05.09.2026 um 19:55 Uhr)
🔧 AI Nachrichten Erstellen Sie mit Google Gemini Music eigene Songs per KI(07.09.2026 um 08:00 Uhr)
🕵️ Sicherheitslücken0patch liefert drei Jahre Support für Microsoft Office 2021 - BornCity(07.09.2026 um 00:15 Uhr)

🔧 Programmierung 🕛 kürzlich 7 Min Lesezeit
0

Profiling a Go Service in Production: pprof in 10 Minutes

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Go ships with a profiler built in. No third-party tools, no agents to install, no instrumentation to add upfront. If your service imports net/http/pprof, you can profile it live in production right now — CPU usage, memory allocations, goroutine counts, blocking operations. The data is available over HTTP, readable with standard Go tooling.



Most engineers know pprof exists. Fewer have actually used it under pressure, on a real service, to find a real problem. This post walks through the mechanics — how to enable it, how to collect a profile, how to read a flame graph — and then shows the three classes of problems it catches most often.









Enabling pprof



For an HTTP service, one import is all it takes:




CODE
import (
"log"
"net/http"

_ "net/http/pprof" // registers handlers on DefaultServeMux
)

func main() {
// Your service setup...

// pprof endpoints are now available on DefaultServeMux
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()

// ... rest of main
}






The blank import registers the pprof HTTP handlers automatically. They’re available at:





  • GET /debug/pprof/ — index of available profiles


  • GET /debug/pprof/goroutine — all current goroutines


  • GET /debug/pprof/heap — memory allocations


  • GET /debug/pprof/profile?seconds=30 — CPU profile (30-second sample)


  • GET /debug/pprof/block — goroutine blocking events


  • GET /debug/pprof/mutex — mutex contention



Security note: bind pprof to localhost only, or a private interface. Never expose it on your public port. If your service runs in Kubernetes, use kubectl port-forward to reach it.



For services not using DefaultServeMux, register the handlers explicitly:




CODE
mux := http.NewServeMux()
mux.HandleFunc("/debug/pprof/", pprof.Index)
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)









Collecting a CPU profile



With the service running under load (profiling a quiet service tells you nothing useful), collect a 30-second CPU sample:




CODE
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30






This downloads the profile and drops you into an interactive shell. Or skip the shell and go straight to a flame graph:




CODE
# Collect the profile to a file
curl -o cpu.prof "http://localhost:6060/debug/pprof/profile?seconds=30"

# Open the flame graph in a browser
go tool pprof -http=:8080 cpu.prof






The -http flag serves a web UI with multiple views. The flame graph is the most useful starting point.









Reading a flame graph



A flame graph shows where your program spends time. Each horizontal bar is a function. Width represents time — wider means more CPU. The vertical axis is the call stack — functions lower down called the ones above them.




CODE
|          processJob          |     |  encode  |
| fetchFromDB |other|
| sql.Query |
| net.Read |






You’re looking for wide bars near the top — those are the functions consuming the most CPU. If json.Marshal is unexpectedly wide, you’re spending a lot of time serializing. If runtime.mallocgc is wide, you’re allocating heavily. If sync.Mutex.Lock is wide, you have lock contention.



The three most common findings in worker pool services:









Problem 1: Excessive allocations



Symptom: runtime.mallocgc is prominent in the CPU profile. Heap profile shows high allocation rate with short-lived objects.




CODE
# Collect a heap profile
curl -o heap.prof "http://localhost:6060/debug/pprof/heap"
go tool pprof -http=:8080 heap.prof






In the heap profile, switch to “alloc_objects” or “alloc_space” view (not “inuse” — that shows what’s live, not what’s being allocated). Look for hot allocation sites.



Common cause in worker pools: allocating a new buffer or slice on every job iteration.




CODE
// ❌ Allocates a new byte slice for every job
func processJob(ctx context.Context, job Job) error {
buf := make([]byte, 4096)
// use buf...
return nil
}

// ✅ Reuse buffers with sync.Pool
var bufPool = sync.Pool{
New: func() interface{} {
b := make([]byte, 4096)
return &b
},
}

func processJob(ctx context.Context, job Job) error {
bufPtr := bufPool.Get().(*[]byte)
defer bufPool.Put(bufPtr)
buf := *bufPtr
// use buf...
return nil
}






sync.Pool maintains a pool of reusable objects. Objects not retrieved before the next GC cycle are collected, so it doesn’t prevent garbage collection — it reduces the rate of allocation, which reduces GC pressure.









Problem 2: Goroutine leak



Symptom: goroutine count grows over time and never decreases.




CODE
# Check current goroutine count
curl "http://localhost:6060/debug/pprof/goroutine?debug=1"






The debug=1 parameter returns a text listing of all goroutines with stack traces. Look for hundreds or thousands of goroutines all blocked at the same location — that’s your leak.




CODE
goroutine 1042 [chan receive, 3 minutes]:
main.worker()
/app/worker.go:45 +0x68
created by main.startWorkers
/app/pool.go:23 +0x4c

goroutine 1043 [chan receive, 3 minutes]:
...
(997 more like this)






A thousand goroutines blocked on chan receive for 3 minutes means the jobs channel was never closed. The fix is covered in the concurrency mistakes post in this series — the producer must own close(jobs) via defer.



You can also check the goroutine count programmatically:




CODE
// In your health check endpoint
func healthHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "goroutines: %d\n", runtime.NumGoroutine())
}






If that number grows continuously over hours of runtime and never stabilizes, you have a leak.









Problem 3: Lock contention



Symptom: throughput is lower than CPU utilization suggests it should be. Workers are running but not making progress.



Mutex profiling is off by default because it has overhead. Enable it from inside the process only while investigating contention:




CODE
import "runtime"

runtime.SetMutexProfileFraction(1)
defer runtime.SetMutexProfileFraction(0)






Then collect the mutex profile:




CODE
curl -o mutex.prof "http://localhost:6060/debug/pprof/mutex"
go tool pprof -http=:8080 mutex.prof






Common cause in worker pools: a shared results map protected by a single mutex, written to by all workers simultaneously.




CODE
// ❌ All workers contend on one mutex
type ResultCollector struct {
mu sync.Mutex
results map[string]Result
}

func (c *ResultCollector) Add(id string, result Result) {
c.mu.Lock()
c.results[id] = result
c.mu.Unlock()
}






Solutions, in order of preference:




CODE
// ✅ Option 1: Use a results channel instead of shared state
results := make(chan Result, 1000)

// Workers send to channel (no lock)
go func() {
for job := range jobs {
result := processJob(ctx, job)
results <- result
}
}()

// Single collector goroutine reads from channel (no contention)
go func() {
for result := range results {
collected[result.ID] = result
}
}()

// ✅ Option 2: sync.Map for concurrent read-heavy workloads
var results sync.Map
results.Store(id, result)
val, ok := results.Load(id)






The channel approach is idiomatic Go — share memory by communicating rather than communicating by sharing memory. The sync.Map approach is better when you need random access to results across goroutines.









A quick reference for which profile to use



High CPU, unclear where — CPU profile: pprof .../profile?seconds=30.



Memory growing over time — heap profile (inuse_space): pprof .../heap.



High GC pressure — heap profile (alloc_objects): pprof .../heap.



Goroutine count growing — goroutine profile: pprof .../goroutine?debug=1.



Low throughput despite low CPU — mutex or block profile: pprof .../mutex or .../block. Enable mutex profiling with runtime.SetMutexProfileFraction; enable block profiling with runtime.SetBlockProfileRate.









One workflow for production incidents



When something is wrong and you need to understand it quickly:




CODE
# 1. Check goroutine count (cheap, immediate)
curl localhost:6060/debug/pprof/goroutine?debug=2 | head -50

# 2. If CPU is high, get a 30s CPU profile
curl -o cpu.prof localhost:6060/debug/pprof/profile?seconds=30

# 3. If memory is growing, get a heap snapshot
curl -o heap.prof localhost:6060/debug/pprof/heap

# 4. Open whichever profile is relevant
go tool pprof -http=:8080 cpu.prof






The whole process takes under 5 minutes. You don’t need to reproduce the problem locally, you don’t need to redeploy with extra instrumentation, and you don’t need to restart the service. The profiler runs against the live process.









Summary



pprof is built into Go’s standard library and available with a single import. The three problems it catches most reliably in worker pool services are excessive allocations (fix: sync.Pool), goroutine leaks (fix: close channels, use context), and mutex contention (fix: channel-based result collection or sync.Map). The flame graph view in go tool pprof -http is the fastest path from profile to finding.



The only prerequisite is running the service under realistic load when you collect the profile. A profile from a quiet service tells you where your code could spend time, not where it does.






Previous in this series: context.Context Is Not Optional

Next: Container-aware resource management in Go.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 37%
🟡 In Evaluierung 28%
🟢 Keine Auswirkung 10%
Spannende Innovation 25%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
ChatGPT showing blank screen [Fix]
1 Quelle
Excel keeps people on Windows, and a Linux distro creator wants Microsoft to end that
1 Quelle
Microsoft just stumbled onto a way to fix Windows 11, but it hasn't realized it yet
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Profiling a Go Service in Production: pprof in 10 Minutes

Thematisch verwandte Begriffe: Profiling, Service, Production, pprof · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...