GO PERFORMANS ANALİZİ: PPROF İLE CPU/MEMORY PROFİLİNG VE BOTTLENECK BULMA
Go ile yazdığınız bir servis “çalışıyor” olabilir; ama gerçek kullanıcı yükü geldiğinde gecikmeler artar, CPU tavan yapar ya da bellek sızıntısı sanılan bir tablo ortaya çıkar. Bu noktada tahmin yürütmek yerine, kodun gerçekten nerede zaman ve bellek harcadığını ölçmek gerekir.
Go ekosisteminde bu işin standart aracı pprof’tur. Doğru toplandığında CPU profilinden fonksiyon bazında “kim ne kadar çalışıyor?” cevabını alır; heap profiliyle bellek tahsislerinin nereden geldiğini görür; goroutine profiliyle takılan iş akışlarını yakalayabilirsiniz.
Bu makalede pprof ile CPU/memory profiling akışını uçtan uca ele alacağız: uygulamaya ekleme, profil alma, pprof çıktısını okuma, darboğaz (bottleneck) doğrulama ve üretimde güvenli kullanım. Daha kapsamlı pratik ve mimari perspektif için Go eğitimi sayfasına da göz atabilirsiniz.
pprof nedir ve ne zaman kullanılır?
pprof, Go’nun standart kütüphanesiyle entegre çalışan bir profil toplama ve analiz yaklaşımıdır. “Performans problemi” tek bir şeye indirgenmez; CPU doygunluğu, kilit (lock) beklemeleri, bellek tahsis patlaması, GC baskısı, I/O gecikmesi gibi çok farklı kök nedenler olabilir. pprof, bu farklı semptomları ölçülebilir hale getirerek optimizasyonu rastgele denemelerden kurtarır.
En kritik prensip: ölçmeden optimize etmeyin. Önce temel metrikleri (p95/p99 latency, RPS, CPU, RSS, GC pause, allocation rate) görün; sonra profilleme ile “neden”i bulun. Aksi halde yanlış yeri optimize edip, daha fazla karmaşıklık ekleyebilirsiniz.

CPU profili, heap profili ve diğerleri
CPU profiling, örnekleme (sampling) yaklaşımıyla CPU üzerinde hangi fonksiyonların ne kadar süre çalıştığını gösterir. Özellikle sıcak yolları (hot path) bulmak için idealdir. Heap profiling ise bellek tahsislerinin nereden geldiğini, hangi kod yollarının bellek büyümesine katkı verdiğini ortaya çıkarır.
Bunlara ek olarak goroutine profili (blokajlar, beklemeler), block profili (senkronizasyon beklemeleri) ve mutex profili (kilit çakışmaları) gibi türler de vardır. Hangi profil türünü seçeceğiniz, semptomun ne olduğuna bağlıdır.
Profiling overhead ve “doğru ortam” seçimi
Profiling, sistemin davranışını az da olsa değiştirir. Özellikle yüksek trafik altında sık profil toplamak, CPU ve bellek kullanımını artırabilir. Bu yüzden önce staging benzeri bir ortamda yaklaşımı doğrulayın; üretimde ise kısa süreli, kontrollü ve yetkilendirilmiş şekilde toplayın.
Özet kural: kısa, hedefli, güvenli. Sorunu yakalayacak kadar veri toplayın; “her şeyi sürekli toplayayım” yaklaşımı yerine tetikleyici senaryolara odaklanın.
Uygulamaya pprof ekleme: HTTP servislerde hızlı kurulum
Bir HTTP servisi geliştiriyorsanız en pratik yöntem net/http/pprof paketini import etmektir. Bu paket /debug/pprof altında endpoint’leri açar ve sizin ek kod yazmadan profil toplamayı mümkün kılar. Ancak üretimde bu endpoint’leri mutlaka korumalı hale getirin; aksi halde hassas sistem bilgisi sızabilir.
net/http/pprof ile endpoint’leri etkinleştirme
Aşağıdaki örnek, ayrı bir admin portunda pprof servis etmeyi gösterir. Bu yaklaşım, uygulama trafiğiyle yönetim trafiğini ayırır ve erişim kontrolünü kolaylaştırır.
package main
import (
"log"
"net/http"
_ "net/http/pprof"
"time"
)
func main() {
// Uygulama endpoint'i (örnek)
app := http.NewServeMux()
app.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
go func() {
srv := &http.Server{
Addr: ":8080",
Handler: app,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.Println("app listening on :8080")
log.Fatal(srv.ListenAndServe())
}()
// Admin/pprof endpoint'leri ayrı portta
admin := http.NewServeMux()
// net/http/pprof import'u ile /debug/pprof otomatik bağlanır
admin.Handle("/debug/pprof/", http.DefaultServeMux)
adminSrv := &http.Server{
Addr: "127.0.0.1:6060",
Handler: admin,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}
log.Println("pprof listening on 127.0.0.1:6060")
log.Fatal(adminSrv.ListenAndServe())
}Bu örnekte pprof yalnızca localhost’tan erişilebilir. Üretimde tipik olarak bu portu sadece bastion/VPN üzerinden erişilecek şekilde sınırlandırmak veya bir servis mesh/policy ile korumak tercih edilir.
CLI/worker uygulamalarda runtime/pprof ile dosyaya yazdırma
Eğer bir HTTP servisi değil de batch job, CLI ya da worker çalıştırıyorsanız profil dosyasını belirli bir süre boyunca toplayıp diske yazdırabilirsiniz. Bu yöntem, kontrollü senaryolarda hızlıca “hangi fonksiyon CPU yiyor?” sorusuna cevap verir.
package main
import (
"log"
"os"
"runtime/pprof"
"time"
)
func main() {
f, err := os.Create("cpu.pprof")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal(err)
}
// 15 saniyelik örnekleme
time.Sleep(15 * time.Second)
pprof.StopCPUProfile()
log.Println("cpu profile written to cpu.pprof")
}Dosya tabanlı yaklaşımın avantajı, ortamdan bağımsız olmasıdır. Dezavantajı ise canlı trafik altındaki davranışı temsil etmesi için doğru yükle çalıştırma gerekliliğidir.

CPU profiling ile bottleneck yakalama ve doğrulama
CPU profili, darboğaz bulmada ilk duraktır. Çünkü latency artışının önemli bir bölümü CPU üzerinde “beklenenden fazla iş” yapılmasından kaynaklanır: gereksiz JSON parse, fazla string kopyalama, pahalı regex, yüksek kilit rekabeti, verimsiz algoritma veya N+1 benzeri uygulama hataları.
Önce doğru senaryoyu kurun: sorun hangi endpoint’te, hangi payload ile, hangi concurrency seviyesinde çıkıyor? Profil toplarken bu senaryoyu tekrar edebilir olmanız, sonra yaptığınız değişikliklerin etkisini net gösterecektir.
go test ile cpuprofile almak
Kütüphane veya paket seviyesinde performans konuşuyorsanız benchmark + cpuprofile kombinasyonu çok etkilidir. Bu sayede “kodu izole ettim, sadece şu fonksiyonu ölçüyorum” diyebilirsiniz.
# Benchmark çalıştır ve CPU profili üret
go test -run '^$' -bench . -benchmem -cpuprofile cpu.out ./...
# pprof ile incele (interaktif)
go tool pprof ./your.test cpu.outBenchmark’ta -benchmem kullanmak, allocation davranışını da görmenizi sağlar. CPU profiliyle birlikte allocation sayısı artıyorsa, muhtemelen gereksiz tahsisler CPU’yu da yükseltiyordur.
HTTP pprof ile CPU profili toplama
Serviste pprof endpoint’leri açıkken 30 saniyelik CPU profili almak yaygın bir yaklaşımdır. Süreyi sorunun görünür olduğu zaman dilimine göre ayarlayın; çok kısa olursa sinyal zayıf, çok uzun olursa veri gürültülü olabilir.
# 30 saniyelik CPU profili indir
curl -o cpu.pprof "http://127.0.0.1:6060/debug/pprof/profile?seconds=30"
# Web arayüzü ile görselleştir
go tool pprof -http=":8081" cpu.pprofWeb arayüzünde genellikle “Top”, “Graph”, “Flame Graph” gibi görünümler bulunur. Hedefiniz, en üstte görünen fonksiyonları körlemesine optimize etmek değil; çağrı zincirini anlayıp, kök nedeni bulmaktır.
pprof içinde en faydalı komutlar: top, list, web
pprof etkileşimli modda şu komutlar, bottleneck bulma hızınızı ciddi artırır:
- top: En fazla CPU tüketen fonksiyonları sıralar (flat ve cum değerleriyle).
- top -cum: Çağrı zinciri dahil toplam maliyeti öne çıkarır; “asıl yük nereden geliyor?” için kullanışlıdır.
- list Foo: Belirli fonksiyonun satır satır profilini gösterir; gerçek optimizasyon noktası burasıdır.
- weblist Foo: Satır bazlı çıktıyı tarayıcıda daha okunur sunar.
- focus / ignore: Gürültüyü azaltır; örneğin sadece kendi paketlerinize odaklanabilirsiniz.
Önemli bir tuzak: “flat” yüksek diye her zaman orayı optimize etmek doğru değildir. Bazı fonksiyonlar doğal olarak sıcak çalışır; asıl iyileştirme, o fonksiyona giden gereksiz çağrıları azaltmak veya daha ucuz bir yaklaşım seçmek olabilir.
Memory profiling: heap, allocation ve GC baskısını anlama
Bellek problemleri çoğu zaman iki şekilde görünür: süreç belleği sürekli artar (sızıntı şüphesi) veya bellek dalgalanır ama GC çok sık çalışır (allocation rate yüksek). Heap profili, bu davranışın kaynağını göstermek için idealdir.
Go’da “sızıntı” her zaman klasik anlamda unutulmuş pointer değildir. Uzun ömürlü cache’ler, büyüyen map’ler, goroutine’lerin tuttuğu referanslar, büyük slice’ların küçük parçalar yüzünden canlı kalması gibi durumlar bellek büyümesine yol açabilir.
Heap profili alma ve temel metrikler
HTTP pprof ile heap profili almak için:
curl -o heap.pprof "http://127.0.0.1:6060/debug/pprof/heap"
go tool pprof -http=":8082" heap.pprofHeap profilinde iki temel bakış açısı vardır: inuse (şu an elde tutulan bellek) ve alloc (toplam tahsis edilen bellek). Eğer “bellek niye büyüyor?” diyorsanız inuse; “neden GC bu kadar sık?” diyorsanız alloc tarafı daha anlamlı olabilir.
pprof içinde örnek filtreler:
- sample_index=inuse_space: Şu an kullanılan baytları öne çıkarır.
- sample_index=alloc_space: Toplam tahsis edilen baytları öne çıkarır.
- top -cum: Tahsis zincirinin nereden geldiğini görmeyi kolaylaştırır.
Allocation rate, kaçış (escape) ve gereksiz kopyalar
Allocation rate yüksekse CPU da yükselir; çünkü GC daha fazla iş yapar. Bu durumda yalnızca “heap küçük kalsın” değil, tahsis sayısını azaltmak hedeflenir. Sık görülen sebepler:
- Her istek için yeni buffer oluşturmak yerine buffer pool kullanmamak.
- String birleştirmelerde verimsiz yaklaşım (tekrarlı kopya).
- Interface kullanımının aşırı olduğu sıcak kod yollarında kaçışların artması.
- JSON encode/decode işlemlerinde gereksiz ara nesneler.
Heap profilinde en üstte çıkan fonksiyonu gördüğünüzde, “bunu nasıl azaltırım?” sorusunu ikiye bölün: (1) Bu kod yoluna daha az girebilir miyim? (2) Giriyorsam tahsisleri nasıl azaltırım? Bu ikisi birlikte düşünülmediğinde optimizasyonlar sınırlı kalır.

Flame graph ve çağrı zinciri üzerinden okumak
Flame graph, performans analizinde “hikayeyi” hızlı okumanızı sağlar. Geniş bloklar, daha fazla örnek (dolayısıyla daha fazla CPU zamanı) demektir. Dikeyde çağrı derinliği artar; yatayda toplam yük dağılır. Bu sayede tek bir fonksiyona değil, tüm çağrı zincirine bakarak doğru müdahale noktasını seçersiniz.
Web arayüzü, SVG ve Graphviz çıktıları
pprof web arayüzü çoğu durumda yeterlidir. Ancak rapor paylaşmak veya CI çıktısına eklemek istediğinizde SVG/Graphviz işe yarar. Bazı ortamlarda Graphviz kurulumu gerekebilir; kurulumu yoksa web arayüzüyle başlamanız daha hızlı olur.
Pratik bir yaklaşım:
- Önce top -cum ile büyük resmi görün.
- Sonra focus ile kendi paketlerinize daraltın.
- Ardından list ile satır bazına inin.
“Sıcak fonksiyon” ile “gerçek kök neden”i ayırmak
Örneğin bir JSON marshal fonksiyonu üstte çıkabilir; ama asıl problem, aynı veriyi gereksiz yere iki kez serialize etmeniz olabilir. Ya da bir kripto fonksiyonu yoğun görünebilir; fakat asıl sorun, aynı istekte iki kez doğrulama yapmanızdır. Flame graph burada yardımcı olur: çağrı zinciri, gereksiz tekrarları yakalamanızı sağlar.
Bu aşamada hedef, mikro optimizasyon değil; işi azaltmaktır. Daha az iş, en sağlam performans artışıdır.
Üretimde profiling stratejileri: güvenlik ve operasyonel gerçekler
Üretimde pprof kullanmak mümkündür ama disiplin ister. Endpoint’leri herkesin erişimine açmak, servis iç yapısını ifşa edebilir. Ayrıca profiling süresi ve sıklığı kontrol edilmezse sistem üzerinde ek yük yaratır.
Erişim kontrolü ve izolasyon
Güvenli yaklaşım, pprof’u ayrı bir admin portunda sunmak ve bu porta erişimi kısıtlamaktır. Öneriler:
- Admin portunu sadece localhost veya özel ağ arayüzünde dinleyin.
- VPN/bastion üzerinden erişim verin, gerekirse ters proxy ile kimlik doğrulama ekleyin.
- Üretim loglarında profil indirme çağrılarını izleyin (kim, ne zaman, ne aldı).
Bu sayede hem güvenlik hem de denetlenebilirlik sağlanır.
Kısa süreli, hedefli profil toplama
CPU profili için 15–60 saniye aralığı çoğu senaryoda yeterlidir. Heap profili ise anlık fotoğraf gibidir; “şimdi ne tutuluyor?” sorusuna cevap verir. Sorun dönemsel ise belirli aralıklarla örnek alıp karşılaştırma yapmak gerekebilir.
İyi bir pratik, değişiklik öncesi ve sonrası aynı senaryoda profil alıp karşılaştırmaktır. Böylece “hızlandı mı?” sorusu netleşir. Performans iyileştirmeleri genellikle yan etkilidir; bir metrik iyileşirken başka bir metrik kötüleşebilir. Bu yüzden tek bir sayıya odaklanmayın.
Yaygın hatalar ve en iyi pratikler
pprof ile çalışırken en sık yapılan hata, tek bir profil çıktısına bakıp kesin karar vermektir. Profil, belirli bir yük ve zaman diliminde alınmış örnektir; sorun aralıklıysa veya yük değişkense tek örnek sizi yanıltabilir. Bu nedenle aynı senaryoda birkaç tekrar alıp ortak desenleri arayın.
Yanlış senaryo ile profil almak
Gerçek performans sorunları çoğu zaman belirli bir endpoint, payload, concurrency veya veri dağılımında görünür. Eğer profil alırken bu koşulları yansıtmıyorsanız “temiz” bir profil görür ve sorunu kaçırırsınız. Bu yüzden sorun raporunu senaryoya çevirmek, profiling’in yarısıdır.
Gürültüyü azaltmadan yorum yapmak
Profilde standart kütüphane ve runtime çağrıları doğal olarak görünür. “Her şey runtime’da” hissine kapılmamak için focus ile kendi modüllerinize yaklaşın. Ardından çağrı zincirini izleyerek runtime’a sizi hangi yolun götürdüğünü bulun.
Son olarak, pprof bulgularını aksiyona dökerken küçük ve ölçülebilir adımlar atın: tek bir değişiklik, tek bir ölçüm, net bir karşılaştırma. Böylece optimizasyon süreci kontrol edilebilir ve güvenilir olur.
pprof, Go performans analizi için güçlü bir araçtır; doğru kullanıldığında CPU profiling ile sıcak noktaları, memory profiling ile tahsis kaynaklarını, çağrı zinciri analizleriyle de gerçek darboğazları görünür kılar. Ölç, anla, küçük adımlarla iyileştir; en sürdürülebilir performans yaklaşımı budur.


