API'ler için Go context zaman aşımları: HTTP işleyicilerinden SQL'e
API'ler için Go context zaman aşımları, HTTP işleyicisinden SQL çağrılarına kadar süre sınırlarını geçirmenize, takılı kalan istekleri önlemenize ve yük altında hizmetlerin kararlı kalmasını sağlamanıza yardımcı olur.

Neden istekler takılır (ve yük altında neden zararlı olur)
Bir istek, dönmeyen bir şeyi beklediğinde "takılmış" olur: yavaş bir veritabanı sorgusu, havuzdan bloke olmuş bir bağlantı, DNS aksaması veya çağrıyı kabul edip yanıt vermeyen upstream bir servis.
Belirti basittir: bazı istekler sonsuza kadar sürer, ardından arkalarında daha fazlası birikir. Genellikle artan bellek, yükselen goroutine sayısı ve boşalmayan açık bağlantı kuyruğu görürsünüz.
Yük altında takılan istekler iki kat zarar verir. Çalışanları meşgul eder ve veritabanı bağlantıları ve kilitler gibi kıt kaynakları tutar. Bu, normalde hızlı olan istekleri yavaşlatır, daha fazla örtüşme yaratır ve daha fazla beklemeye sebep olur.
Tekrar denemeler ve trafik sıçramaları bu sarmalı kötüleştirir. Bir istemci zaman aşımına uğrar ve orijinal istek hala çalışırken tekrar dener, böylece iki istek için ödeme yaparsınız. Bir yavaşlama sırasında birçok istemciyi çarpın ve veritabanını aşırı yükleyebilir veya bağlantı sınırlarına ulaşabilirsiniz, ortalama trafik iyi olsa bile.
Zaman aşımı basitçe bir vaatir: "X süreden uzun beklemeyeceğiz." Bu hızlı başarısız olmanıza ve kaynakları serbest bırakmanıza yardımcı olur, ama işi daha hızlı bitirmez.
Ayrıca işin anında duracağını garanti etmez. Örneğin, veritabanı çalışmaya devam edebilir, bir upstream servis iptali görmezden gelebilir veya kendi kodunuz iptal edildiğinde güvenli olmayabilir.
Bir zaman aşımının garanti ettiği şey, handler'ın beklemeyi bırakıp net bir hata döndürebilmesi ve tuttuğu kaynakları serbest bırakabilmesidir. Bu sınırlı bekleme, birkaç yavaş çağrının tam bir kesintiye dönüşmesini engeller.
Go context zaman aşımlarıyla hedef, uçtan en derin çağrıya kadar paylaşılan bir sonlandırma zamanıdır. Bunu HTTP sınırında bir kez ayarlayın, aynı context'i servis kodunuz boyunca iletin ve database/sql çağrılarında da kullanın, böylece veritabanı da ne zaman beklemeyi bırakacağını bilir.
Go'da context basitçe
Bir context.Context, şu anda ne olduğuna dair bilgiyi kodunuz boyunca taşıdığınız küçük bir nesnedir. "Bu istek hâlâ geçerli mi?", "Ne zaman vazgeçmeliyiz?" ve "Bu işle ilgili küçük meta veriler (örn. istek kimliği) nedir?" gibi soruları cevaplar.
Büyük kazanç, sisteminizin kenarında (HTTP handler) yapılan tek bir kararın aynı context'i iletmeniz koşuluyla tüm alt adımları korumasıdır.
Context neler taşır
Context iş verileri için bir yer değildir. Kontrol sinyalleri ve sınırlı istek kapsamı içindir: iptal, bir son tarih/zaman aşımı ve günlükler için istek kimliği gibi küçük meta veriler.
Zaman aşımı ile iptal arasındaki fark basittir: zaman aşımı iptalin bir sebebidir. 2 saniyelik bir zaman aşımı ayarlarsanız, 2 saniye geçince context iptal edilir. Ancak kullanıcı sekmeyi kapatırsa, load balancer bağlantıyı düşürürse veya kodunuz isteği erkenden durdurmaya karar verirse context erken iptal edilebilir.
Context, fonksiyon çağrıları boyunca ilk parametre olarak açıkça geçirilir: genellikle func DoThing(ctx context.Context, ...). Bu amaçtır. Her çağrı noktasında göründüğü için onu "unutmak" zordur.
Süre dolduğunda, context'i izleyen her şey hızla durmalıdır. Örneğin, QueryContext kullanan bir veritabanı sorgusu context deadline exceeded gibi bir hata ile erken dönmeli ve handler beklemek yerine bir zaman aşımı ile yanıt verebilmelidir.
İyi bir zihinsel model: bir istek, bir context, her yere iletilir. İstek ölürse, işin de ölmesi gerekir.
HTTP sınırında net bir son tarih belirlemek
Uçtan uca zaman aşımının çalışmasını istiyorsanız, saatin nerede başladığını belirleyin. En güvenli yer HTTP kenarıdır, böylece her alt çağrı (iş mantığı, SQL, diğer servisler) aynı son tarihe miras kalır.
Bu son tarihi birkaç yerde ayarlayabilirsiniz. Sunucu düzeyinde zaman aşımı iyi bir temel sağlar ve yavaş istemcilerden korur. Middleware, route grupları arasında tutarlılık için iyidir. Handler içinde ayarlamak da belirgin ve lokal bir ayar istediğinizde uygundur.
Çoğu API için, middleware veya handler içinde istek başına zaman aşımı tutarlı ve anlaşılması kolaydır. Gerçekçi tutun: kullanıcılar, asılı kalan bir isteğin yerine hızlı, belirgin bir hatayı tercih eder. Birçok ekip okuma için daha kısa (1–2s) ve yazma için biraz daha uzun (3–10s) bütçeler kullanır; endpoint'in yaptığı işe göre değişir.
Basit bir handler deseni şuna benzer:
func (s *Server) getReport(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
report, err := s.reports.Generate(ctx, r.URL.Query().Get("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusGatewayTimeout)
return
}
json.NewEncoder(w).Encode(report)
}
İki kural bunu etkili kılar:
- Her zaman
cancel()çağırın ki zamanlayıcılar ve kaynaklar hızla serbest bırakılsın. - Handler içinde isteğin context'ini
context.Background()veyacontext.TODO()ile asla değiştirmeyin. Bu zinciri kırar ve veritabanı çağrılarınız ile dış istekler istemci gittikten sonra sonsuza dek çalışabilir.
Kod tabanınız boyunca context'i iletmek
HTTP sınırında bir son tarih ayarladıktan sonra asıl iş, aynı son tarihin bloklayabilecek her katmana ulaşmasını sağlamaktır. Amaç, handler, servis kodu ve ağ veya diskle temas eden her şey tarafından paylaşılan tek bir saattir.
Basit bir kural her şeyi tutarlı kılar: bekleyebilecek her fonksiyon bir context.Context kabul etsin ve bu ilk parametre olsun. Bu çağrı noktalarında bariz olur ve alışkanlık haline gelir.
Pratik imza deseni
Servisler ve repository'ler için DoThing(ctx context.Context, ...) gibi imzalardan yana olun. Context'i struct'lar içinde gizlemeyin veya alt katmanlarda context.Background() ile yeniden yaratmayın; çünkü bu çağıranın süre sınırını sessizce düşürür.
func (h *Handler) CreateOrder(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if err := h.svc.CreateOrder(ctx, r.Body); err != nil {
// map context errors to a clear client response elsewhere
http.Error(w, err.Error(), http.StatusRequestTimeout)
return
}
}
func (s *Service) CreateOrder(ctx context.Context, body io.Reader) error {
// parsing or validation can still respect cancellation
select {
case <-ctx.Done():
return ctx.Err()
default:
}
return s.repo.InsertOrder(ctx, /* data */)
}
Erken çıkışları temiz ele almak
ctx.Done()'ı normal bir kontrol yolu gibi ele alın. İki alışkanlık yardımcı olur:
- Masraflı işe başlamadan önce ve uzun döngülerden sonra
ctx.Err()kontrol edin. ctx.Err()değerini yukarıya değişmeden döndürün, böylece handler hızlıca yanıt verebilir ve kaynak israfını durdurur.
Her katman aynı ctx'yi ilettiğinde, tek bir zaman aşımı parsing'i, iş mantığını ve veritabanı beklemelerini bir kerede kesebilir.
database/sql sorgularına süre sınırı uygulamak
HTTP handler'ınız bir son tarihe sahip olduğunda, veritabanı çalışmanızın gerçekten buna kulak verdiğinden emin olun. database/sql ile bu, her seferinde context farkındalıklı metodları kullanmak demektir. Query() veya Exec() gibi context olmayan çağrılar yaparsanız, API'niz istemci vazgeçtikten sonra yavaş sorguda beklemeye devam edebilir.
Bunları tutarlı şekilde kullanın: db.QueryContext, db.QueryRowContext, db.ExecContext ve db.PrepareContext (ardından dönen statement üzerinde QueryContext/ExecContext).
func (s *Store) GetUser(ctx context.Context, id int64) (*User, error) {
row := s.db.QueryRowContext(ctx,
`SELECT id, email FROM users WHERE id = $1`, id,
)
var u User
if err := row.Scan(&u.ID, &u.Email); err != nil {
return nil, err
}
return &u, nil
}
func (s *Store) UpdateEmail(ctx context.Context, id int64, email string) error {
_, err := s.db.ExecContext(ctx,
`UPDATE users SET email = $1 WHERE id = $2`, email, id,
)
return err
}
İki şey kolay kaçırılır.
İlk olarak, SQL sürücünüzün context iptalini dikkate alması gerekir. Birçok sürücü yapıyor, ama yığındaki halinizi test ederek yavaş bir sorguyu kasıtlı çalıştırıp süre dolduğunda hızlıca iptal olduğunu doğrulayın.
İkincisi, veritabanı tarafı zaman aşımını bir güvenlik hattı olarak düşünün. Örneğin Postgres, her ifade için bir sınır (statement timeout) uygulayabilir. Bu, bir uygulama hatası context'i bir yerde unutursa veritabanını korur.
Bir işlem zaman aşımından durduğunda, bunu normal bir SQL hatasından farklı ele alın. errors.Is(err, context.DeadlineExceeded) ve errors.Is(err, context.Canceled) kontrolleri yapın ve bunu "veritabanı bozuk" gibi işlemeyip (ör. 504 gibi) net bir yanıt döndürün. Eğer Go backend'ler (örneğin AppMaster ile) üretiyorsanız, bu hata yollarını ayrı tutmak logları ve tekrar denemeleri anlamayı kolaylaştırır.
Aşağı yöndeki çağrılar: HTTP client'lar, cache'ler ve diğer servisler
Handler ve SQL sorgularınız context'e uyuyor olsa bile, bir aşağı yön çağrısı sonsuza dek beklerse istek yine asılı kalabilir. Yük altında birkaç takılan goroutine birikebilir, bağlantı havuzlarını yiyebilir ve küçük bir yavaşlamayı tam bir kesintiye çevirebilir. Çözüm tutarlı propagasyon artı sert bir güvenlik hattıdır.
Giden HTTP
Başka bir API çağırırken, isteği aynı context ile oluşturun ki süre sınırı ve iptal otomatik akışa girsin.
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { /* handle */ }
resp, err := httpClient.Do(req)
Sadece context'e güvenmeyin. Ayrıca HTTP client ve transport'u yapılandırın ki kod yanlışlıkla background context kullansa veya DNS/TLS/idle bağlantılar takılsa bile korunmuş olun. http.Client.Timeout'ı tüm çağrı için üst sınır olarak ayarlayın, transport zaman aşımlarını belirleyin (dial, TLS handshake, response header) ve her istek için yeni bir client yaratmayıp tek bir client yeniden kullanın.
Cache'ler ve kuyruqlar
Cache'ler, mesaj broker'lar ve RPC client'lar genellikle kendi bekleme noktalarına sahiptir: bir bağlantı edinmek, yanıt beklemek, dolu kuyruğa takılmak veya bir kilit için beklemek. Bu operasyonların ctx kabul ettiğinden emin olun ve varsa kütüphane seviyesinde zaman aşımları kullanın.
Pratik bir kural: kullanıcı isteğinin 800ms'i kaldıysa 2 saniye sürebilecek bir aşağı yön çağrısına başlamayın. Atlayın, bozunma yapın veya kısmi yanıt döndürün.
Zaman aşımının API'niz için ne anlama geldiğini önceden belirleyin. Bazen doğru cevap hızlı bir hata, bazen isteğe bağlı alanlar için kısmi veri, bazen de açıkça işaretlenmiş önbellekten eski verilerdir.
Go backend'ler (üretken olanlar dahil, ör. AppMaster) oluşturuyorsanız, bu "zaman aşımı var" ile "zaman aşımları trafiğe karşı tutarlı şekilde korur" arasındaki farktır.
Adım adım: bir API'yi uçtan uca zaman aşımı kullanacak şekilde yeniden düzenlemek
Zaman aşımı için yeniden düzenleme, tek alışkanlığa dayanır: aynı context.Context'i HTTP kenarından aşağıya, bloklayabilecek her çağrıya kadar iletin.
Bunu yapmak için pratik yol yukarıdan aşağı çalışmaktır:
- Handler'ınızı ve temel servis metodlarınızı
ctx context.Contextkabul edecek şekilde değiştirin. - Her DB çağrısını
QueryContextveyaExecContextkullanacak şekilde güncelleyin. - Dış çağrılar (HTTP client'lar, cache'ler, kuyruklar) için de aynı yapın. Eğer bir kütüphane
ctxkabul etmiyorsa onu sarmalayın ya da değiştirin. - Kim zaman aşımını yönetiyor karar verin. Yaygın bir kural: handler toplam süre sınırını belirler; alt katmanlar sadece belirli operasyonlar için daha kısa süreler belirler.
- Kenarda hataları öngörülebilir hale getirin:
context.DeadlineExceededvecontext.Canceled'ı net HTTP yanıtlarına eşleyin.
Katmanlar arasında şu şekli isteyebilirsiniz:
func (h *Handler) GetOrder(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
defer cancel()
order, err := h.svc.GetOrder(ctx, r.PathValue("id"))
if errors.Is(err, context.DeadlineExceeded) {
http.Error(w, "request timed out", http.StatusGatewayTimeout)
return
}
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(order)
}
func (r *Repo) GetOrder(ctx context.Context, id string) (Order, error) {
row := r.db.QueryRowContext(ctx, `SELECT id,total FROM orders WHERE id=$1`, id)
// scan...
}
Zaman aşımı değerleri sıkıcı ve tutarlı olmalı. Eğer handler'ın toplam 2 saniyesi varsa, JSON kodlama ve diğer işler için alan bırakmak adına veritabanı sorgularını 1 saniyenin altında tutun.
İşleyişi kanıtlamak için, zaman aşımını zorlayan bir test ekleyin. Basit bir yaklaşım, ctx.Done()'a kadar bloklanan ve sonra ctx.Err() döndüren sahte bir repository yöntemidir. Testiniz handler'ın yapay gecikme süresi sonunda değil, hızlıca 504 döndürdüğünü doğrulamalı.
AppMaster gibi bir jeneratörle Go backend'ler oluşturuyorsanız, kural aynı: tek bir istek context'i, her yere işlenmiş, ve süre sahibinin net olduğu bir yapı.
Gözlemlenebilirlik: zaman aşımlarının çalıştığını kanıtlamak
Zaman aşımı yalnızca gerçekleştiğini görebiliyorsanız yardımcı olur. Hedef basit: her isteğin bir son tarihi olsun ve başarısız olduğunda zamanın nerede harcandığını anlayın.
Aşamalar için yeterli ama güvenli log'larla başlayın. Tüm isteği dump etmek yerine, olayları bağlamak ve yavaş yolları tespit etmek için yeterli bilgiyi loglayın: istek ID'si (veya trace ID), kenarda bir süre olup olmadığı ve kritik noktalarda ne kadar süre kaldığı, operasyon adı (handler, SQL sorgu adı, outbound çağrı adı) ve sonuç kategorisi (ok, timeout, canceled, başka hata).
Davranışı yük altında görünür kılacak birkaç odaklı metrik ekleyin:
- Endpoint ve bağımlılık bazında zaman aşımı sayısı
- İstek gecikmesi (p50/p95/p99)
- Eş zamanlı (in-flight) istekler
- Veritabanı sorgu gecikmesi (p95/p99)
- Hata oranı türlere göre ayrılmış
Hataları işlerken doğru etiketleyin. context.DeadlineExceeded genellikle bütçenize takıldığınız anlamına gelir. context.Canceled genellikle istemcinin ayrıldığı veya bir upstream zaman aşımının önce tetiklendiği anlamına gelir. Bunları ayrı tutun çünkü düzeltmeler farklıdır.
İzleme: zamanın nerede gittiğini bulun
Tracing span'ları handler'dan database/sql çağrılarına (QueryContext gibi) aynı context'i takip etmelidir. Örneğin, bir istek 2 saniyede zaman aşımına uğruyor ve trace veritabanı bağlantısı için 1.8 saniye beklediğini gösteriyorsa, sorun sorgu metninde değil havuz boyutunda veya uzun işlemde demektir.
Bunu göstermek için dahili bir pano (endpoint'lere göre zaman aşımı sayıları, en yavaş sorgular vb.) yapabilirsiniz. AppMaster gibi no-code araçlar, gözlemlenebilirliği ayrı bir mühendislik projesi yapmadan hızlıca sunmanıza yardımcı olabilir.
Süreleri anlamsız hale getiren yaygın hatalar
Çoğu "hala bazen takılıyor" hatası birkaç küçük hatadan gelir.
- Yolda saati sıfırlamak. Handler 2s belirler ama repository kendi zaman aşımını (veya hiç zaman aşımı olmayan yeni bir context) oluşturur. Artık veritabanı istemci gittikten sonra çalışmaya devam edebilir. Gelen
ctx'yi geçirin ve yalnızca net bir neden varsa sıkılaştırın. - Hiç durmayan goroutine başlatmak.
context.Background()ile başlatılan veya context düşürülen iş, istemek iptal olduktan sonra çalışmaya devam eder. Goroutine'lere isteğin ctx'sini geçirin veselectilectx.Done()'u kontrol edin. - Gerçek trafiğe göre çok kısa süreler. 50ms gibi bir zaman aşımı laptop'ta çalışırken üretimde küçük bir sıçrama sırasında başarısız olur; tekrar denemeler daha fazla yük ve kendinizin neden olduğu küçük bir kesinti yaratır. Zaman aşımı seçimlerini normal gecikme ve biraz boşluk üzerine kurun.
- Gerçek hatayı gizlemek.
context.DeadlineExceeded'ı genel bir 500 gibi ele almak, hata ayıklamayı ve istemci davranışını kötüleştirir. Bunu net bir zaman aşımı yanıtına eşleyin ve "istemci tarafından iptal" ile "zaman aşımıyla iptal" arasındaki farkı loglayın. - Erken çıkışlarda kaynak bırakmak. Erken döndüğünüzde yine de
defer rows.Close()yaptığınızdan vecontext.WithTimeout'ın cancel fonksiyonunu çağırdığınızdan emin olun. Sızan rows veya kalan işler yük altında bağlantıları tüketebilir.
Kısa bir örnek: bir endpoint rapor sorgusu tetikliyor. Kullanıcı sekmeyi kapattığında handler ctx iptal olur. Eğer SQL çağrınız yeni bir background context kullandıysa, sorgu yine çalışır, bir bağlantıyı meşgul eder ve herkesi yavaşlatır. Aynı ctx'yi QueryContext içine ilettiğinizde veritabanı çağrısı kesilir ve sistem daha hızlı toparlanır.
Güvenilir zaman aşımı davranışı için hızlı kontrol listesi
Zaman aşımı ancak tutarlıysa yardımcı olur. Tek kaçırılan çağrı bir goroutine'i meşgul edebilir, bir DB bağlantısını tutabilir ve sonraki istekleri yavaşlatabilir.
- Kenarda (çoğunlukla HTTP handler) tek bir net son tarih belirleyin. İçindeki her şey bunu miras almalı.
- Aynı
ctx'yi servis ve repository katmanları boyunca geçirin. İstek kodundacontext.Background()kullanmaktan kaçının. - Her yerde context farkındalıklı DB metodlarını kullanın:
QueryContext,QueryRowContext,ExecContext. - Aynı
ctx'yi giden çağrılara ekleyin (HTTP client'lar, cache'ler, kuyruklar). Çocuk context oluşturursanız daha kısa olsun, daha uzun olmasın. - İptal ve zaman aşımı durumlarını tutarlı ele alın: temiz bir hata döndürün, işi durdurun ve iptal edilmiş bir isteğin içinde tekrar deneme döngüleri başlatmayın.
Bunları yaptıktan sonra, baskı altında davranışı doğrulayın. Zaman aşımı tetiklense bile kaynakları yeterince hızlı serbest bırakmıyorsa hâlâ güvenilirliği zedeler.
Panolar, zaman aşımlarını ortalamaların içinde gizlemek yerine görünür kılmalı. Aşağıdaki sinyalleri takip edin: istek zaman aşımı ve DB zaman aşımları (ayrı), gecikme yüzdelikleri (p95, p99), DB havuzu istatistikleri (kullanımdaki bağlantılar, bekleme sayısı, bekleme süresi) ve hata nedenleri dökümü (context deadline exceeded vs diğer hatalar).
AppMaster gibi bir platformda dahili araçlar inşa ediyorsanız, aynı kontrol listesi bağlanacağınız her Go servisine uygulanır: sınırları kenarda tanımlayın, onları iletin ve metriklerde takılı kalan isteklerin hızlıca başarısız olup kaynakları serbest bırakıp bırakmadığını doğrulayın.
Örnek senaryo ve sonraki adımlar
Bunun işe yaradığını gösteren yaygın bir yer arama endpoint'idir. GET /search?q=printer veritabanı büyük bir rapor sorgusuyla meşgul olduğunda yavaşlıyorsa düşünün. Son tarih yoksa her gelen istek uzun SQL sorgusunu bekler. Yük altında bu takılan istekler birikir, worker goroutine'leri ve bağlantıları meşgul eder ve tüm API donmuş gibi hissedilir.
HTTP handler'da net bir süre sınırı ve aynı ctx repository'ye geçirildiğinde, bütçe bittiğinde sistem beklemeyi bırakır. Süre dolduğunda veritabanı sürücüsü sorguyu iptal eder (destekliyorsa), handler döner ve sunucu yeni istekleri beklemek yerine servis etmeye devam edebilir.
Kullanıcı tarafında davranış bozulduğunda bile daha iyidir. 30–120 saniye dönüp sonra karışık şekilde hata almak yerine istemci hızlı ve öngörülebilir bir hata (çoğunlukla 504 veya 503 ve kısa bir mesaj: "request timed out") alır. Daha da önemlisi, sistem hızla toparlanır çünkü yeni istekler eski olanların arkasında beklemez.
Bunu tüm endpoint'lere ve ekiplere yayıp kabullenmek için sonraki adımlar:
- Endpoint türüne göre (arama vs yazma vs dışa aktarma) standart zaman aşımı değerleri seçin.
- Kod incelemede
QueryContextveExecContextkullanımını zorunlu kılın. - Kenarda zaman aşımı hatalarını açık hale getirin (net durum kodu, basit mesaj).
- Zaman aşımı ve iptal için metrikler ekleyin ki regresyonları erken fark edin.
- Her handler'ın aynı şekilde davranması için context oluşturma ve loglama sarmalayan bir yardımcı yazın.
Eğer AppMaster ile servisler ve dahili araçlar inşa ediyorsanız, bu zaman aşımı kurallarını türetilmiş Go backendlere, API entegrasyonlarına ve panolara tutarlı şekilde uygulayabilirsiniz. AppMaster, appmaster.io üzerinde bulunur (no-code, gerçek Go kaynak kodu üretimi ile), bu yüzden el ile her yönetici aracını inşa etmeden tutarlı istek işleme ve gözlemlenebilirlik istediğinizde pratik bir seçim olabilir.
SSS
Bir istek, yavaş bir SQL sorgusu, havuzdan bloke olmuş bir bağlantı, DNS aksaması veya yan hizmetin yanıt vermemesi gibi dönmeyen bir şeyin beklenmesi durumunda “takılmış” olur. Yük altında takılan istekler art arda yığılır, çalışanları ve bağlantıları meşgul eder ve küçük bir yavaşlamayı geniş çaplı bir kesintiye dönüştürebilir.
Genel süre sınırını HTTP kenarında belirleyin ve aynı ctx'yi bloklayabilecek her katmana iletin. Bu paylaşılan süre sınırı, birkaç yavaş işlemin kaynakları uzun süre tutmasını engeller.
ctx, cancel := context.WithTimeout(r.Context(), d) kullanın ve handler (veya middleware) içinde her zaman defer cancel() yapın. cancel() çağrısı zamanlayıcıları serbest bırakır ve istek erken biterse beklemeyi hızla durdurmaya yardımcı olur.
context.Background() veya context.TODO() ile değiştirmeyin; çünkü bu iptali ve süre sınırlarını kırar. İstek bağlamını bırakırsanız, SQL veya outbound HTTP gibi alt katmanlardaki işler istemci gitmiş olsa bile çalışmaya devam eder.
context.DeadlineExceeded ve context.Canceled'ı normal kontrol sonuçları olarak ele alın ve yukarı doğru değişmeden iletin. Kenarda bunları net yanıtlarla eşleyin (çoğunlukla zaman aşımı için 504), böylece istemciler rastgele bir 500 üzerinde kör tekrar denemeler yapmaz.
Her yerde context farkındalıklı metodları kullanın: QueryContext, QueryRowContext, ExecContext ve PrepareContext. Query() veya Exec() gibi context olmayan çağrılar kullanırsanız, handler zaman aşımına uğrayabilir ama veritabanı çağrısı yine de goroutine'inizi bloke edip bağlantıyı tutabilir.
Birçok sürücü iptali destekler, ama kendi ortamınızda yavaş bir sorgu çalıştırıp süre dolduğunda hızlı döndüğünden emin olun. Ayrıca bazı kod yolları ctx'yi unutursa diye veritabanı tarafında bir sorgu zaman aşımı (statement timeout) ayarlamak akıllıca olur.
Giden istekleri http.NewRequestWithContext(ctx, ...) ile oluşturun, böylece aynı süre sınırı ve iptal otomatik olarak akışa girer. Ayrıca istemci ve transport seviyesinde zaman aşımları (dial, TLS handshake, response header ve http.Client.Timeout) ayarlayın; çünkü bazen arka plan bağlamı veya düşük seviyeli beklemeler context ile korunmaz.
Alt katmanlar zaman aşımı oluşturabilir ama gelen isteğin zamanını uzatmayın; çocuğa verilen süre genellikle daha kısa olmalı, daha uzun olmamalı. İstek çok az zaman bırakıyorsa isteğe bağlı çağrıları atlayın, kısmi yanıt döndürün veya hızlıca hata verin.
Endpoint ve bağımlılık bazında zaman aşımı ve iptal sayılarını ayrı takip edin, gecikme yüzdeliklerini ve bekleyen istekleri izleyin. İzlemelerde handler, outbound çağrılar ve QueryContext içindeki süreleri takip edin ki zamanın nerede harcandığını görebilesiniz.


