← Semua pembelajaran / Go Nol → Enterprise
Fase 9 · Performa & Observability

pprof — menemukan di mana waktunya habis

Menebak di mana aplikasi lambat hampir selalu salah. pprof memberi jawabannya dalam dua menit, dan ia sudah ada di dalam setiap binari Go tanpa dependensi apa pun.

Sumber asli pkg.go.dev Resmi Rangkuman ~8 menit baca

Intisari

  • Impor net/http/pprof untuk mendapat endpoint profil — di port terpisah, tidak pernah terbuka ke publik.
  • Profil CPU menunjukkan di mana waktu habis; heap menunjukkan apa yang memakan memori.
  • go tool pprof -http=:8081 membuka graf api (flame graph) di browser.
  • Overhead profil CPU sekitar 5% — aman diambil dari produksi selama 30 detik.
  • goroutine adalah profil pertama yang dilihat saat memori naik pelan tanpa sebab.

Memasangnya dengan aman

import (
	"net/http"
	_ "net/http/pprof"      // mendaftarkan handler ke DefaultServeMux
)

// ❌ JANGAN: kalau handler utamamu memakai DefaultServeMux, seluruh
//    endpoint pprof jadi terbuka ke internet.

// ✅ Port terpisah yang hanya bisa dijangkau dari dalam VPC / sidecar.
func mulaiDebug(addr string, log *slog.Logger) {
	go func() {
		srv := &http.Server{
			Addr:              addr,           // "127.0.0.1:6060" atau port internal
			Handler:           http.DefaultServeMux,
			ReadHeaderTimeout: 5 * time.Second,
		}
		if err := srv.ListenAndServe(); err != nil &&
			!errors.Is(err, http.ErrServerClosed) {
			log.Error("server debug berhenti", "err", err)
		}
	}()
}

Endpoint pprof yang terbuka adalah kebocoran dan sekaligus alat serangan. Profil heap memuat potongan data yang sedang di memori; /debug/pprof/cmdline memuat argumen proses; dan meminta profil CPU 60 detik berulang kali membebani prosesmu. Di ECS, jadikan port debug tidak terekspos di target group mana pun (Fase 10).

Profil yang tersedia

ProfilMenjawabKapan dipakai
profile (CPU)Fungsi mana yang memakai CPUCPU tinggi, respons lambat
heapApa yang menahan memori sekarangMemori tinggi, OOM
allocsTotal alokasi sejak proses mulaiTekanan GC tinggi
goroutineSemua goroutine dan tumpukannyaKebocoran goroutine (Fase 2)
blockMenunggu channel dan mutexThroughput rendah, CPU rendah
mutexPerebutan lockTidak naik meski core ditambah
traceLini masa penjadwal dan GCLatensi ekor yang tidak jelas

Mengambil dan membaca

# CPU selama 30 detik, langsung buka antarmuka web
go tool pprof -http=:8081 http://localhost:6060/debug/pprof/profile?seconds=30

# Heap saat ini
go tool pprof -http=:8081 http://localhost:6060/debug/pprof/heap

# Goroutine — versi teks paling cepat dibaca
curl -s 'http://localhost:6060/debug/pprof/goroutine?debug=1' | head -40

# Simpan dulu, analisis nanti (mis. dari produksi)
curl -o cpu.pprof 'http://localhost:6060/debug/pprof/profile?seconds=30'
go tool pprof -http=:8081 ./bin/server cpu.pprof
TampilanUntuk
Flame GraphMelihat sekaligus; lebar batang = porsi waktu
TopDaftar fungsi paling mahal
SourceBiaya per baris kodemu
GraphHubungan pemanggilan

Baca kolom cum, bukan flat, saat mencari penyebab. flat adalah waktu di fungsi itu sendiri; cum termasuk semua yang dipanggilnya. Handler-mu hampir selalu punya flat mendekati nol dan cum besar — dan yang ingin kamu temukan adalah titik di mana cum besar berubah jadi flat besar.

Pola yang khas dan artinya

Yang terlihat di profilBiasanya berarti
runtime.mallocgc besarTerlalu banyak alokasi (Fase 8)
runtime.gcBgMarkWorker besarGC bekerja keras — kurangi alokasi atau naikkan GOGC
encoding/json.Marshal besarRespons terlalu gemuk, atau serialisasi diulang
runtime.selectgo, chanrecvMenunggu — cek profil block
sync.(*Mutex).LockPerebutan lock — cek profil mutex
syscall.Syscall besarMenunggu I/O; bukan masalah CPU
Profil merata tanpa puncakBottleneck-nya di luar proses — biasanya database

Menemukan kebocoran goroutine

curl -s 'http://localhost:6060/debug/pprof/goroutine?debug=1' | head -5
goroutine profile: total 48213
41077 @ 0x43e1c5 0x44f3f8 0x7c2a41
#	0x7c2a40	contoh.com/toko/internal/notif.(*Pengirim).kirim+0x120

   ↑ 41.077 goroutine menumpuk di satu baris — ini kebocoran,
     bukan beban tinggi.

Bandingkan dua profil goroutine berselang sepuluh menit. Kalau jumlahnya naik terus tanpa kenaikan trafik, ada goroutine yang tidak pernah selesai — dan tumpukannya langsung menunjuk barisnya. Go 1.26 juga punya profil goroutineleak eksperimental yang melakukan analisis ini untukmu (Fase 2).

Membandingkan sebelum dan sesudah

go tool pprof -http=:8081 -base lama.pprof baru.pprof

Tampilannya jadi selisih: hijau untuk yang berkurang, merah untuk yang bertambah. Ini cara tercepat membuktikan sebuah optimasi benar-benar bekerja — dan cara tercepat menemukan regresi setelah deploy.

Profil terus-menerus di produksi

// Perekam terbang (flight recorder), stabil sejak Go 1.25: simpan jejak
// beberapa detik terakhir di memori, tulis HANYA saat sesuatu terjadi.
fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{
	MinAge: 5 * time.Second,
})
_ = fr.Start()

// Di middleware: kalau ada permintaan yang sangat lambat, abadikan
// beberapa detik sebelum kejadiannya.
if durasi > 2*time.Second {
	var buf bytes.Buffer
	if _, err := fr.WriteTo(&buf); err == nil {
		simpanKeS3(ctx, buf.Bytes())
	}
}

Ini menjawab masalah klasik "lambatnya cuma sesekali dan tidak bisa direproduksi". Alih-alih mencoba menangkapnya saat terjadi, perekam terbang selalu merekam ke buffer melingkar dan hanya menulis keluar saat pemicunya terpenuhi — jejak dari sebelum kejadian, bukan sesudahnya.

Latihan: tambahkan endpoint yang sengaja boros (menyambung string dalam loop sejuta kali), bebani dengan hey -n 200 -c 10, lalu ambil profil CPU 15 detik dan buka flame graph-nya. Temukan fungsimu, perbaiki dengan strings.Builder, ambil profil kedua, dan bandingkan dengan -base.

Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.