पाठ 13 / 26

Retries, Idempotency और Retry Storms

सिर्फ़ सुरक्षित, अस्थायी विफलताओं को backoff और budgets के साथ retry करें और बोझ बढ़ने से बचें।

मददगार या हानिकारक

Retry अस्थायी समस्याएँ (pod का restart होना, गिरा connection) ठीक करता है पर लापरवाही से करने पर outages बदतर कर देता है। नियम: सिर्फ़ idempotent अनुरोध retry करें (GET, PUT, DELETE, या idempotency key वाला POST) और सिर्फ़ सुरक्षित विफलता प्रकारों पर (connection errors, timeouts, 502/503/504), 400/401/404 पर कभी नहीं। Jitter के साथ exponential backoff और छोटा retry budget उपयोग करें (जैसे retries अधिकतम 10% अतिरिक्त बोझ जोड़ें)। और amplification याद रखें: 3 परतों में से हर एक 2 अतिरिक्त बार retry करे तो एक user अनुरोध सबसे गहरी service तक 27 calls बन सकता है। हर परत पर नहीं, एक परत पर retry करें (अक्सर gateway या mesh)।

503 पर retry (config)

resilient एक टूटा upstream (flaky, हमेशा 503) और एक स्वस्थ upstream सूचीबद्ध करता है। proxy_next_upstream errors, timeouts और 503 पर अगला server आज़माता है। Gateway config का अंश।

upstream resilient { server flaky:8080; server orders-v1:8080; }

location /resilient/ { proxy_next_upstream error timeout http_503; proxy_pass http://resilient/; }

टूटे server के बावजूद हर अनुरोध सफल, चलाकर

मैंने यह Docker में असली nginx 1.27 gateway पर चलाया, upstreams के रूप में छोटी Node.js services के साथ (पूरा setup केस स्टडी में)। चारों calls orders-v1 से 200 लौटाती हैं: जब भी nginx ने पहले टूटा flaky server चुना, उसे 503 मिला और उसने पारदर्शी रूप से स्वस्थ पर retry किया। Client ने विफलता कभी नहीं देखी।

# four calls to /resilient/x: status and the service that answered

Output:

200 orders-v1, 200 orders-v1, 200 orders-v1, 200 orders-v1

Amplification और backoff, चलाकर

मैंने यह सादा-Python मॉडल चलाया। हर परत पर 2 retries के साथ, 1, 2, 3 और 4 परतों के retries 3, 9, 27 और 81 calls बनाते हैं। दूसरी पंक्ति retry-budget का विचार है: 10% अतिरिक्त पर सीमित retries का मतलब लगभग 1.1 गुना बोझ। Backoff सूची 0.1 s से 5 s की सीमा तक दोगुना होती प्रतीक्षा दिखाती है।

import hashlib, bisect, math, random

def calls(layers, retries): return (1 + retries) ** layers
print([calls(l, 2) for l in (1, 2, 3, 4)])
def with_budget(rate, budget=0.1): return round(1 + budget, 2)
print(with_budget(100))

def backoff(attempt, base=0.1, cap=5.0): return min(cap, base * 2 ** attempt)
print([backoff(a) for a in range(8)])

Output:

[3, 9, 27, 81]
1.1
[0.1, 0.2, 0.4, 0.8, 1.6, 3.2, 5.0, 5.0]

त्वरित जाँच: हर परत पर retry करना ख़तरनाक क्यों है?

  • HTTP में retries की अनुमति नहीं
  • Retries गुणा होते हैं, एक अनुरोध को कई बनाकर विफल होती service पर बोझ डालते हैं
  • Retries responses छोटे करते हैं
  • यह सिर्फ़ logging को प्रभावित करता है
Answer

Retries गुणा होते हैं, एक अनुरोध को कई बनाकर विफल होती service पर बोझ डालते हैं — परतदार retries outage के दौरान बोझ को घातीय रूप से बढ़ाते हैं।