Go'da GC baskısını sync.Pool ve escape analizi ile nasıl azaltırım?
Soru
Go ile yazdığımız bir veri ingest servisi saniyede 50.000 küçük JSON paketi alıp işleyip veritabanına yazıyor. Yoğun yükte GC devreye girince CPU tavan yapıyor ve "Stop-the-World" duraklamaları API yanıt sürelerinde spike yaratıyor. Bellekteki nesne tahsisini azaltmak için `sync.Pool` ve escape analysis'i bu senaryoda nasıl uygularım?
Cevap
Kısa cevap: GC duraklamaları bir semptom; asıl düşman tahsis hızı (allocation rate). Önce tahsisleri kes, GC zaten rahatlar.
Yaşadığın spike’lar GC’nin “kötü” olmasından değil — saniyede 50k paket, saniyede yüz binlerce kısa ömürlü nesne demek; GC bu çöpü toplamak için sürekli ve agresif çalışıyor, Stop-the-World duraklamaları da yanıt sürene yansıyor. Çözüm GC’yi kapatmak değil, ona daha az iş vermek.
- Hot path’te
sync.Poolile yeniden kullan. Ingest yolundaki kısa ömürlü buffer’ları, struct’ları ve decoder’ları her pakettenew’lemek yerine havuzdan al-geri ver. Kritik detay:Putetmeden önce nesneyi resetle ki eski veri sızmasın. Bu, en sıcak yoldaki tahsis basıncını dramatik düşürür. - Tahsisi kökten azalt. Slice/map’leri biliyorsan baştan
make([]T, 0, n)ile boyutlandır;bytes.Bufferve JSON decoder’ı tekrar kullan; gereksiz[]byte↔stringkopyalarından kaçın. Tek bir kopya 50k kez çarpınca dağ olur. - Escape analizini çalıştır, stack’te tut.
go build -gcflags=-mçıktısı sana hangi değerin heap’e “kaçtığını” (escape) söyler. Bir pointer’ı fonksiyondan dışarı kaçırmak, bir değeri interface’e koymak çoğu kaçışın nedenidir; bunları stack’te tutarsan GC o nesneyi hiç görmez — en ucuz tahsis, hiç yapılmayan tahsistir. - Kör gitme:
pprofile önce/sonra ölç.pprof’unalloc_spaceprofili sana en çok tahsis eden satırı söyler;GODEBUG=gctrace=1ile GC sıklığını ve duraklama süresini izle. Değişikliği uygulamadan önce ve sonra aynı yükle ölç — “iyileştirdim” diye düşündüğün şey çoğu zaman ölçünce yerinde sayıyordur. - Pooling’in tuzağına dikkat. Başka heap verisine pointer tutan nesneleri havuzlamak ters tepebilir: havuz o nesneyi canlı tuttuğu için işaret ettiği büyük graf da serbest kalmaz, bellek beklediğinden çok şişer. Havuzda yalnızca düz, kendi içinde kapalı buffer/struct’ları tut.
Sonuç: Sıralama net: profille → en çok tahsis edeni öldür → hayatta kalanları havuzla → sonra GOGC/GOMEMLIMIT ayarla. GOGC ve soft memory limit (GOMEMLIMIT) GC’yi seyrekleştirir ama saniyede 50k tahsis yapan bir yolu düzeltmez; onlar son ince ayar, ilk hamle değil. Asıl kazanç, GC’ye en sıcak yolda hiç çöp üretmemekten gelir.
İlgili Yazılar
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.