# Goroutine sızıntılarını uzun süre çalışan bir Go servisinde nasıl tespit edip önlerim?

> NumGoroutine()'i dışa açıp zirvede pprof goroutine dökümü alın, binlerce kopyalı park etmiş stack'in spawn noktasını ctx.Done()'a uydurun, goleak ekleyin.

- Soruldu: 2026-07-06
- Yanıtlandı: 2026-07-10
- Soran: Kerem
- Etiketler: go, concurrency
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/goroutine-sizintilarini-uzun-sure-calisan-bir-go-servisinde-nasil-tespit-edip/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Go ile yazdığım bir veri ingestion servisi birkaç gün içinde yavaş yavaş on binlerce goroutine'e tırmanıyor. Servis event başına `go func()` ile iş fırlatıyor, channel'lar ve birkaç dış HTTP çağrısı var.

Sorun şu: hangi spawn noktasının sızdırdığını bir türlü çıkaramıyorum. Bu sızıntıyı üretimde nasıl tespit ederim, kök nedenini nasıl bulurum ve bir daha olmaması için nasıl önlerim?


Kısa cevap: Sayının istikrarlı biçimde tırmanması, bir channel/lock/IO üzerinde sonsuza dek bloklanmış ve iptal yolu olmayan goroutine'ler demektir. Önce ölçün ve dökümü alın, sonra spawn noktasının sözleşmesini düzeltin.

İçgüdü, artan sayıyı yüke bağlamaktır; ama sızıntının bir spike'ta olmayan bir imzası vardır: asla geri inmez. Koda dokunmadan önce bu ikisini ayırın, çünkü tamamen farklı çözümler gerektirirler.

1. **Önce sayıyı doğrulayın.** `runtime.NumGoroutine()` değerini bir metrik olarak dışa açın. Sayı yükle birlikte monoton artıyor ve boşta bile geri inmiyorsa, bu bir spike değil sızıntıdır. Bu ayrım tüm teşhisin temelidir.
2. **pprof goroutine dökümü kanıttır.** `net/http/pprof`'u bağlayın ve `/debug/pprof/goroutine?debug=2` (ham stack'ler) ya da `debug=1` (gruplanmış) çıktısını alın. Binlerce kopyası olan stack, hepsi aynı channel recv/send veya `select` üzerinde park etmiş hâlde, tam da sızan noktadır.
3. **Kök neden neredeyse her zaman eksik iptaldir.** Okuyucusu gitmiş bir channel'a yazmaya çalışan goroutine, hiç kapanmayan bir `for range ch`, ya da context/timeout'suz bir HTTP çağrısı. Her goroutine'in bir çıkış yolu olmalı: `ctx.Done()`, kapanan bir channel veya sınırlı iş.
4. **Context'i her yere taşıyın.** `context.Context`'i aşağıya geçirin, `select` içinde ona uyun ve tüm ağ çağrılarına timeout koyun (`http.Client.Timeout`, DB için query context). Takılmış bir ağ okuması üzerinde park eden goroutine, süreç ölene dek sızar.
5. **Sızıntıyı test edin.** `go.uber.org/goleak`'i `TestMain` içinde kullanın; test bitince hayatta kalan goroutine varsa test'i düşürsün. Yeni sızıntıları spawn noktası bazında, daha üretime çıkmadan yakalamanın en ucuz yolu budur.
6. **Eşzamanlılığı sınırlayın.** Event başına sınırsız `go func()` yerine worker pool / semaphore kullanın. Sınırsız spawn modelindeki bir sızıntının tavanı yoktur; havuz, hasarı en baştan sınırlar.

```go
func worker(ctx context.Context, jobs <-chan Job) {
    for {
        select {
        case <-ctx.Done(): // her goroutine'in çıkış yolu
            return
        case j, ok := <-jobs:
            if !ok {
                return
            }
            process(ctx, j)
        }
    }
}
```

```bash
# Üretimde zirve anında park etmiş stack'i bul:
curl -s 'http://localhost:6060/debug/pprof/goroutine?debug=2' | less
```

**Sonuç:** Ben olsam `NumGoroutine()`'i hemen bir dashboard'a ve pprof endpoint'ini servise bağlardım; zirvede bir `goroutine?debug=2` dökümü alıp binlerce kopyalı park etmiş stack'i bulur, o spawn noktasının `ctx.Done()`'a uymasını sağlardım. Sonra `goleak`'i ekleyip bir daha regresyon olmamasını garantilerdim. Sızıntı bir "gizem" değildir; döküm size satırı gösterir.

## İlgili Yazılar

- [Go'da eşzamanlılık: goroutine ve channel pratiği](/blog/goda-eszamanlilik-goroutine-ve-channel-pratigi/) — Blog
- [Birbirine bağımlı birden fazla işi, ilk hata hepsini iptal edecek şekilde errgroup ile mi yürütmeliyim?](https://www.muhammetsafak.com.tr/sor-bakalim/birbirine-bagimli-birden-fazla-isi-ilk-hata-hepsini-iptal-edecek-sekilde/) — Sor Bakalım
- [İç içe geçmiş servis çağrılarında context iptalini doğru şekilde nasıl yayarım?](https://www.muhammetsafak.com.tr/sor-bakalim/ic-ice-gecmis-servis-cagrilarinda-context-iptalini-dogru-sekilde-nasil-yayarim/) — Sor Bakalım
- [Go CLI aracını GoReleaser ve Homebrew ile çoklu platforma nasıl dağıtırım?](https://www.muhammetsafak.com.tr/sor-bakalim/go-cli-aracini-goreleaser-ve-homebrew-ile-dagitmak/) — Sor Bakalım
