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

Birbirine bağımlı birden fazla işi, ilk hata hepsini iptal edecek şekilde errgroup ile mi yürütmeliyim?


Soru

Go ile yazdığımız bir API'de her isteğin başında beş ayrı upstream servise (kullanıcı, abonelik, kampanya, envanter ve fiyatlandırma) paralel HTTP çağrısı yapıyoruz. Şu an bunları düz bir `sync.WaitGroup` ile fan out edip tüm sonuçları topluyorum. Sorun şu: bu beş çağrıdan biri hata verdiği anda geri kalan dördünün cevabını beklemenin anlamı kalmıyor, çünkü isteği zaten hatayla bitireceğim. İlk hata anında tüm batch'i iptal etmek için `errgroup`'a mı geçmeliyim, yoksa bu overkill mi olur?

Cevap

Kısa cevap: Evet, geçin.

Kısa cevap

Bu tam olarak errgroup.WithContext’in çözmek için var olduğu senaryo; burada overkill değil, doğru araç. sync.WaitGroup size “hepsini bekle” verir, sizin istediğiniz ise “biri düşerse hepsini bırak”. errgroup, golang.org/x/sync/errgroup altında yaşayan küçük ve olgun bir bağımlılıktır; bu tür fan-out’lar için taşımaya kesinlikle değer.

Neden

  1. errgroup.WithContext deyimi budur. WithContext, gruba bir türetilmiş context verir ve goroutine’lerden biri nil olmayan bir hata döndürdüğü anda bu context’i iptal eder. Yani ilk hata, kalan çağrılara “durun” sinyalini otomatik gönderir; sizin elle sinyalleşme kurmanıza gerek kalmaz.

  2. İptal işbirlikçidir — çağrılarınız ctx’e uymak zorunda. Burası en sık atlanan nokta. ctx iptal olsa bile, HTTP çağrınız http.NewRequestWithContext ile bu context’i taşımıyorsa iptalin hiçbir etkisi olmaz; çağrı bitene kadar çalışmaya devam eder. Aynısı DB sorguları için de geçerli. Context’i tabana kadar geçirmezseniz errgroup’un yarısı boşa gider.

    g, ctx := errgroup.WithContext(ctx)
    results := make([]Result, len(ids))
    for i, id := range ids {
        i, id := i, id // Go 1.22 öncesi döngü değişkeni yakalama
        g.Go(func() error {
            r, err := lookup(ctx, id) // ctx içeride http.NewRequestWithContext'e gider
            if err != nil {
                return fmt.Errorf("lookup %s: %w", id, err)
            }
            results[i] = r
            return nil
        })
    }
    if err := g.Wait(); err != nil {
        return nil, err // yalnızca İLK hata
    }
  3. g.Wait() yalnızca ilk hatayı döner. Diğer goroutine’lerin hataları düşürülür. Beş çağrının hepsinin hatasını toplamak istiyorsanız errgroup yanlış araçtır; o zaman WaitGroup + kanal ya da errors.Join gerekir. Ama sizin ihtiyacınız “ilki yeter, gerisini iptal et” olduğu için birebir uyuyor.

  4. Wait() yine de tüm goroutine’ler dönene kadar bloklar. İptal “durun” der ama Wait, her goroutine gerçekten geri dönene kadar beklemeyi sürdürür. Ctx’i yok sayan bir çağrınız varsa yine en yavaşı kadar beklersiniz — bu yüzden 2. madde kritik.

Ne yapmalı

  1. Paylaşılan sonuçlara senkronizasyonsuz yazmayın. Her goroutine kendi results[i] indeksine yazıyorsa güvenlidir (ayrı bellek hücreleri). Ama ortak bir map’e yazarsanız data race olur — -race ile yakalanır. Slice indeksi kullanın ya da mutex koyun.

  2. Fan out büyürse SetLimit. Beş çağrı için sınırsız concurrency sorun değil, ama bu sayı büyürse g.SetLimit(n) ile eşzamanlı goroutine sayısını kapatın; upstream’i ezmeyin.

  3. Üst context’e bir deadline koyun. İptal, yalnızca çağrılardan biri hata dönerse tetiklenir. Hiçbiri hata dönmüyor ama biri asılı kalıyorsa (yavaş upstream, yanıt vermeyen soket), errgroup sonsuza kadar bekler. Bunun için WithContext’e verdiğiniz context’i context.WithTimeout ile sarın; deadline dolduğunda ctx iptal olur, ctx’e uyan tüm çağrılar geri döner ve isteğiniz belirli bir üst sınırda biter.

Sonuç: Ben olsam errgroup.WithContext’e geçer, üstüne bir request-level timeout koyar, ctx’i her HTTP/DB çağrısına kadar geçirir ve sonuçları per-index slice’a yazardım. Go 1.22+ kullanıyorsanız döngü değişkeni yakalama satırına da gerek yok. Kısacası: hem “biri düşerse bırak” hem de “kimse dönmezse zamanaşımına uğra” garantisini birlikte kurun.

İlgili Yazılar

Etiketler: #Go#Concurrency
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