Profiling memori Go untuk lonjakan lalu lintas: panduan pprof
Profiling memori Go membantu menangani lonjakan lalu lintas mendadak. Panduan pprof praktis untuk menemukan titik panas alokasi di JSON, scan DB, dan middleware.

Apa yang dilakukan lonjakan lalu lintas mendadak terhadap memori layanan Go
Sebuah “lonjakan memori” di produksi jarang berarti satu angka sederhana yang naik. Anda mungkin melihat RSS (memori proses) naik cepat sementara heap Go nyaris tak bergerak, atau heap tumbuh dan turun dalam gelombang tajam saat GC berjalan. Pada saat yang sama, latensi sering memburuk karena runtime menghabiskan lebih banyak waktu untuk pembersihan.
Polanya umum di metrik:
- RSS naik lebih cepat dari yang diharapkan dan kadang tidak turun sepenuhnya setelah lonjakan
- Heap in-use naik, lalu turun dalam siklus tajam saat GC berjalan lebih sering
- Laju alokasi melonjak (byte yang dialokasikan per detik)
- Waktu jeda GC dan CPU GC meningkat, bahkan jika setiap jeda kecil
- Latensi request melonjak dan latensi tail menjadi berisik
Lonjakan lalu lintas memperbesar alokasi per-request karena "pemborosan" kecil skala linier dengan beban. Jika satu request mengalokasikan tambahan 50 KB (buffer JSON sementara, objek scan per-barisan, data konteks middleware), maka pada 2.000 request per detik Anda memberi allocator sekitar 100 MB tiap detik. Go bisa menangani banyak hal, tetapi GC tetap harus menelusuri dan membebaskan objek-objek short-lived itu. Ketika alokasi melebihi pembersihan, target heap tumbuh, RSS mengikuti, dan Anda bisa mencapai batas memori.
Gejalanya familier: OOM kill dari orchestrator, lonjakan latensi mendadak, lebih banyak waktu dihabiskan di GC, dan layanan terlihat “sibuk” walau CPU tidak penuh. Anda juga bisa mendapatkan GC thrash: layanan tetap hidup tetapi terus mengalokasikan dan mengumpulkan sehingga throughput turun tepat saat Anda paling membutuhkannya.
pprof membantu menjawab satu pertanyaan dengan cepat: jalur kode mana yang paling banyak mengalokasikan, dan apakah alokasi itu perlu? Profil heap menunjukkan apa yang tertahan sekarang. Tampilan yang fokus pada alokasi (seperti alloc_space) menunjukkan apa yang dibuat dan dibuang.
Yang pprof tidak lakukan adalah menjelaskan setiap byte RSS. RSS mencakup lebih dari heap Go (stack, metadata runtime, pemetaan OS, alokasi cgo, fragmentasi). pprof paling baik menunjuk titik panas alokasi di kode Go Anda, bukan membuktikan total memori container secara tepat.
Menyiapkan pprof dengan aman (langkah demi langkah)
pprof paling mudah dipakai sebagai endpoint HTTP, tetapi endpoint itu bisa mengungkap banyak tentang layanan Anda. Perlakukan mereka seperti fitur admin, bukan API publik.
1) Tambahkan endpoint pprof
Di Go, setup paling sederhana adalah menjalankan pprof di server admin terpisah. Itu menjaga route profiling jauh dari router utama dan middleware Anda.
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
// Admin only: bind to localhost
log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
}()
// Your main server starts here...
// http.ListenAndServe(":8080", appHandler)
select {}
}
Jika Anda tidak bisa membuka port kedua, Anda bisa mount route pprof ke server utama, tetapi lebih mudah mengeksposnya secara tidak sengaja. Port admin terpisah adalah default yang lebih aman.
2) Kunci sebelum deploy
Mulailah dengan kontrol yang sulit salah. Binding ke localhost berarti endpoint tidak bisa dijangkau dari internet kecuali seseorang juga mengekspos port itu.
Checklist cepat:
- Jalankan pprof di port admin, bukan port utama yang melayani pengguna
- Bind ke 127.0.0.1 (atau interface privat) di produksi
- Tambahkan allowlist di edge jaringan (VPN, bastion, atau subnet internal)
- Wajibkan autentikasi jika edge Anda bisa menegakkannya (basic auth atau token)
- Verifikasi Anda bisa mengambil profil yang akan dipakai: heap, allocs, goroutine
3) Bangun dan rollout dengan aman
Jaga perubahan kecil: tambahkan pprof, kirim, dan konfirmasi hanya bisa diakses dari tempat yang Anda harapkan. Jika Anda punya staging, uji di sana dulu dengan mensimulasikan beban dan menangkap profil heap dan allocs.
Untuk produksi, roll out bertahap (satu instance atau sebagian kecil traffic). Jika pprof salah konfigurasi, radius kerusakan tetap kecil saat Anda memperbaikinya.
Tangkap profil yang tepat selama lonjakan
Selama lonjakan, satu snapshot jarang cukup. Tangkap timeline kecil: beberapa menit sebelum lonjakan (baseline), selama lonjakan (impact), dan beberapa menit setelah (recovery). Itu membuatnya lebih mudah memisahkan perubahan alokasi nyata dari perilaku warm-up normal.
Jika Anda bisa mereproduksi lonjakan dengan load terkontrol, cocokkan produksi serapih mungkin: campuran request, ukuran payload, dan concurrency. Lonjakan request kecil berperilaku berbeda dari lonjakan respons JSON besar.
Ambil profil heap dan profil fokus-alokasi. Mereka menjawab pertanyaan berbeda:
- Heap (inuse) menunjukkan apa yang hidup dan menahan memori sekarang
- Alokasi (alloc_space atau alloc_objects) menunjukkan apa yang banyak dialokasikan, meski cepat dibebaskan
Pola capture praktis: ambil satu profil heap, lalu satu profil alokasi, lalu ulangi 30–60 detik kemudian. Dua titik selama lonjakan membantu melihat apakah jalur tersangka stabil atau semakin cepat.
# examples: adjust host/port and timing to your setup
curl -o heap_during.pprof "http://127.0.0.1:6060/debug/pprof/heap"
curl -o allocs_30s.pprof "http://127.0.0.1:6060/debug/pprof/allocs?seconds=30"
Bersamaan dengan file pprof, catat beberapa statistik runtime supaya Anda bisa menjelaskan apa yang GC lakukan pada waktu yang sama. Ukuran heap, jumlah GC, dan waktu jeda biasanya cukup. Bahkan satu baris log singkat pada tiap waktu capture membantu mengkorelasikan “alokasi naik” dengan “GC mulai berjalan terus-menerus.”
Simpan catatan insiden saat Anda berjalan: versi build (commit/tag), versi Go, flag penting, perubahan konfigurasi, dan traffic yang terjadi (endpoint, tenant, ukuran payload). Detail itu sering penting nanti ketika Anda membandingkan profil dan menyadari campuran request bergeser.
Cara membaca profil heap dan alokasi
Profil heap menjawab pertanyaan berbeda tergantung tampilan.
Inuse space menunjukkan apa yang masih ada di memori pada saat capture. Gunakan untuk kebocoran, cache jangka panjang, atau request yang meninggalkan objek.
Alloc space (total allocations) menunjukkan apa yang dialokasikan sepanjang waktu, bahkan jika dibebaskan cepat. Gunakan ketika lonjakan menyebabkan kerja GC tinggi, lonjakan latensi, atau OOM akibat churn.
Sampling penting. Go tidak merekam setiap alokasi. Ia melakukan sampling alokasi (dikontrol oleh runtime.MemProfileRate), sehingga alokasi kecil yang sering bisa kurang terwakili dan angkanya adalah perkiraan. Pelanggar terbesar tetap biasanya menonjol, terutama dalam kondisi lonjakan. Cari tren dan kontributor teratas, bukan akuntansi sempurna.
Tampilan pprof yang paling berguna:
- top: cepat melihat siapa yang mendominasi inuse atau alloc (cek flat dan cumulative)
- list
: sumber alokasi pada level baris dalam fungsi panas - graph: jalur panggilan yang menjelaskan bagaimana Anda sampai di sana
Diffs adalah tempatnya menjadi praktis. Bandingkan profil baseline (traffic normal) dengan profil lonjakan untuk menyoroti apa yang berubah, alih-alih mengejar noise latar.
Validasi temuan dengan perubahan kecil sebelum refactor besar:
- Reuse buffer (atau tambahkan
sync.Pool) di jalur panas - Potong pembuatan objek per-request (misalnya, hindari membangun map per-field untuk JSON)
- Re-profile di bawah beban yang sama dan konfirmasi diff mengecil di tempat yang diharapkan
Jika angka bergerak ke arah yang benar, Anda telah menemukan penyebab nyata, bukan hanya laporan yang menakutkan.
Menemukan titik panas alokasi pada encoding JSON
Selama lonjakan, pekerjaan JSON bisa menjadi tagihan memori utama karena dijalankan pada setiap request. Titik panas JSON sering muncul sebagai banyak alokasi kecil yang membuat GC bekerja lebih keras.
Tanda bahaya di pprof
Jika tampilan heap atau alokasi menunjuk ke encoding/json, periksa dengan teliti apa yang Anda berikan ke sana. Pola ini umum memperbesar alokasi:
- Menggunakan
map[string]any(atau[]any) untuk respons alih-alih struct bertipe - Memarsalkan objek yang sama beberapa kali (mis. logging dan juga mengembalikannya)
- Pretty printing dengan
json.MarshalIndentdi produksi - Membangun JSON melalui string sementara (
fmt.Sprintf, konkatenasi string) sebelum memarsal - Mengonversi
[]bytebesar menjadistring(atau sebaliknya) hanya untuk memenuhi API
json.Marshal selalu mengalokasikan []byte baru untuk output penuh. json.NewEncoder(w).Encode(v) biasanya menghindari satu buffer besar itu karena menulis ke io.Writer, tetapi masih bisa mengalokasikan secara internal, terutama jika v penuh any, map, atau struktur berbobot pointer.
Perbaikan cepat dan eksperimen singkat
Mulailah dengan struct bertipe untuk bentuk respons Anda. Mereka mengurangi kerja refleksi dan menghindari boxing interface per-field.
Lalu hilangkan sementara per-request yang tidak perlu: reuse bytes.Buffer lewat sync.Pool (dengan hati-hati), jangan indent di produksi, dan jangan re-marshal hanya untuk log.
Eksperimen kecil yang mengkonfirmasi JSON sebagai penyebab:
- Ganti
map[string]anydengan struct untuk satu endpoint panas dan bandingkan profil - Beralih dari
MarshalkeEncoderyang menulis langsung ke response - Hapus
MarshalIndentatau format debug-only dan re-profile di beban yang sama - Lewati encoding JSON untuk respons yang di-cache tidak berubah dan ukur penurunannya
Menemukan titik panas alokasi pada pemindaian query
Saat memori melonjak selama lonjakan, pembacaan database sering menjadi kejutan. Mudah fokus pada waktu SQL, tetapi langkah scan dapat mengalokasikan banyak per baris, terutama saat Anda melakukan scan ke tipe fleksibel.
Pelaku umum:
- Scan ke
interface{}(ataumap[string]any) dan membiarkan driver menentukan tipe - Mengonversi
[]bytekestringuntuk setiap field - Menggunakan wrapper nullable (
sql.NullString,sql.NullInt64) pada result set besar - Mengambil kolom teks/blob besar yang tidak selalu diperlukan
Satu pola yang diam-diam membakar memori adalah memindai data baris ke variabel sementara, lalu menyalinnya ke struct yang sebenarnya (atau membangun map per baris). Jika Anda bisa memindai langsung ke struct dengan field konkrit, Anda menghindari alokasi ekstra dan pengecekan tipe.
Ukuran batch dan paging mengubah bentuk memori Anda. Mengambil 10.000 baris ke sebuah slice mengalokasikan untuk pertumbuhan slice dan setiap baris, sekaligus. Jika handler hanya butuh halaman, dorong itu ke query dan jaga ukuran halaman stabil. Jika Anda harus memproses banyak baris, stream mereka dan agregasi ringkasan kecil alih-alih menyimpan setiap baris.
Kolom teks besar perlu perhatian khusus. Banyak driver mengembalikan teks sebagai []byte. Mengonversinya ke string menyalin data, jadi melakukannya untuk setiap baris bisa meledakkan alokasi. Jika Anda hanya perlu nilai itu kadang-kadang, tunda konversi atau scan lebih sedikit kolom untuk endpoint itu.
Untuk memastikan apakah driver atau kode Anda yang melakukan sebagian besar alokasi, periksa apa yang mendominasi profil:
- Jika frame mengarah ke kode mapping Anda, fokus pada target scan dan konversi
- Jika frame mengarah ke
database/sqlatau driver, kurangi baris dan kolom dulu, lalu pertimbangkan opsi spesifik driver - Periksa baik
alloc_spacemaupunalloc_objects; banyak alokasi kecil bisa lebih buruk daripada beberapa yang besar
Contoh: endpoint “list orders” melakukan scan SELECT * ke []map[string]any. Selama lonjakan, setiap request membangun ribuan map dan string kecil. Mengubah query untuk memilih hanya kolom yang dibutuhkan dan memindai ke []Order{ID int64, Status string, TotalCents int64} sering menurunkan alokasi segera. Ide yang sama berlaku jika Anda memprofil backend Go yang dihasilkan dari AppMaster: titik panas biasanya ada pada bagaimana Anda membentuk dan memindai data hasil, bukan database itu sendiri.
Pola middleware yang diam-diam mengalokasikan per request
Middleware terasa murah karena "hanya pembungkus", tetapi ia berjalan pada setiap request. Saat lonjakan, alokasi kecil per-request cepat bertambah dan muncul sebagai kenaikan laju alokasi.
Middleware logging adalah sumber umum: memformat string, membangun map field, atau menyalin header untuk output yang lebih rapi. Helper request ID bisa mengalokasikan saat membuat ID, mengonversinya ke string, lalu menyertakannya ke context. Bahkan context.WithValue bisa mengalokasikan jika Anda menyimpan objek baru (atau string baru) setiap request.
Compression dan penanganan body adalah penyebab sering lain. Jika middleware membaca seluruh body request untuk “intip” atau memvalidasinya, Anda bisa berakhir dengan buffer besar per request. Middleware gzip bisa mengalokasikan banyak jika membuat reader dan writer baru setiap kali alih-alih reuse buffer.
Layer auth dan session bisa serupa. Jika setiap request mem-parse token, decode base64 cookie, atau memuat blob session ke struct baru, Anda mendapat churn konstan meski pekerjaan handler ringan.
Tracing dan metrik bisa mengalokasikan lebih dari yang diperkirakan ketika label dibangun dinamis. Menggabungkan nama route, user agent, atau tenant ID ke string baru per request adalah biaya tersembunyi klasik.
Pola yang sering muncul sebagai “kematian oleh seribu potongan”:
- Membangun log line dengan
fmt.Sprintfdan map barumap[string]anyper request - Menyalin header ke map atau slice baru untuk logging atau signing
- Mengalokasikan buffer gzip dan reader/writer baru alih-alih pooling
- Membuat label metrik kardinalitas tinggi (banyak string unik)
- Menyimpan struct baru di context pada tiap request
Untuk mengisolasi biaya middleware, bandingkan dua profil: satu dengan chain penuh aktif dan satu dengan middleware sementara dinonaktifkan atau diganti no-op. Tes sederhana adalah endpoint health yang seharusnya hampir bebas alokasi. Jika /health mengalokasikan banyak selama lonjakan, handler bukanlah masalahnya.
Jika Anda membangun backend Go yang dihasilkan oleh AppMaster, aturan yang sama berlaku: jaga fitur lintas-potong (logging, auth, tracing) dapat diukur, dan perlakukan alokasi per-request sebagai anggaran yang bisa diaudit.
Perbaikan yang biasanya cepat memberi hasil
Setelah Anda punya tampilan heap dan allocs dari pprof, prioritaskan perubahan yang mengurangi alokasi per-request. Tujuannya bukan trik cerdas. Tujuannya membuat jalur panas membuat lebih sedikit objek short-lived, terutama saat beban.
Mulai dengan kemenangan aman dan membosankan
Jika ukuran dapat diprediksi, preallocate. Jika sebuah endpoint biasanya mengembalikan sekitar 200 item, buat slice dengan kapasitas 200 agar tidak tumbuh dan menyalin beberapa kali.
Hindari membangun string di jalur panas. fmt.Sprintf nyaman, tetapi sering mengalokasikan. Untuk logging, prioritaskan field terstruktur, dan reuse buffer kecil jika masuk akal.
Jika Anda menghasilkan respons JSON besar, pertimbangkan men-streaming-nya alih-alih membangun satu []byte atau string besar di memori. Pola lonjakan umum: request masuk, Anda membaca body besar, membangun respons besar, memori melonjak sampai GC mengejar.
Perubahan cepat yang biasanya tampak jelas di profil sebelum/sesudah:
- Preallocate slice dan map ketika Anda tahu rentang ukuran
- Ganti format fmt-heavy di penanganan request dengan alternatif lebih murah
- Stream respons JSON besar (encode langsung ke response writer)
- Gunakan
sync.Pooluntuk objek berulang dengan bentuk sama (buffer, encoder) dan kembalikan dengan konsisten - Tetapkan batas request (ukuran body, ukuran payload, ukuran halaman) untuk membatasi kasus terburuk
Gunakan sync.Pool dengan hati-hati
sync.Pool membantu ketika Anda berulang kali mengalokasikan hal yang sama, seperti bytes.Buffer per request. Ia juga bisa merugikan jika Anda pool objek dengan ukuran tak terduga atau lupa meresetnya, yang menjaga array backing besar tetap hidup.
Ukur sebelum dan sesudah menggunakan workload yang sama:
- Tangkap profil allocs selama jendela lonjakan
- Terapkan satu perubahan pada satu waktu
- Jalankan kembali campuran request yang sama dan bandingkan total allocs/op
- Pantau tail latency, bukan hanya memori
Jika Anda membangun backend Go yang dihasilkan oleh AppMaster, perbaikan ini tetap berlaku pada kode kustom di sekitar handler, integrasi, dan middleware. Di situlah alokasi yang dipicu lonjakan cenderung bersembunyi.
Kesalahan pprof umum dan alarm palsu
Cara tercepat membuang hari adalah mengoptimalkan hal yang salah. Jika layanan lambat, mulailah dengan CPU. Jika dibunuh oleh OOM, mulai dengan heap. Jika bertahan tetapi GC terus bekerja, lihat laju alokasi dan perilaku GC.
Perangkap lain adalah menatap “top” dan menganggap selesai. “Top” menyembunyikan konteks. Selalu periksa call stacks (atau flame graph) untuk melihat siapa pemanggil allocator. Perbaikan sering satu atau dua frame di atas fungsi panas.
Juga waspadai kebingungan antara inuse vs churn. Sebuah request mungkin mengalokasikan 5 MB objek short-lived, memicu GC ekstra, dan berakhir hanya dengan 200 KB inuse. Jika Anda hanya melihat inuse, Anda melewatkan churn. Jika hanya melihat total alokasi, Anda mungkin mengoptimalkan sesuatu yang tidak tetap tinggal dan tidak berisiko OOM.
Pengecekan cepat sebelum mengubah kode:
- Pastikan Anda di tampilan yang benar: heap inuse untuk retensi, alloc_space/alloc_objects untuk churn
- Bandingkan stack, bukan hanya nama fungsi (
encoding/jsonsering gejala) - Reproduksi traffic secara realistis: endpoint yang sama, ukuran payload, header, concurrency
- Tangkap baseline dan profil lonjakan, lalu diff keduanya
Uji beban yang tidak realistis menyebabkan alarm palsu. Jika tes Anda mengirim body JSON kecil tetapi produksi mengirim 200 KB, Anda akan mengoptimalkan jalur yang salah. Jika tes Anda mengembalikan satu baris database, Anda tidak akan melihat perilaku scanning yang muncul dengan 500 baris.
Jangan kejar noise. Jika sebuah fungsi muncul hanya di profil lonjakan (bukan baseline), itu petunjuk kuat. Jika muncul di keduanya pada level yang sama, mungkin itu kerja latar normal.
Contoh walkthrough insiden realistis
Promo Senin pagi keluar dan API Go Anda mulai menerima 8x traffic normal. Gejala pertama bukan crash. RSS naik, GC bekerja lebih keras, dan p95 latency melonjak. Endpoint paling panas adalah GET /api/orders karena aplikasi mobile menyegarkannya setiap kali membuka layar.
Anda mengambil dua snapshot: satu dari saat sepi (baseline) dan satu selama lonjakan. Tangkap tipe heap profile yang sama pada kedua waktu agar perbandingan adil.
Alur yang bekerja saat itu:
- Ambil profil heap baseline dan catat RPS, RSS, dan p95 latency saat itu
- Selama lonjakan, ambil profil heap lain plus profil alokasi dalam jendela 1–2 menit yang sama
- Bandingkan allocator teratas antara keduanya dan fokus pada yang paling tumbuh
- Telusuri dari fungsi terbesar ke pemanggilnya sampai Anda mencapai path handler
- Lakukan satu perubahan kecil, deploy ke satu instance, dan re-profile
Dalam kasus ini, profil lonjakan menunjukkan sebagian besar alokasi baru berasal dari encoding JSON. Handler membangun map[string]any untuk baris, lalu memanggil json.Marshal pada slice map. Setiap request membuat banyak string short-lived dan nilai interface.
Perbaikan paling kecil dan aman adalah berhenti membangun map. Scan baris database langsung ke struct bertipe dan encode slice itu. Tidak ada yang lain berubah: field sama, bentuk respons sama, status code sama. Setelah menggulir perubahan ke satu instance, alokasi di jalur JSON turun, waktu GC turun, dan latensi stabil.
Hanya setelah itu Anda rollout secara bertahap sambil memantau memori, GC, dan tingkat error. Jika Anda membangun layanan di platform no-code seperti AppMaster, ini juga pengingat untuk menjaga model respons bertipe dan konsisten, karena itu membantu menghindari biaya alokasi tersembunyi.
Langkah selanjutnya untuk mencegah lonjakan memori berikutnya
Setelah Anda menstabilkan lonjakan, buat lonjakan berikutnya membosankan. Perlakukan profiling seperti drill yang bisa diulang.
Tulis runbook singkat yang tim Anda bisa ikuti saat lelah. Harus menyebut apa yang ditangkap, kapan menangkap, dan bagaimana membandingkannya dengan baseline yang dikenal baik. Buat praktis: perintah tepat, ke mana profil disimpan, dan apa yang terlihat "normal" untuk allocator teratas Anda.
Tambahkan monitoring ringan untuk tekanan alokasi sebelum Anda mencapai OOM: ukuran heap, siklus GC per detik, dan byte yang dialokasikan per request. Menangkap “alokasi per request naik 30% week-over-week” sering lebih berguna daripada menunggu alarm memori keras.
Dorong pengecekan lebih awal dengan uji beban singkat di CI pada endpoint representatif. Perubahan respons kecil bisa menggandakan alokasi jika memicu salinan ekstra, dan lebih baik menemukannya sebelum trafik produksi melakukannya.
Jika Anda menjalankan backend Go yang dihasilkan, ekspor source dan profil dengan cara yang sama. Kode yang dihasilkan tetap kode Go, dan pprof akan menunjuk fungsi dan baris nyata.
Jika kebutuhan Anda sering berubah, AppMaster (appmaster.io) bisa menjadi cara praktis untuk membangun ulang dan meregenerasi backend Go yang bersih saat aplikasi berkembang, lalu memprofil kode yang diekspor di bawah beban realistis sebelum dikirim.
FAQ
Lonjakan biasanya meningkatkan laju alokasi lebih dari yang Anda perkirakan. Bahkan objek sementara kecil per-request bertambah secara linear dengan RPS, yang memaksa GC berjalan lebih sering dan bisa mendorong RSS naik meskipun heap hidup tidak besar.
Metrik heap melacak memori yang dikelola Go, tetapi RSS mencakup lebih banyak: stack goroutine, metadata runtime, pemetaan OS, fragmentasi, dan alokasi non-heap (termasuk beberapa penggunaan cgo). Normal jika RSS dan heap bergerak berbeda selama lonjakan; gunakan pprof untuk menemukan titik panas alokasi daripada mencoba “mencocokkan” RSS persis.
Mulai dengan profil heap saat Anda curiga ada retensi (sesuatu tetap hidup), dan profil berfokus alokasi (seperti allocs/alloc_space) saat Anda curiga churn (banyak objek short-lived). Selama lonjakan lalu lintas, churn seringkali masalah nyata karena mendorong waktu CPU GC dan latensi tail.
Setup paling sederhana dan aman adalah menjalankan pprof pada server admin terpisah yang dibind ke 127.0.0.1, dan hanya membuatnya dapat diakses lewat akses internal. Perlakukan pprof seperti antarmuka admin karena bisa mengekspos detail internal layanan Anda.
Ambil garis waktu singkat: satu profil beberapa menit sebelum lonjakan (baseline), satu selama lonjakan (impact), dan satu setelahnya (recovery). Ini memudahkan melihat apa yang berubah dibanding hanya menangkap satu snapshot.
Gunakan inuse untuk menemukan apa yang benar-benar tertahan pada saat capture, dan gunakan alloc_space (atau alloc_objects) untuk menemukan apa yang banyak dialokasikan. Kesalahan umum adalah hanya melihat inuse dan melewatkan churn yang menyebabkan GC thrash dan lonjakan latensi.
Jika encoding/json mendominasi alokasi, biasanya penyebabnya adalah bentuk data Anda, bukan paket itu sendiri. Mengganti map[string]any dengan struct bertipe, menghindari json.MarshalIndent, dan tidak membangun JSON lewat string sementara biasanya mengurangi alokasi dengan segera.
Memindai baris ke target fleksibel seperti interface{} atau map[string]any, mengubah []byte ke string untuk banyak kolom, dan mengambil terlalu banyak baris/kolom dapat mengalokasikan banyak per-request. Memilih hanya kolom yang dibutuhkan, paging hasil, dan memindai langsung ke field struct konkrit adalah perbaikan berdampak tinggi.
Middleware berjalan di setiap request, jadi alokasi kecil menjadi besar saat beban naik. Logging yang membangun string baru, tracing yang membuat label kardinalitas tinggi, pembuatan ID permintaan, pembuat gzip per-request, dan menyimpan objek baru dalam context dapat muncul sebagai churn alokasi konsisten di profil.
Ya — pendekatan berbasis profil yang sama berlaku untuk kode Go apa pun, baik di-generate maupun ditulis tangan. Jika Anda mengekspor source dari backend yang dihasilkan, jalankan pprof, identifikasi jalur yang mengalokasikan, lalu sesuaikan model, handler, dan logika lintas-lapisan untuk mengurangi alokasi per-request sebelum lonjakan berikutnya.


