VDS (Sanal Özel Sunucu) üzerinde Go dilinde yazılmış web uygulamalarını çalıştırırken, ilk haftalarda sorunsuz çalışan uygulama, zaman geçtikçe RAM tüketimini artırmaya başlıyor; sunucunun belleği yavaş yavaş tükeniyor ve OOM (Out of Memory) Killer süreci kapatmaya başlıyor. CPU ve disk kullanımı normal olsa bile, sadece RAM sorunu yaşıyorsanız, büyük ihtimalle goroutine sızıntısı ile karşı karşıyasınız.

Goroutine Sızıntısı Nedir?

Goroutine, Go dilinin hafif ve hızlı eş zamanlılık modelidir. Ancak bir goroutine hiçbir zaman sonlandırılmazsa, kullandığı bellek serbest bırakılmaz ve sistem belleğine kademeli olarak yüklenmeye başlar. Bu, özellikle HTTP sunucuları, WebSocket bağlantıları ve uzun süren işlemleri yönetmek zorunda olan uygulamalarda yaygındır.

Örneğin, her gelen HTTP isteği için bir goroutine başlatan fakat hiçbir zaman kapatmayan bir uygulama, birkaç saat içinde binlerce ölü goroutine biriktirerek sunucuyu felç edebilir.

Goroutine Sızıntısını Nasıl Tespit Edersiniz?

Go uygulamalarında bellek sorununu teşhis etmek için pprof (Go'nun yerleşik profiling aracı) kullanabilirsiniz. Uygulamanıza aşağıdaki import'u ekleyin:

import _ "net/http/pprof"

Ardından uygulamanıza bir HTTP listener ekleyin (örneğin 6060 portunda). Daha sonra tarayıcıda veya komut satırında şu adrese erişin:

curl http://localhost:6060/debug/pprof/goroutine

Bu, şu an kaç tane goroutine çalıştığını gösterir. Eğer bu sayı saatler içinde 1000'lere ulaşıyorsa, goroutine sızıntısı yaşıyorsunuz demektir. Bellek profili için şu komutu çalıştırın:

go tool pprof http://localhost:6060/debug/pprof/heap

Bu, hangi fonksiyonların en fazla bellek tahsis ettiğini gösterir.

Yaygın Goroutine Sızıntısı Senaryoları

  • Context iptal edilmeyen işlemler: HTTP isteğinde bir goroutine başlatırken context.Context kullanmamak, istek bitince de goroutine'yi sonlandırmamak
  • Buffersiz channel deadlock'ları: Buffersiz channel'a veri gönderin fakat hiç okuyan olmuyorsa, goroutine beklemede kalır
  • Timer ve Ticker unutkanlığı: time.NewTimer veya time.NewTicker oluşturduktan sonra .Stop() çağırmamak
  • Database bağlantı havuzu kapatılmaması: Veritabanı bağlantısı açık bırakılarak goroutine'ler asılı kalması
  • HTTP istemci timeout'u ayarlanmadığında: Dış API'ye bağlanırken timeout olmadığında sonsuz bekleme

Goroutine Sızıntısını Düzeltme Yöntemleri

İlk adım, isteğin context.Context kullanmasıdır. Örneğin:

func handleRequest(w http.ResponseWriter, r *http.Request) {\n ctx := r.Context()\n go processWithContext(ctx)\n}

Bu şekilde, HTTP isteği iptal edilirse, goroutine'nin bunu algıması ve sonlanması mümkün olur.

İkinci adım, açılan kaynakları (channel, timer, db bağlantısı) Her zaman kapatmaktır. Defer kullanarak bu işi otomatikleştirin:

defer cancel()\ndefer db.Close()\ndefer ticker.Stop()

Üçüncü adım, HTTP istemcilerine timeout eklemektir:

client := &http.Client{\n Timeout: 30 * time.Second,\n}

VDS Ortamında Goroutine Sızıntısıyla Başa Çıkma

VDS sunucusunda, bellek izlemesi ve otomatik yeniden başlatma mekanizması oluşturmak gerekir. Systemd service dosyasında Restart=always ve RestartSec=10 ekleyin, böylece uygulama çöktüğünde otomatik yeniden başlatılır. Ancak bu geçici bir çözümdür—asıl çözüm, kodunuzdaki goroutine sızıntısını gidermektir.

Ek olarak, memory monitoring araçları (prometheus, grafana) kullanarak bellek eğilimini gerçek zamanlı takip edin. RAM kullanımı saatte %1-2 oranında artıyorsa, sorunu erkenden fark edersiniz.

Sonuç

VDS'de Go uygulaması çalıştırırken, goroutine sızıntısı en önemli bellek sorunu kaynaklarından biridir. pprof ile tanı koyun, context kullanıp kaynakları doğru yönetin ve timeout ayarlarını belirleyin. Bu basit adımlar, sunucunuzun saatler içinde collapse olmasını önleyecektir.

Bogahost'un yönetimli VDS hizmetlerinde, teknik destek ekibi bu tür performans sorunlarını tespit ve çözmekte yardımcı olabilir.