Mert Tosun
← Yazılar
Go'da Context, Timeout ve Cancellation: Production Dayanıklılık Rehberi

Go'da Context, Timeout ve Cancellation: Production Dayanıklılık Rehberi

Mert TosunGo

Go ile servis yazarken en sık görülen üretim problemlerinden biri şudur:
istek bitmiş olmasına rağmen alt işlemler çalışmaya devam eder, veritabanı bağlantıları gereksiz tutulur ve kuyruklar yavaş yavaş dolar.

Bu noktada context.Context, sadece bir fonksiyon parametresi değil, sistemin yaşam döngüsü protokolü haline gelir.

Bu yazıda:

  1. Context zinciri nasıl kurulur,
  2. Timeout değerleri nasıl katmanlara dağıtılır,
  3. Cancellation sinyali nasıl doğru taşınır,
  4. Shutdown sırasında nasıl kontrollü kapanış yapılır

adım adım inceleyeceğiz.


1) Context neden kritik?

context.Context size üç temel şey sağlar:

  • Deadline/timeout (işin ne kadar sürede bitmesi gerektiği),
  • Cancellation (iş artık gereksizse durma sinyali),
  • Request scope değerleri (trace id gibi küçük metadata).

Mikroservis zincirinde bir üst katman isteği iptal ettiğinde alt katmanların da durması gerekir.
Bu davranış yoksa "zombi iş" problemi büyür.


2) Timeout bütçesi: tek yerde değil, katmanlı

Yaygın hata: API gateway 5 saniye timeout verir, handler 10 saniye, DB çağrısı 30 saniye bekler.
Sonuç: istek dışarıdan biter, içeride işlem devam eder.

Doğru yaklaşım: üst timeout > alt timeout değil, tersine alt katmanlar toplam bütçeye göre daha kısa olmalı.

Örnek dağılım:

  • HTTP request toplam: 3s
  • Service katmanı: 2.5s
  • DB sorgusu: 1.5s
  • External API: 1.2s

Her katmanda alt context üretmek net kontrol sağlar.

func (h *Handler) GetProfile(c *gin.Context) {
    reqCtx, cancel := context.WithTimeout(c.Request.Context(), 3*time.Second)
    defer cancel()

    profile, err := h.svc.GetProfile(reqCtx, c.Param("id"))
    if err != nil {
        c.JSON(http.StatusGatewayTimeout, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, profile)
}

3) Cancellation'ı gerçekten işletebiliyor musun?

Sadece context geçirmek yetmez. Alt operasyonların da bu sinyali dinlemesi gerekir.

Veritabanı

database/sql ile QueryContext ve ExecContext kullanmak şart:

rows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE status = $1", "active")

HTTP client

Context'i request'e bağlayın:

req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := httpClient.Do(req)

Worker / goroutine

select ile ctx.Done() dinlenmeli:

for {
    select {
    case <-ctx.Done():
        return ctx.Err()
    case job := <-jobs:
        process(job)
    }
}

4) context.WithValue ne zaman kullanılır?

WithValue, config taşımak için değil; request-scope metadata için kullanılmalı:

  • trace id
  • request id
  • user id (güvenlik modeline uygunsa)

Karmaşık domain verisini context'e koymak test edilebilirliği düşürür.
Kural basit: "iş mantığı verisi parametrede, gözlemlenebilirlik verisi context'te."


5) Graceful shutdown: cancellation'ın son halkası

Servis kapanırken aktif istekleri hemen kesmek yerine kontrollü bitirmek gerekir.

Temel akış:

  1. SIGTERM al
  2. Yeni istek kabulünü durdur
  3. Mevcut istekler için sınırlı süre ver (örn. 10s)
  4. Süre sonunda kalan işleri iptal et
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = server.Shutdown(shutdownCtx)

Bu davranış, rolling update sırasında 5xx oranını ciddi düşürür.


6) Production checklist

  • Handler'dan aşağı her çağrıya context geçiyor mu?
  • QueryContext / ExecContext kullanılıyor mu?
  • Harici API çağrıları context-aware mi?
  • Timeout değerleri katmanlar arasında tutarlı mı?
  • Shutdown akışı test edildi mi?
  • Timeout/cancel metrikleri (counter + latency) izleniyor mu?

Sonuç

Go'da context doğru kurgulandığında:

  • Kaynak tüketimi kontrol altında kalır,
  • P95/P99 gecikme dalgalanması azalır,
  • Deploy ve restart süreçleri daha güvenli hale gelir.

Kısacası context; "opsiyonel iyi pratik" değil, production stabilitesinin temel bileşenidir.