पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन
घातीय बैकऑफ, इडेम्पोटेंसी कुंजी और दूषित संदेशों के डेड-लेटर मार्ग से कार्यों को सुदृढ़ बनाइए।
पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन, CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप का एक निःशुल्क पाठ है। यह 4 में से 3वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
कार्यों को लचीलेपन की आवश्यकता क्यों होती है
FastAPI बैकएंड में आप धीमे कार्य (ईमेल भेजना, कार्ड से शुल्क लेना, तृतीय-पक्ष API कॉल करना) Celery worker को सौंपते हैं, ताकि HTTP अनुरोध जल्दी लौट सके। लेकिन पृष्ठभूमि के कार्य प्रतिकूल परिस्थितियों में चलते हैं: नेटवर्क रुक-रुककर काम कर सकता है, API अनुरोधों की दर सीमित कर सकते हैं और worker कार्य के बीच में क्रैश हो सकते हैं।
लचीले कार्य को विफलता की तीन स्थितियों से निपटना चाहिए:
- क्षणिक विफलताएँ — घातीय बैकऑफ़ के साथ पुनःप्रयास करें, ताकि संघर्ष कर रही सेवा पर लगातार दबाव न पड़े।
- डुप्लिकेट डिलीवरी — एक ही संदेश दो बार संसाधित हो सकता है, इसलिए कार्य idempotent होने चाहिए।
- दूषित संदेश — जो कार्य हमेशा विफल होता है, उसे अंतहीन चक्र में डालने के बजाय डेड-लेटर कतार में भेजना चाहिए।
यह पाठ इन तीनों को एक साथ जोड़ता है।
कम-से-कम-एक-बार डिलीवरी
Celery ब्रोकर (RabbitMQ, Redis) कम-से-कम-एक-बार डिलीवरी देते हैं, बिल्कुल-एक-बार नहीं। किसी संदेश को ack तभी किया जाता है जब कार्य पूरा हो जाए। यदि worker काम करने के बाद, लेकिन ack करने से पहले बंद हो जाता है, तो ब्रोकर संदेश फिर भेजता है और कार्य दोबारा चलता है।
acks_late=True के साथ ack निष्पादन के बाद होता है — यह क्रैश से अधिक सुरक्षित है, लेकिन यह सुनिश्चित करता है कि कुछ कार्य दो बार चलेंगे। यही मूल कारण है कि idempotency वैकल्पिक नहीं है।
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
# Recommended resilience defaults
app.conf.update(
task_acks_late=True, # ack only after the task body returns
task_reject_on_worker_lost=True, # requeue if the worker is killed
worker_prefetch_multiplier=1, # don't hoard messages on one worker
)autoretry_for के साथ स्वचालित पुनःप्रयास
पुनःप्रयास करने का सबसे सरल तरीका यह घोषित करना है कि कौन-से अपवाद पुनःप्रयास योग्य हैं। Celery उन्हें पकड़कर कार्य को अपने-आप फिर शेड्यूल करता है।
autoretry_for— वे अपवाद वर्ग जो पुनःप्रयास शुरू करते हैं।max_retries— कार्य को विफल चिह्नित करने से पहले की अधिकतम सीमा।retry_backoff— घातीय बैकऑफ़ चालू करता है (हर प्रयास पर विलंब दोगुना होता है)।
केवल क्षणिक त्रुटियों (timeout, 5xx, कनेक्शन रीसेट) पर पुनःप्रयास करें। खराब इनपुट से उत्पन्न ValueError पर कभी भी आँख बंद करके पुनःप्रयास न करें — यह हर बार उसी तरह विफल होगा।
import requests
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(
autoretry_for=(requests.exceptions.RequestException,),
max_retries=5,
retry_backoff=True, # 1s, 2s, 4s, 8s, ...
retry_backoff_max=600, # cap the delay at 10 minutes
retry_jitter=True, # randomize to avoid thundering herd
)
def call_payment_api(charge_id: str):
resp = requests.post("https://api.example.com/charge", json={"id": charge_id}, timeout=10)
resp.raise_for_status()
return resp.json()घातीय बैकऑफ़ की व्याख्या
घातीय बैकऑफ़ का अर्थ है कि प्रयासों के बीच प्रतीक्षा ज्यामितीय रूप से बढ़ती है: nवें प्रयास का विलंब लगभग base * 2 ** n होता है और अधिकतम सीमा पर रुक जाता है। इससे संघर्ष कर रही डाउनस्ट्रीम सेवा को पुनःस्थापित होने का समय मिलता है, बजाय इसके कि उस पर लगातार पुनःप्रयासों का दबाव डाला जाए।
Jitter यादृच्छिकता जोड़ता है, ताकि एक ही क्षण में विफल हुए हजारों कार्य भी उसी क्षण पुनःप्रयास न करें (इसे “thundering herd” कहा जाता है)। यहाँ वह गणित दिया गया है जिसे Celery एक स्वतंत्र, सरल प्रोग्राम के रूप में लागू करता है।
import random
def backoff_delay(attempt, base=1, cap=600, jitter=True):
delay = min(cap, base * (2 ** attempt))
if jitter:
delay = random.uniform(0, delay) # full jitter
return delay
for attempt in range(8):
raw = min(600, 1 * (2 ** attempt))
print(f"attempt {attempt}: raw={raw:>3}s jittered~={backoff_delay(attempt):6.1f}s")self.retry के साथ मैन्युअल पुनःप्रयास
जब आपको कस्टम तर्क की आवश्यकता हो — जैसे प्रतिक्रिया हेडर से विलंब तय करना या केवल कुछ स्थिति कोड पर पुनःप्रयास करना — तो कार्य को बाँधें और self.retry() को स्पष्ट रूप से कॉल करें।
bind=True का उपयोग करके self प्राप्त करें, प्रयास संख्या जानने के लिए self.request.retries पढ़ें और विलंब के लिए countdown दें। self.retry() के परिणाम को raise करने से वर्तमान निष्पादन साफ़ तौर पर रुक जाता है।
import requests
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, max_retries=5)
def sync_inventory(self, sku: str):
resp = requests.get(f"https://api.example.com/stock/{sku}", timeout=5)
if resp.status_code == 429: # rate limited
wait = int(resp.headers.get("Retry-After", 2 ** self.request.retries))
raise self.retry(countdown=wait)
resp.raise_for_status()
return resp.json()["qty"]Idempotency का वास्तविक अर्थ
किसी ऑपरेशन को idempotent तब कहा जाता है, जब उसे दो बार चलाने का प्रभाव उसे एक बार चलाने के समान हो। क्योंकि Celery कम-से-कम-एक-बार डिलीवरी करता है, इसलिए हर वह कार्य जो स्थिति बदलता है (कार्ड से शुल्क लेना, रिकॉर्ड बनाना, स्टॉक घटाना) idempotent होना चाहिए, वरना ग्राहकों से दो बार शुल्क लिया जा सकता है।
मानक साधन एक idempotency key है: संदेश के लिए नहीं, बल्कि व्यावसायिक उद्देश्य के लिए विशिष्ट पहचानकर्ता। आप स्थायी भंडारण में “मैंने key X को पहले ही संसाधित कर लिया है” दर्ज करते हैं और अगली डिलीवरी पर कार्य को तुरंत समाप्त कर देते हैं।
- API अनुरोध से key भेजें (क्लाइंट भी इसे दे सकते हैं)।
- इसे किसी तालिका या Redis में
UNIQUEconstraint के साथ संग्रहीत करें। - रेस से सुरक्षित बनाने वाला तत्व एप्लिकेशन तर्क नहीं, बल्कि constraint है।
Idempotency Guard
यह पैटर्न अलग रूप में देखें: एक guard, जो पूर्ण की गई keys को याद रखता है और समवर्ती कॉल के बीच भी प्रभाव को दो बार चलने से रोकता है। वास्तविक कोड में seen सेट Redis SET NX या विशिष्ट key वाली DB पंक्ति बन जाता है, लेकिन तर्क समान रहता है।
import threading
class IdempotencyGuard:
def __init__(self):
self._seen = set()
self._lock = threading.Lock()
def run_once(self, key, effect):
with self._lock: # the unique-constraint stand-in
if key in self._seen:
return "skipped (duplicate)"
self._seen.add(key)
return effect()
guard = IdempotencyGuard()
charges = []
def charge():
charges.append(99)
return "charged 99"
print(guard.run_once("order-123", charge))
print(guard.run_once("order-123", charge)) # duplicate delivery
print("total charges applied:", len(charges))Celery कार्य के अंदर Idempotency
व्यवहार में, आप प्रभाव को डेटाबेस लेन-देन में लपेटते हैं और सत्य का स्रोत UNIQUE बाधा को बनाते हैं। पहले idempotency कुंजी डालें; यदि डालने पर unique-violation आती है, तो इसका अर्थ है कि पिछली या समवर्ती डिलीवरी इसे पहले ही संभाल चुकी है, इसलिए आप तुरंत लौट आते हैं।
इससे जाँच और साइड इफ़ेक्ट परमाण्विक बने रहते हैं — ऐसी कोई अवधि नहीं रहती जिसमें एक डिलीवरी “पूरा नहीं हुआ” देखे, जबकि दूसरी डिलीवरी शुल्क ले रही हो।
from sqlalchemy.exc import IntegrityError
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, acks_late=True, max_retries=3, retry_backoff=True)
def charge_order(self, order_id: str, idem_key: str):
with db_session() as s:
try:
s.add(ProcessedKey(key=idem_key)) # UNIQUE column
s.flush() # raises on duplicate
except IntegrityError:
s.rollback()
return {"status": "already_processed", "order_id": order_id}
amount = payment_gateway.charge(order_id, idempotency_key=idem_key)
s.commit()
return {"status": "charged", "amount": amount}विषाक्त संदेश और डेड-लेटर कतार
कुछ संदेश कभी सफल नहीं हो सकते: गलत प्रारूप वाले पेलोड, हटाई गई पंक्तियों के संदर्भ, या ऐसा बग जो हमेशा अपवाद उत्पन्न करता है। इन्हें हमेशा retry करते रहना वर्करों को व्यर्थ व्यस्त करता है और आपके लॉग भर देता है। इन्हें विषाक्त संदेश कहा जाता है।
डेड-लेटर कतार (DLQ) एक अलग कतार होती है, जहाँ समाप्त या अस्वीकृत संदेशों को जाँच, चेतावनी या मैन्युअल replay के लिए रखा जाता है। RabbitMQ में आप x-dead-letter-exchange तर्क के साथ एक कतार घोषित करते हैं; जो संदेश अस्वीकृत होते हैं (nack के साथ requeue=False) या जिनका TTL समाप्त हो जाता है, उन्हें ब्रोकर अपने-आप वहाँ भेज देता है।
from kombu import Exchange, Queue
dead_exchange = Exchange("dlx", type="direct")
task_queues = (
Queue(
"payments",
Exchange("payments"),
routing_key="payments",
queue_arguments={
"x-dead-letter-exchange": "dlx",
"x-dead-letter-routing-key": "payments.dead",
},
),
Queue("payments_dead", dead_exchange, routing_key="payments.dead"),
)समाप्त कार्यों को DLQ में भेजना
ब्रोकर अस्वीकृति होने पर संदेश को डेड-लेटर में भेज देता है, लेकिन सेलेरी की retry व्यवस्था max_retries तक पहुँचने पर अपने-आप अस्वीकृति नहीं भेजती — वह केवल कार्य को FAILED चिह्नित करती है। समाप्त हो चुके कार्यों को DLQ में भेजने के लिए आप MaxRetriesExceededError पकड़ते हैं (या अंतिम प्रयास का पता लगाते हैं) और पेलोड को अपने डेड-लेटर कार्य या कतार में स्पष्ट रूप से भेजते हैं।
डेड-लेटर हैंडलर को कभी भी दोबारा process नहीं करना चाहिए — उसे विफलता दर्ज करनी चाहिए, alert उत्पन्न करना चाहिए और पेलोड सहेजना चाहिए, ताकि मूल कारण ठीक करने के बाद कोई ऑपरेटर उसे replay कर सके।
from celery import Celery
from celery.exceptions import MaxRetriesExceededError
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, max_retries=5, retry_backoff=True)
def process_event(self, payload: dict):
try:
do_work(payload)
except TransientError as exc:
try:
raise self.retry(exc=exc)
except MaxRetriesExceededError:
dead_letter.delay(payload, reason=str(exc)) # park it
except PermanentError as exc:
dead_letter.delay(payload, reason=str(exc)) # never retry
@app.task
def dead_letter(payload: dict, reason: str):
store_failed_message(payload, reason)
alert_oncall(reason)सब कुछ एक साथ जोड़ना
उत्पादन-स्तर का resilient task हर घटक को मिलाता है:
- acks_late, ताकि क्रैश होने पर काम खोने के बजाय फिर से deliver हो।
- केवल अस्थायी त्रुटियों पर autoretry_for, साथ में exponential backoff + jitter।
- unique constraint द्वारा सुरक्षित idempotency key, ताकि दोबारा delivery होने पर कोई नुकसान न हो।
- स्थायी त्रुटियों और समाप्त retries के लिए dead-letter मार्ग।
मानसिक मॉडल यह है: अस्थायी त्रुटि को retry करें, डुप्लिकेट को deduplicate करें, और न बच सकने वाले संदेश को dead-letter करें। प्रत्येक व्यवस्था विफलता के अलग प्रकार को संभालती है — साथ मिलकर वे वर्कर को डेटा को चुपचाप दूषित करने के बजाय सुरक्षित रूप से विफल होने देती हैं।
@app.task(
bind=True, acks_late=True,
autoretry_for=(TransientError,),
max_retries=5, retry_backoff=True, retry_jitter=True,
)
def handle_webhook(self, payload: dict, idem_key: str):
if already_processed(idem_key): # unique-constraint check
return "duplicate-ignored"
try:
result = apply_effect(payload, idem_key)
except PermanentError as exc:
dead_letter.delay(payload, reason=str(exc))
return "dead-lettered"
mark_processed(idem_key)
return resultत्वरित जाँच
आपका सेलेरी task क्रेडिट कार्ड से शुल्क लेता है और acks_late=True के साथ चलता है। क्योंकि ब्रोकर कम-से-कम-एक-बार delivery करता है, वही संदेश कभी-कभी दो बार process हो जाता है। दो बार शुल्क लगने से बचने का सही प्राथमिक उपाय क्या है?
पुनरावलोकन
आपने सीखा कि वास्तविक दुनिया की विफलताओं में सेलेरी tasks को कैसे टिकाऊ बनाया जाए:
- सेलेरी कम-से-कम-एक-बार delivery करता है;
acks_late=Trueवर्कर क्रैश से सुरक्षा देता है, लेकिन कभी-कभी डुप्लिकेट runs होना सुनिश्चित करता है। - Jitter के साथ exponential backoff (
retry_backoff/autoretry_forया मैन्युअलself.retryके माध्यम से) downstream सेवाओं पर अत्यधिक भार डाले बिना अस्थायी विफलताओं को संभालता है — केवल अस्थायी त्रुटियों को retry करें। - Idempotency keys, unique constraint द्वारा समर्थित होकर, डुप्लिकेट deliveries को harmless बनाती हैं और जाँच को साइड इफ़ेक्ट के साथ atomic रखती हैं।
- Dead-letter queues विषाक्त संदेशों और समाप्त retries को alerting तथा मैन्युअल replay के लिए रखती हैं, बजाय उन्हें अनंत loop में चलाने के।
नियम याद रखें: अस्थायी त्रुटि को retry करें, डुप्लिकेट को deduplicate करें, और न बच सकने वाले संदेश को dead-letter करें।
एआई शिक्षक के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप सीखें — निःशुल्क
अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।
- पाठ्यक्रम
- 21
- पाठ
- 84
अक्सर पूछे जाने वाले प्रश्न
क्या “पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन” पाठ निःशुल्क है?
हाँ — FastAPI बैकएंड डेवलपमेंट बूटकैंप अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
“पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन” में मैं क्या सीखूँगा?
घातीय बैकऑफ, इडेम्पोटेंसी कुंजी और दूषित संदेशों के डेड-लेटर मार्ग से कार्यों को सुदृढ़ बनाइए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ FastAPI बैकएंड डेवलपमेंट बूटकैंप का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।
क्या FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?
पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर FastAPI बैकएंड डेवलपमेंट बूटकैंप शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 3वाँ पाठ है।
“पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन” पाठ पूरा करने में कितना समय लगता है?
CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।
क्या मैं इस FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में कोड लिख और चला सकता हूँ?
हाँ। हर FastAPI बैकएंड डेवलपमेंट बूटकैंप पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।
इस पाठ्यक्रम के सभी पाठ
- BackgroundTasks के साथ हल्का ऑफलोडिंग
- Celery वर्करों को FastAPI ऐप से जोड़ना
- पुनःप्रयास, इडेम्पोटेंसी और डेड-लेटर प्रबंधन
- Celery Beat के साथ निर्धारित और आवधिक जॉब