İçeriğe geç
Muhammet Şafak
en
Soran: Arda Cevaplandı:

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 kesin, GC zaten rahatlar.

Kısa cevap

Yaşadığınız 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üresine yansıyor. Çözüm GC’yi kapatmak değil, ona daha az iş vermek. Bu yolun eşzamanlılık tarafını Go’da goroutine ve channel pratiği yazısında ele almıştım.

Neden

  1. Duraklamanın kaynağı tahsis sıklığıdır. GC ne kadar çöp üretirseniz o kadar sık koşar; ayarı değiştirmek üretimi değiştirmez.

  2. En ucuz tahsis, hiç yapılmayandır. Bir değeri stack’te tutabiliyorsanız GC o nesneyi hiç görmez — havuzlamaktan da ucuzdur.

  3. Pooling’in bir ters tepme riski var. Başka heap verisine pointer tutan bir nesneyi havuzlarsanız, havuz onu canlı tuttuğu için işaret ettiği büyük graf da serbest kalmaz; bellek beklediğinizden çok şişer.

Ne yapmalı

  1. Kör gitmeyin: pprof ile önce/sonra ölçün. alloc_space profili en çok tahsis eden satırı söyler; GODEBUG=gctrace=1 GC sıklığını ve duraklama süresini gösterir. Aynı yükle önce ve sonra ölçün.

  2. Tahsisi kökten azaltın. Slice/map’leri biliyorsanız baştan make([]T, 0, n) ile boyutlandırın; bytes.Buffer ve JSON decoder’ı tekrar kullanın; gereksiz []bytestring kopyalarından kaçının. Tek bir kopya 50k kez çarpınca dağ olur.

  3. Escape analizini çalıştırın, stack’te tutun. go build -gcflags=-m çıktısı hangi değerin heap’e kaçtığını söyler. Bir pointer’ı fonksiyondan dışarı kaçırmak ya da bir değeri interface’e koymak çoğu kaçışın nedenidir.

  4. Hot path’te sync.Pool ile yeniden kullanın. Ingest yolundaki kısa ömürlü buffer’ları, struct’ları ve decoder’ları havuzdan al-geri verin. Put etmeden önce nesneyi resetleyin ki eski veri sızmasın. Havuzda yalnızca düz, kendi içinde kapalı buffer/struct’ları tutun.

  5. GOGC/GOMEMLIMIT ayarını en sona bırakın. GC’yi seyrekleştirirler ama saniyede 50k tahsis yapan bir yolu düzeltmezler; son ince ayar, ilk hamle değil.

Sonuç: Sıralama net: profilleyin → en çok tahsis edeni öldürün → hayatta kalanları havuzlayın → sonra GOGC/GOMEMLIMIT ayarlayın. Asıl kazanç, GC’ye en sıcak yolda hiç çöp üretmemekten gelir.

İlgili Yazılar

Etiketler: #Performans#Go
Uzmanlık: Go Geliştirici
Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi