APIs के लिए Go Context टाइमआउट: HTTP हैंडलर से SQL तक
Go context टाइमआउट्स से आप HTTP हैंडलर से SQL कॉल तक डेडलाइन पास कर सकते हैं, अटकी रिक्वेस्ट्स रोक सकते हैं, और लोड के दौरान सेवाओं की स्थिरता बनाए रख सकते हैं।

रिक्वेस्ट क्यों अटक जाती है (और लोड के दौरान यह क्यों हानिकारक है)
एक रिक्वेस्ट "अटकी" तब कहलाती है जब वह किसी ऐसी चीज़ का इंतज़ार कर रही होती है जो लौटकर नहीं आती: धीमा डेटाबेस क्वेरी, पूल की अटकी कनेक्शन, DNS समस्या, या कोई अपस्ट्रीम सर्विस जो कॉल स्वीकार कर लेती है पर जवाब नहीं देती।
लक्षण आसान लगते हैं: कुछ रिक्वेस्ट्स हमेशा के लिए लेती हैं, और उनके पीछे ढेर सारी रिक्वेस्ट्स जमा हो जाती हैं। अक्सर आप बढ़ती मेमोरी, बढ़ती goroutine गिनती, और खुले कनेक्शन्स की एक कतार देखेंगे जो कभी खाली नहीं होती।
लोड के दौरान, अटकी रिक्वेस्ट्स का असर दोगुना होता है। वे वर्कर्स को व्यस्त रखती हैं, और दुर्लभ संसाधनों जैसे डेटाबेस कनेक्शन्स और लॉक को थाम कर रखती हैं। इससे सामान्यतः तेज़ रिक्वेस्ट्स भी स्लो हो जाती हैं, जो और ओवरलैप पैदा करती हैं, और और भी इंतज़ार बनता है।
रिट्राईज़ और ट्रैफ़िक स्पाइक्स इस स्पाइरल को और बिगाड़ देते हैं। क्लाइंट टाइमआउट कर देता है और रिट्राई करता है जबकि मूल रिक्वेस्ट अभी भी चल रही होती है, तो अब आप दो रिक्वेस्ट्स का भुगतान कर रहे होते हैं। यदि कई क्लाइंट छोटा‑सा स्लोडाउन देख कर रिट्राई करें तो आप डेटाबेस ओवरलोड कर सकते हैं या कनेक्शन लिमिट्स तक पहुँच सकते हैं भले औसत ट्रैफ़िक ठीक ही क्यों न हो।
एक टाइमआउट बस एक वादा है: हम X से ज़्यादा इंतज़ार नहीं करेंगे। यह आपको फेल‑फास्ट होकर रिसोर्सेज़ मुक्त करने में मदद करता है, पर यह काम को तेज़ नहीं करता।
यह भी गारंटी नहीं देता कि काम तुरंत रुक जाएगा। उदाहरण के लिए, डेटाबेस क्वेरी चलती रह सकती है, कोई अपस्ट्रीम सर्विस आपकी कैंसलेशन को अनदेखा कर सकती है, या आपका खुद का कोड कैंसलेशन होने पर सुरक्षित न हो।
टाइमआउट जो गारंटी देता है वह यह है कि आपका हैंडलर इंतज़ार बंद कर सके, एक स्पष्ट त्रुटि लौटाए, और जो वह पकड़ रखा है उसे छोड़ दे। यही सीमित इंतज़ार कुछ धीमे कॉल्स को पूरे सिस्टम को नीचे ले जाने से रोकता है।
Go context टाइमआउट्स का लक्ष्य यह है कि एज से सबसे अंदर तक एक साझा डेडलाइन हो। HTTP बॉउण्ड्री पर एक बार सेट करें, वही context अपनी सर्विस कोड में पास करें, और database/sql कॉल्स में भी इसका उपयोग करें ताकि डेटाबेस को भी बताया जा सके कि कब इंतज़ार बंद करना है।
Go का context सामान्य शब्दों में
context.Context एक छोटा ऑब्जेक्ट है जिसे आप अपने कोड में पास करते हैं ताकि यह बताये जा सके कि अभी क्या हो रहा है। यह ऐसे प्रश्नों का उत्तर देता है: "क्या यह रिक्वेस्ट अब भी वैध है?", "हमें कब छोड़ देना चाहिए?", और "इस कार्य के साथ कौन‑सा रिक्वेस्ट‑स्कोप्ड वैल्यू जाना चाहिए?"
बड़ा फायदा यह है कि सिस्टम के किनारे (HTTP हैंडलर) पर लिया गया एक निर्णय हर डाउनस्ट्रीम स्टेप को बचा सकता है, बस आप वही context सब जगह पास करते रहें।
Context क्या साथ लाता है
Context बिज़नेस डेटा के लिए जगह नहीं है। यह कंट्रोल सिग्नल और थोड़ी‑सी रिक्वेस्ट स्कोप्ड जानकारी के लिए है: कैंसलेशन, एक डेडलाइन/टाइमआउट, और छोटे‑मोटे मेटाडेटा जैसे लॉग के लिए रिक्वेस्ट आईडी।
टाइमआउट बनाम कैंसलेशन सरल है: टाइमआउट कैंसलेशन का एक कारण है। यदि आप 2 सेकंड का टाइमआउट सेट करते हैं, तो 2 सेकंड के बाद context कैंसल हो जाएगा। पर context पहले भी कैंसल हो सकता है यदि यूज़र टैब बंद कर दे, लोड बैलेंसर कनेक्शन ड्रॉप कर दे, या आपका कोड तय करे कि रिक्वेस्ट बंद हो जानी चाहिए।
Context फ़ंक्शन कॉल्स के माध्यम से एक स्पष्ट पैरामीटर के रूप में फ्लो होता है, आमतौर पर पहला पैरामीटर: func DoThing(ctx context.Context, ...)। यही उद्देश्य है। हर कॉल साइट पर यह दिखेगा तो इसे भूलना मुश्किल होगा।
जब डेडलाइन खत्म हो जाती है, जो भी उस context को देख रहा है उसे जल्दी रुक जाना चाहिए। उदाहरण के लिए, QueryContext का उपयोग कर रहा डेटाबेस क्वेरी जल्दी context deadline exceeded जैसी त्रुटि के साथ लौटना चाहिए, और आपका हैंडलर सर्वर के वर्कर्स खत्म होने तक हैंग होने के बजाय टाइमआउट के साथ उत्तर दे सकता है।
एक अच्छा मानसिक मॉडल: एक रिक्वेस्ट, एक context, सब जगह पास किया गया। अगर रिक्वेस्ट खत्म हो जाती है, तो उससे जुड़ा काम भी समाप्त हो जाना चाहिए।
HTTP बॉउण्ड्री पर स्पष्ट डेडलाइन सेट करना
यदि आप एंड‑टू‑एंड टाइमआउट्स चाहते हैं तो तय करें कि घड़ी कहाँ से चलनी शुरू होती है। सबसे सुरक्षित जगह HTTP एज पर ही है, ताकि हर डाउनस्ट्रीम कॉल (बिज़नेस लॉजिक, SQL, अन्य सर्विसेज़) वही डेडलाइन विरासत में पाए।
आप वह डेडलाइन कई जगहों पर सेट कर सकते हैं। सर्वर‑स्तरीय टाइमआउट्स एक अच्छा बेसलाइन हैं और धीमे क्लाइंट्स से आपको बचाते हैं। मिडलवेयर राउट‑ग्रुप्स में स्थिरता के लिए ज़बरदस्त है। हैंडलर के अंदर यह सेट करना भी ठीक है जब आप कुछ स्पष्ट और लोकल चाहते हैं।
अधिकांश APIs के लिए, मिडलवेयर या हैंडलर में प्रति‑रिक्वेस्ट टाइमआउट्स सबसे आसान होते हैं। उन्हें वास्तविक रखें: उपयोगकर्ता लंबे हैंग होने की बजाय तेज़ स्पष्ट फेलियर ही पसंद करते हैं। कई टीमें रीड्स के लिए छोटा बजट (जैसे 1–2s) और राइट्स के लिए थोड़ा लंबा (3–10s) उपयोग करती हैं, यह इस बात पर निर्भर करता है कि एंडप्वाइंट क्या करता है।
यहाँ एक सरल हैंडलर पैटर्न है:
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)
}
दो नियम इसे प्रभावी बनाए रखते हैं:
- हमेशा
cancel()कॉल करें ताकि टाइमर और रिसोर्स जल्दी रिलीज़ हों। - हैंडलर के अंदर
context.Background()याcontext.TODO()से request context बदलकर कभी भी न रखें। इससे चेन टूट जाती है, और आपका डेटाबेस कॉल्स और आउटबाउंड रिक्वेस्ट्स क्लाइंट चले जाने के बाद भी अनंत काल तक चल सकते हैं।
अपने कोडबेस में context फैलाना
एक बार आपने HTTP बॉउण्ड्री पर डेडलाइन सेट कर दी, असली काम यह सुनिश्चित करना है कि वही डेडलाइन हर उस लेयर तक पहुँचे जो ब्लॉक कर सकती है। विचार यह है: एक घड़ी, हैंडलर, सर्विस कोड, और जो कुछ भी नेटवर्क या डिस्क को छूता है, सब में साझा।
एक सरल नियम चीज़ों को सुसंगत रखता है: हर फ़ंक्शन जो इंतज़ार कर सकता है उसे context.Context स्वीकार करना चाहिए, और यह पहला पैरामीटर होना चाहिए। इससे कॉल साइट्स पर यह स्पष्ट रहता है और यह आदत बन जाती है।
एक व्यावहारिक सिग्नेचर पैटर्न
सर्विस और रिपॉज़िटरी के लिए DoThing(ctx context.Context, ...) जैसे सिग्नेचर पसंद करें। context को structs में छुपाने या लोअर लेयर्स में context.Background() से फिर से बनाने से बचें, क्योंकि वह कॉलर की डेडलाइन को चुपचाप छोड़ देता है।
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 */)
}
जल्दी निकलने को साफ़ तरीके से संभालना
ctx.Done() को सामान्य कंट्रोल पाथ समझें। दो आदतें मददगार हैं:
- महंगी काम शुरू करने से पहले और लंबे लूप्स के बाद
ctx.Err()चेक करें। ctx.Err()को ऊपर की ओर बिना बदले रिटर्न करें, ताकि हैंडलर जल्दी जवाब दे सके और रिसोर्स बेकार न हो रहे हों।
जब हर लेयर वही ctx पास करती है, एक ही टाइमआउट पार्सिंग, बिज़नेस लॉजिक और डेटाबेस वेट्स को एक ही बार में काट सकती है।
database/sql क्वेरीज़ पर डेडलाइन लागू करना
एक बार आपके HTTP हैंडलर के पास डेडलाइन हो, सुनिश्चित करें कि आपका डेटाबेस वर्क भी वास्तव में उसे सुनता है। database/sql के साथ, इसका मतलब है हर बार context‑aware मेथड्स का उपयोग। यदि आप Query() या Exec() बिना context के कॉल करते हैं, तो आपकी API धीमी क्वेरी पर तब तक इंतज़ार कर सकती है जब तक क्लाइंट हार न मान ले।
इनका सुसंगत रूप से उपयोग करें: db.QueryContext, db.QueryRowContext, db.ExecContext, और db.PrepareContext (फिर returned statement पर 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
}
दो बातें आसानी से छूट सकती हैं।
पहला, आपका SQL ड्राइवर context कैंसलेशन का सम्मान करना चाहिए। कई करते हैं, पर अपने स्टैक में टेस्ट करके पुष्टि करें: एक जानबूझकर धीली क्वेरी चलाकर देखें कि डेडलाइन के बाद वह जल्दी कैंसल होती है या नहीं।
दूसरा, एक डेटाबेस‑साइड टाइमआउट को बैकस्टॉप के रूप में विचार करें। उदाहरण के लिए, Postgres पर प्रति‑स्टेटमेंट लिमिट (स्टेटमेंट टाइमआउट) लगा सकते हैं। यह डेटाबेस को तब भी बचाता है जब कोई ऐप बग कहीं context पास करना भूल जाए।
जब कोई ऑपरेशन टाइमआउट की वजह से रुकता है, तो उसे सामान्य SQL त्रुटि से अलग तरह से हैंडल करें। errors.Is(err, context.DeadlineExceeded) और errors.Is(err, context.Canceled) चेक करें और एक स्पष्ट रिस्पॉन्स (जैसे 504) लौटाएँ बजाय यह मान लेने के कि "डेटाबेस टूट गया है"। यदि आप Go बैकएंड्स जेनरेट करते हैं (उदाहरण के लिए AppMaster के साथ), तो इन एरर पाथ्स को अलग रखना लॉग और रिट्राईज़ के हिसाब से आसान बनाता है।
डाउनस्ट्रीम कॉल्स: HTTP क्लाइंट, कैश और अन्य सर्विसेज़
भले ही आपका हैंडलर और SQL क्वेरीज़ context का सम्मान करें, एक रिक्वेस्ट फिर भी हैंग कर सकती है यदि कोई डाउनस्ट्रीम कॉल अनिश्चित समय तक इंतज़ार करे। लोड के दौरान कुछ अटकी गोरूटीन जमा हो सकती हैं, कनेक्शन पूल पर असर डाल सकती हैं, और छोटे स्लोडाउन को पूरे आउटेज में बदल सकती हैं। समाधान है सुसंगत propagation और एक हार्ड बैकस्टॉप।
आउटबाउंड HTTP
जब किसी अन्य API को कॉल करें, वही context उपयोग करके रिक्वेस्ट बनाएं ताकि डेडलाइन और कैंसलेशन अपने‑आप फ्लो हो जाएँ。
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil { /* handle */ }
resp, err := httpClient.Do(req)
केवल context पर भरोसा न रखें। HTTP क्लाइंट और ट्रांसपोर्ट को भी कॉन्फ़िगर करें ताकि यदि कोड गलती से बैकग्राउंड context का उपयोग करे, या DNS/TLS/idle कनेक्शन्स अटक जाएँ, तो आप सुरक्षित रहें। http.Client.Timeout को ऊपर‑सीमा के रूप में सेट करें, ट्रांसपोर्ट टाइमआउट्स (dial, TLS handshake, response header) सेट करें, और प्रति‑रिक्वेस्ट नया क्लाइंट बनाने के बजाय एक ही क्लाइंट को reuse करें।
कैश और क्यूज़
कैश्स, मैसेज ब्रोकर्स, और RPC क्लाइंट अक्सर अपने इंतज़ार बिंदु रखते हैं: कनेक्शन प्राप्त करना, रिप्लाई का इंतज़ार, फुल क्यू पर ब्लॉक होना, या लॉक का इंतज़ार। सुनिश्चित करें कि वे ऑपरेशन्स ctx स्वीकार करते हैं, और जहाँ उपलब्ध हो लाइब्रेरी‑स्तरीय टाइमआउट्स का भी उपयोग करें।
एक व्यावहारिक नियम: अगर उपयोगकर्ता रिक्वेस्ट के पास 800ms बचे हैं, तो ऐसा कोलब मत शुरू करें जो 2 सेकंड ले सकता है। उसे स्किप करें, degrade करें, या आंशिक रिस्पॉन्स लौटाएँ।
यह पहले से तय करें कि आपके API के लिए टाइमआउट का मतलब क्या है। कभी‑कभी सही उत्तर तेज़ एरर है। कभी‑कभी यह वैकल्पिक फील्ड्स के लिए आंशिक डेटा है। कभी‑कभी यह कैश का स्टेल डेटा है, स्पष्ट रूप से मार्क किया हुआ।
यदि आप Go बैकएंड बनाते हैं (जिसमें जेनरेटेड भी शामिल हैं, जैसे AppMaster), तो यही फर्क है "टाइमआउट्स मौजूद हैं" और "टाइमआउट्स सिस्टम को कंसिस्टेंट रूप से बचाते हैं" जब ट्रैफ़िक स्पाइक्स आए।
चरण‑दर‑चरण: एक API को एंड‑टू‑एंड टाइमआउट्स के लिए रिफ़ैक्टर करें
टाइमआउट्स के लिए रिफ़ैक्टर करना एक आदत में आता है: HTTP एज से वही context.Context हर उस कॉल तक पास करें जो ब्लॉक कर सकती है।
व्यावहारिक तरीका ऊपर से नीचे जाने का है:
- अपने हैंडलर और कोर सर्विस मेथड्स को
ctx context.Contextस्वीकार करने के लिए बदलें। - हर DB कॉल को
QueryContextयाExecContextमें अपडेट करें। - बाहरी कॉल्स (HTTP क्लाइंट्स, कैश, क्यूज़) के लिए भी यही करें। यदि कोई लाइब्रेरी
ctxस्वीकार नहीं करती, तो उसे रैप करें या बदल दें। - तय करें कि टाइमआउट किसका है। सामान्य नियम: हैंडलर समग्र डेडलाइन सेट करे; निचली लेयर्स केवल विशिष्ट ऑपरेशन्स के लिए छोटे टाइमआउट सेट करें जब ज़रूरत हो।
- एज पर एरर्स को प्रेडिक्टेबल बनाएं:
context.DeadlineExceededऔरcontext.Canceledको स्पष्ट HTTP रिस्पॉन्स में मैप करें।
यहाँ लेयर्स में जो आकृति आप चाहते हैं वह है:
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...
}
टाइमआउट वैल्यूज़ उबाऊ और सुसंगत होनी चाहिए। यदि हैंडलर के पास कुल 2 सेकंड हैं, तो DB क्वेरीज़ को 1 सेकंड के अंदर रखें ताकि JSON एन्कोडिंग और अन्य काम के लिए जगह बचे।
यह साबित करने के लिए कि यह काम करता है, एक टेस्ट जोड़ें जो टाइमआउट को मजबूर करे। एक सरल तरीका है एक फेक रिपॉज़िटरी मेथड जो ctx.Done() तक ब्लॉक करे और फिर ctx.Err() लौटाए। आपका टेस्ट यह सुनिश्चित करे कि हैंडलर जल्दी 504 लौटाता है, न कि फेक डिले के बाद।
यदि आप AppMaster जैसे जनरेटर के साथ Go बैकएंड बनाते हैं, नियम वही है: एक रिक्वेस्ट context, हर जगह थ्रेड किया हुआ, और स्पष्ट डेडलाइन ओनरशिप।
ऑब्ज़रवेबिलिटी: यह साबित करना कि टाइमआउट्स काम कर रहे हैं
टाइमआउट तभी मदद करते हैं जब आप उन्हें होते हुए देख सकें। लक्ष्य सरल है: हर रिक्वेस्ट की एक डेडलाइन हो, और जब वह फेल हो तो आप बता सकें समय कहाँ गया।
सबसे पहले ऐसे लॉग से शुरू करें जो सुरक्षित और उपयोगी हों। पूर्ण रिक्वेस्ट बॉडी डालने के बजाय इतना लॉग करें कि डॉट्स कनेक्ट हों और स्लो पाथ्स दिखें: रिक्वेस्ट ID (या ट्रेस ID), क्या डेडलाइन सेट है और प्रमुख बिंदुओं पर कितना समय बचा है, ऑपरेशन का नाम (हैंडलर, SQL क्वेरी नाम, आउटबाउंड कॉल नाम), और रिज़ल्ट कैटेगरी (ok, timeout, canceled, other error)।
कुछ फोकस्ड मेट्रिक्स जोड़ें ताकि लोड के दौरान व्यवहार स्पष्ट हो:
- एंडप्वाइंट और डिपेंडेंसी के अनुसार टाइमआउट काउंट
- रिक्वेस्ट लेटेंसी (p50/p95/p99)
- इन‑फ्लाइट रिक्वेस्ट्स
- डेटाबेस क्वेरी लेटेंसी (p95/p99)
- एरर रेट, प्रकार के हिसाब से विभाजित
जब आप एरर्स हैंडल करें, उन्हें सही टैग दें। context.DeadlineExceeded आम तौर पर मतलब है कि आपने अपना बजट हिट कर लिया। context.Canceled अक्सर मतलब है कि क्लाइंट चला गया या किसी अपस्ट्रीम टाइमआउट ने पहले फायर कर दिया। इन्हें अलग रखें क्योंकि फिक्स अलग‑अलग होंगे।
ट्रेसिंग: समय कहां जा रहा है यह खोजें
ट्रेसिंग स्पैन्स को वही context हैंडलर से database/sql कॉल्स जैसे QueryContext तक फॉलो करना चाहिए। उदाहरण के लिए, एक रिक्वेस्ट 2 सेकंड पर टाइमआउट होता है और ट्रेस में दिखता है कि 1.8 सेकंड DB कनेक्शन के इंतज़ार में गया। यह पूल साइज़ या धीमी ट्रांज़ैक्शन्स की ओर इशारा करता है, क्वेरी टेक्स्ट की ओर नहीं।
यदि आप इसके लिए एक आंतरिक डैशबोर्ड बनाते हैं (रूट्स के अनुसार टाइमआउट), तो AppMaster जैसे नो‑कोड टूल से आप इसे जल्दी शिप कर सकते हैं बिना ऑब्ज़रवेबिलिटी को एक अलग इंजीनियरिंग प्रोजेक्ट बनाए।
सामान्य गलतियाँ जो आपके टाइमआउट्स को बेअसर कर देती हैं
ज्यादातर "कभी‑कभी अभी भी हैंग हो जाता है" बग कुछ छोटी गलतियों से आते हैं।
- बीच में घड़ी रीसेट कर देना। एक हैंडलर 2s डेडलाइन सेट करता है, पर रिपॉज़िटरी नया context अपने टाइमआउट के साथ (या बिना टाइमआउट के) बना देती है। अब डेटाबेस क्लाइंट चला गया पर क्वेरी अभी भी रन कर सकती है। आने वाले
ctxको पास करें और केवल तब कसा हुआ context बनाएं जब स्पष्ट कारण हो। - गोरूटीन शुरू करना जो कभी न रुके। काम को
context.Background()के साथ स्पॉन करना (या ctx को पूरी तरह छोड़ देना) मतलब वह काम क्लाइंट के जाने के बाद भी चलता रहेगा। गोरूटीन में रिक्वेस्ट ctx पास करें औरselectमेंctx.Done()पर रिएक्ट करें। - डेडलाइन्स जो असल ट्रैफ़िक के लिए बहुत कम हैं। 50ms टाइमआउट लैपटॉप पर काम कर सकता है पर प्रोडक्शन में छोटे स्पाइक पर fail कर देगा, जिससे रिट्राईज़ और और लोड होगा। सामान्य लेटेंसी और हेडरूम के आधार पर टाइमआउट चुनें।
- असल एरर छिपा देना।
context.DeadlineExceededको generic 500 मानना डिबगिंग और क्लाइंट बिहेवियर खराब कर देता है। इसे स्पष्ट टाइमआउट रिस्पॉन्स में मैप करें और "क्लाइंट द्वारा कैंसल" और "टाइमआउट हुआ" के बीच का अंतर लॉग करें। - जल्दी निकलने पर रिसोर्स खुले छोड़ देना। यदि आप जल्दी रिटर्न कर रहे हैं, तो सुनिश्चित करें कि आप अभी भी
defer rows.Close()करते हैं औरcontext.WithTimeoutसे मिली cancel फंक्शन कॉल करते हैं। लीक हुई rows या लटकी काम लोड के दौरान कनेक्शन्स ख़त्म कर सकती हैं।
एक छोटा उदाहरण: एक एंडप्वाइंट रिपोर्ट क्वेरी ट्रिगर करता है। अगर यूज़र टैब बंद कर दे, हैंडलर ctx कैंसल हो जाता है। यदि आपकी SQL कॉल ने नया बैकग्राउंड context इस्तेमाल किया, तो क्वेरी फिर भी चलेगी, एक कनेक्शन पकड़ कर रखेगी और सबको धीमा कर देगी। जब आप वही ctx QueryContext में पास करते हैं, डेटाबेस कॉल रुक जाती है और सिस्टम तेज़ी से रिकवर कर लेता है।
भरोसेमंद टाइमआउट व्यवहार के लिए त्वरित चेकलिस्ट
टाइमआउट तभी मदद करते हैं जब वे सुसंगत हों। एक ही चूकी हुई कॉल एक गोरूटीन को व्यस्त रख सकती है, DB कनेक्शन पकड़ सकती है, और अगले रिक्वेस्ट्स को धीमा कर सकती है।
- एज पर एक स्पष्ट डेडलाइन सेट करें (आमतौर पर HTTP हैंडलर). इसके अंदर की सब चीज़ें इसे विरासत में पाती हैं।
- अपने सर्विस और रिपॉज़िटरी लेयर्स में वही
ctxपास करें। रिक्वेस्ट कोड मेंcontext.Background()से बचें। - हर जगह context‑aware DB मेथड्स का उपयोग करें:
QueryContext,QueryRowContext, औरExecContext। - आउटबाउंड कॉल्स (HTTP क्लाइंट, कैश, क्यूज़) में भी वही
ctxजोड़ें। यदि आप चाइल्ड context बनाते हैं तो वह छोटा रखें, लंबा नहीं। - कैंसलेशन और टाइमआउट्स को सुसंगत रूप से हैंडल करें: साफ़ एरर लौटाएँ, काम रोकें, और कैंसल हुई रिक्वेस्ट के अंदर रिट्राई लूप से बचें।
इसके बाद, प्रेशर के तहत व्यवहार को सत्यापित करें। ऐसी टाइमआउट जो ट्रिगर होती है पर पर्याप्त तेजी से रिसोर्स रिलीज़ न करे, फिर भी विश्वसनीयता को नुकसान पहुंचाएगी।
डैशबोर्ड्स को टाइमआउट्स को स्पष्ट बनाना चाहिए, न कि औसत के भीतर छिपाना। कुछ सिग्नल ट्रैक करें जो यह जवाब दें कि "क्या डेडलाइन्स सचमुच लागू हो रही हैं?": रिक्वेस्ट टाइमआउट्स और DB टाइमआउट्स (अलग‑अलग), लेटेंसी पर्सेंटाइल (p95, p99), DB पूल स्टैट्स (in‑use connections, wait count, wait duration), और एरर कारणों का ब्रेकडाउन (context deadline exceeded बनाम अन्य फेलियर)।
यदि आप AppMaster जैसे प्लेटफ़ॉर्म पर आंतरिक टूल बनाते हैं, वही चेकलिस्ट किसी भी Go सर्विस के लिए लागू होती है जो आप उससे कनेक्ट करते हैं: बॉउण्ड्री पर डेडलाइन्स परिभाषित करें, उन्हें फैलाएँ, और मेट्रिक्स में पुष्टि करें कि अटकी रिक्वेस्ट्स धीमी फेलियर के बजाय तेज़ असफलताओं में बदल रही हैं।
उदाहरण परिदृश्य और अगले कदम
एक सामान्य जगह जहाँ यह फायदा देता है वह है सर्च एंडपॉइंट। कल्पना कीजिए GET /search?q=printer तब धीमा हो जाता है जब डेटाबेस एक बड़े रिपोर्ट क्वेरी से व्यस्त है। बिना डेडलाइन के, हर इनकमिंग रिक्वेस्ट लंबी SQL क्वेरी के इंतज़ार में बैठ सकती है। लोड के दौरान, वे अटकी रिक्वेस्ट्स जमा हो जाती हैं, वर्कर गोरूटीन और कनेक्शन्स बांध लेती हैं, और पूरा API फ्रीज़ जैसा महसूस होने लगता है।
HTTP हैंडलर में एक स्पष्ट डेडलाइन और वही ctx रिपॉज़िटरी तक पास करने पर, सिस्टम बजट खत्म होते ही इंतज़ार बंद कर देता है। डेडलाइन पर, डेटाबेस ड्राइवर क्वेरी कैंसल कर देता है (जहाँ समर्थित हो), हैंडलर लौट आता है, और सर्वर नई रिक्वेस्ट्स को परोसा कर सकता है बजाय पुराने रिक्वेस्ट्स के पीछे रुका रहने के।
उपयोगकर्ता‑दृष्टि से व्यवहार बेहतर होता है जब चीज़ें गलत हों। 30–120 सेकंड तक घूमने और फिर गड़बड़ तरीके से फेल होने के बजाय, क्लाइंट को तेज़, प्रेडिक्टेबल एरर मिलता है (अक्सर 504 या 503, छोटा संदेश जैसे 'request timed out')। और सबसे महत्वपूर्ण, सिस्टम जल्दी रिकवर करता है क्योंकि नई रिक्वेस्ट्स पुराने वालों के पीछे ब्लॉक नहीं होतीं।
अगले कदम जो इसे एन्डपॉइंट्स और टीम्स में बनाये रखने में मदद करेंगे:
- एंडपॉइंट प्रकार के अनुसार स्टैंडर्ड टाइमआउट चुनें (search vs writes vs exports)।
- कोड रिव्यू में
QueryContextऔरExecContextकी आवश्यकता रखें। - एज पर टाइमआउट एरर्स स्पष्ट रखें (साफ़ स्टेटस कोड, सरल संदेश)।
- टाइमआउट्स और कैंसलेशन्स के लिए मेट्रिक्स जोड़ें ताकि आप रेग्रेशन जल्दी नोटिस कर सकें।
- एक हेम्पर बनाएँ जो context निर्माण और लॉगिंग को रैप करे ताकि हर हैंडलर एक जैसा व्यवहार करे।
यदि आप AppMaster के साथ सर्विसेस और आंतरिक टूल बना रहे हैं, तो आप इन टाइमआउट नियमों को जेनरेटेड Go बैकएंड्स, API इंटीग्रेशन, और डैशबोर्ड्स में एक ही जगह सुसंगत रूप से लागू कर सकते हैं। AppMaster उपलब्ध है appmaster.io (no-code, with real Go source code generation), इसलिए यह व्यावहारिक रूप से फिट हो सकता है जब आप कंसिस्टेंट रिक्वेस्ट हैंडलिंग और ऑब्ज़रवेबिलिटी बिना हर एडमिन टूल को हाथ से बनाये लागू करना चाहें।
सामान्य प्रश्न
एक रिक्वेस्ट तब “अटकी” होती है जब वह किसी ऐसी चीज़ का इंतज़ार कर रही होती है जो लौटकर नहीं आती — जैसे धीला SQL क्वेरी, कनेक्शन पूल में अटकी हुई कनेक्शन, DNS समस्या, या कोई अपस्ट्रीम सर्विस जो कॉल स्वीकार कर लेती है पर जवाब नहीं देती। लोड के दौरान ऐसी अटकी रिक्वेस्ट्स इकट्ठी हो जाती हैं, वर्कर्स और कनेक्शन्स बांध कर रखती हैं, और एक छोटी सी स्लोडाउन को व्यापक आउटेज में बदल सकती हैं।
कुल समयसीमा (overall deadline) HTTP बाउंडरी पर सेट करें और वही ctx हर उस लेयर में पास करें जो ब्लॉक कर सकती है। यही साझा डेडलाइन कुछ धीमे ऑपरेशंस को लंबे समय तक रिसोर्स रोके रखने से रोकती है।
ctx, cancel := context.WithTimeout(r.Context(), d) का उपयोग करें और हैंडलर (या middleware) में हमेशा defer cancel() करें। cancel कॉल टाइमर और अन्य रिसोर्सेज़ को जल्दी रिलीज़ करने में मदद करती है जब रिक्वेस्ट जल्दी खत्म हो जाती है।
सबसे बड़ी गलती है रिक्वेस्ट कोड में context.Background() या context.TODO() से मूल context बदल देना, क्योंकि इससे कैंसलेशन और डेडलाइन्स टूट जाते हैं। जब आप request context छोड़ देते हैं, तो डाउनस्ट्रीम काम जैसे SQL या आउटबाउंड HTTP क्लाले बिना रुकावट के चलते रहते हैं।
context.DeadlineExceeded और context.Canceled को सामान्य कंट्रोल परिणाम की तरह ट्रीट करें और ऊपर की ओर बिना बदले पास करें। एज पर इन्हें स्पष्ट HTTP रिस्पॉन्स (अक्सर 504 टाइमआउट के लिए) में मैप करें ताकि क्लाइंट अनजाने में 500 पर रिट्राई न करे।
हर जगह context‑aware मेथड्स का उपयोग करें: QueryContext, QueryRowContext, ExecContext, और PrepareContext। यदि आप Query() या Exec() बिना context के कॉल करते हैं, तो आपका हैंडलर टाइमआउट हो सकता है पर डेटाबेस कॉल आपकी गोरूटीन को रोककर कनेक्शन पकड़ कर रख सकता है।
कई ड्राइवर context कैंसलेशन का पालन करते हैं, पर अपने स्टैक में टेस्ट करके पक्का करें: जानबूझकर धीली क्वेरी चलाएँ और देखिए कि डेडलाइन के बाद वह जल्दी रिटर्न होती है या नहीं। इसके अलावा बैकअप के तौर पर डेटाबेस‑साइड स्टेटमेंट टाइमआउट सेट करना समझदारी है ताकि कोई कोडपाथ context पास करना भूल भी जाए तो DB सुरक्षित रहे।
आउटबाउंड रिक्वेस्ट http.NewRequestWithContext(ctx, ...) से बनाएं ताकि वही डेडलाइन और कैंसलेशन अपने आप प्रवाहित हो। साथ ही HTTP क्लाइंट और ट्रांसपोर्ट टाइमआउट सेट करें ताकि यदि किसी कारण से बैकग्राउंड context का इस्तेमाल हो जाए या DNS/TLS स्टॉल हो जाए तो आपके पास एक हार्ड अपर बाउंड हो।
निचली लेयर्स को नई context बनाकर समयसीमा बढ़ाने से बचना चाहिए; चाइल्ड टाइमआउट बनाएँ तो वे असल बफर से छोटे होने चाहिए, लंबे नहीं। यदि रिक्वेस्ट के पास बहुत कम समय बचा है तो वैकल्पिक डाउनस्ट्रीम कॉल शुरू न करें, डिग्रेड कर दें या आंशिक डेटा वापस करें।
एंड‑टू‑एंड टाइमआउट्स के काम करने का सबूत जुटाने के लिए इन्हें अलग‑अलग ट्रैक करें: पाथ/डिपेंडेंसी के हिसाब से टाइमआउट काउंट, लेटेंसी पर्सेंटाइल, इन‑फ्लाइट रिक्वेस्ट्स, और DB पूल स्टैट्स। ट्रेसिंग में वही context हैंडलर से लेकर QueryContext तक फॉलो होना चाहिए ताकि आप देख सकें समय कहाँ बिता।


