वर्कर बंद हो गया. सिंक नहीं रुका.

बैच के बीच में वर्कर को बंद करें और एक कॉन्टैक्ट का पीछा करें: कौन सा कर्सर सेव हुआ, दोबारा चलाया गया राइट दो बार क्यों नहीं लिखा जाता, और रीस्टार्ट के दौरान सिंक किसके हाथ में रहता है. फिर वे दो घटनाएं जिन्होंने दिखाया कि रिकवरी काम का सिर्फ़ आधा हिस्सा थी.

सिंक फ़ेलियर रिकवरी

जब वर्कर किसी बैच के बीच में बंद हो जाए, तो चल रहे सिंक का क्या होता है?

कर्सर सिर्फ़ एक साफ़ बैच के अंत में आगे बढ़ता है, इसलिए बैच के बीच में बंद किया गया वर्कर उसे उसी बैच की शुरुआत पर छोड़ देता है और अगला रन वही पेज पढ़ता है. हर रिकॉर्ड की डिलीवरी कुंजी पेज कर्सर और उसकी जगह से बनती है, और पूरे हुए राइट्स का सात दिन का लेजर रीप्ले को दो बार लिखने से रोकता है. हर सिंक के लिए एक लीज़ दो रन को एक साथ काम करने से रोकती है. यह आसान आधा हिस्सा था. एक सिंक रन के बाद रन विफल होता रहा, इससे पहले कि किसी चीज़ ने उसे रोका, और एक वर्कस्पेस एरर के साथ और बिना किसी सफलता के चुपचाप पड़ा रहा, इससे पहले कि किसी चीज़ ने ध्यान दिया. इसलिए अब एक ब्रेकर है जो लगातार पांच विफलताओं के बाद सिंक रोक देता है, और चौबीस घंटे बिना सफलता के बीतने पर हर घंटे चलने वाला अलर्ट है.

और जानें
इस पेज पर

AI Engineer द्वारा 3 अक्टूबर 2026 को प्रकाशित एक टॉक में, Crusoe के एक डेवलपर एडवोकेट ने दो A100 नोड पर PyTorch की एक ट्रेनिंग जॉब शुरू की. फिर उन्होंने डेमो के अपने मॉनिटरिंग सिस्टम को बताया कि एक GPU ने XID 79 एरर दिया है. नोड को डाउन चिह्नित किया गया और जॉब रद्द हो गई. प्रोसेस को SIGTERM मिला और उसके पास “उस SIGTERM को संभालने के लिए दो मिनट तक का समय था, ताकि आप या तो अपने चेकपॉइंट सेव कर लें या अपने लॉग फ़्लश कर दें”. एक अतिरिक्त नोड आया, और “slurm जॉब फिर से शुरू होगी. यह चेकपॉइंट लोड करेगी और ट्रेनिंग वहीं से जारी रखेगी जहां छोड़ी थी”. शुरू से अंत तक पंद्रह मिनट से थोड़ा कम. किसी को पेज नहीं किया गया.

सिंक, ट्रेनिंग रन की तुलना में कहीं छोटा जानवर है. सवाल वही है. एक रन को किस चीज़ की ज़रूरत है ताकि वर्कर बंद होने की क़ीमत कुछ मिनट हो और कोई डुप्लिकेट न बने?

यह Tajo के अपने सिंक रनर का जवाब है, जिसे किसी ब्रोशर से नहीं, बल्कि कोड से पढ़ा गया है. Reverse ETL काम का ढांचा बताता है. यह वह है जो काम के बंद हो जाने पर होता है.

एक कॉन्टैक्ट का पीछा करें

एक रन सेव किए गए कर्सर से शुरू करके सोर्स को एक बार में एक पेज पढ़ता है. पेज कर्सर को c3 कहें और हमारे कॉन्टैक्ट को उस पेज का 212वां रिकॉर्ड. कुछ भी लिखे जाने से पहले उस कॉन्टैक्ट के बारे में तीन बातें सच हैं.

पहली, उसके पास एक डिलीवरी कुंजी है. यह कुंजी सिंक की आईडी, सोर्स, पेज कर्सर, पेज पर जगह और ख़ुद रिकॉर्ड का hash है. कर्सर और रिकॉर्ड hash में जाते हैं और कुंजी के अंदर कभी शामिल नहीं किए जाते, इसलिए कुंजी में कोई ईमेल पता नहीं होता.

दूसरी, राइट सीधे डेस्टिनेशन पर नहीं, बल्कि एक लेजर से होकर जाता है. डेस्टिनेशन को कॉल करने से पहले कुंजी को पेंडिंग के रूप में क्लेम किया जाता है. राइट सफल होने पर एंट्री को पूरी चिह्नित किया जाता है और सात दिन तक रखा जाता है.

तीसरी, कर्सर आगे नहीं बढ़ा है. यह सिर्फ़ बैच के अंत में आगे बढ़ता है, सिर्फ़ तब जब उस बैच का हर रिकॉर्ड पहुंच गया हो, और सिर्फ़ तब जब रन यह साबित कर दे कि सिंक अब भी उसी के पास है.

अब वर्कर को राइट और चेकपॉइंट के बीच बंद कर दें.

SIGTERM पर हर क्यू वर्कर एक साझा पंद्रह सेकंड की डेडलाइन में ड्रेन होता है, और प्रोसेस पच्चीस सेकंड बाद बंद हो जाता है, चाहे कुछ भी अब भी चल रहा हो; systemd उसे मारने से पहले पैंतालीस सेकंड देता है. जो रन उस विंडो में पूरा नहीं हुआ, वह अब भी चालू चिह्नित रहता है, और अगला वर्कर उसे क्यू की अटकी जॉब रिकवरी के ज़रिए उठा लेता है. हार्ड किल पर कोई ड्रेन होता ही नहीं. दोनों ही स्थितियों में वही दो तथ्य क़ायम रहते हैं. डेस्टिनेशन के पास हमारा कॉन्टैक्ट है. हमारा कर्सर अब भी c3 कहता है.

रीप्ले

अगला रन c3 से शुरू होता है, वही पेज पढ़ता है, 212वें रिकॉर्ड के लिए वही कुंजी बनाता है, और लेजर से पूछता है. लेजर कहता है: पूरा. डेस्टिनेशन को कॉल नहीं किया जाता; सेव किया गया नतीजा रीप्ले होता है और रन आगे बढ़ जाता है. बल्क पाथ पर, जहां Brevo का इंपोर्ट एंडपॉइंट एक ही कॉल में पूरा पेज लेता है, यही जांच रिक्वेस्ट बनने से पहले हर कुंजी के लिए चलती है. जिस पेज की हर कुंजी पहले से पूरी हो, वह कुछ नहीं भेजता.

पूरी तरकीब यही है, और यह बताना ज़रूरी है कि यह काम क्यों करती है. कुंजी बैच की शुरुआत में पेज कर्सर से बनती है, उस रन से नहीं जो संयोग से चल रहा था. क्रैश, साठ सेकंड बाद की दोबारा कोशिश और कल का शेड्यूल्ड रन, सभी एक ही रिकॉर्ड के लिए एक ही कुंजी बनाते हैं. लेजर को फ़र्क़ नहीं पड़ता कि उनमें से कौन पूछ रहा है.

अब क़ीमत. लेजर सात दिन बाद भूल जाता है. कोड की अपनी टिप्पणी कहती है कि सात दिन एक देर से हुई दोबारा कोशिश और एक छोटी रुकावट को कवर करते हैं, और बस इतना ही दावा करती है. उससे पुराने कर्सर से रीप्ले फिर से लिखता है. और जो रन ज़बरदस्ती रोका गया हो, उसे इंतज़ार करना पड़ सकता है. बंद हुए रन की लीज़ पंद्रह मिनट तक बनी रहती है, और शेड्यूल्ड सिंक पर जिस रन की लीज़ खाली हो, उसे तभी साफ़ करके दोबारा शेड्यूल किया जाता है जब उसे शुरू हुए एक घंटे से ज़्यादा हो चुका हो. Crusoe का डेमो पंद्रह मिनट से कम में वापस आ जाता है. हमारा हमेशा नहीं.

रीस्टार्ट के दौरान सिंक किसके हाथ में है

एक समय में एक ही रन किसी सिंक का मालिक होता है. रन Redis में अपने टोकन के साथ एक लीज़ लेता है, जिसकी उम्र पंद्रह मिनट होती है और जो हर पांच मिनट में रिन्यू होती है. हर चेकपॉइंट से पहले वह पुष्टि करता है कि लीज़ अब भी उसी के पास है. अगर नहीं, तो वह रुक जाता है. रन उस सीधे वाक्य के साथ रिकॉर्ड होता है जिसे यूज़र पढ़ेगा: “The run lost its slot part-way through and stopped to avoid writing twice.” (रन बीच में अपनी जगह खो बैठा और दो बार लिखने से बचने के लिए रुक गया.) जो दूसरा रन किसी सक्रिय मालिक से टकराए, उसकी दोबारा कोशिश नहीं की जाती. वह “Another run of this sync was already in progress” (इस सिंक का एक और रन पहले से चल रहा था) के साथ ख़त्म होता है, और अगला शेड्यूल्ड रन सामान्य रूप से शुरू होता है.

सुधार की ज़िम्मेदारी भी लिखी हुई है. हर विफलता कोड एक वाक्य से जुड़ा है कि क्या हुआ, एक वाक्य से कि आगे क्या करना है, और एक मालिक से: आप या Tajo. जो डेस्टिनेशन हर रिकॉर्ड को इसलिए अस्वीकार कर दे कि मैपिंग ग़लत है, वह आपकी ज़िम्मेदारी है. खोई हुई लीज़ हमारी ज़िम्मेदारी है. रन का पेज अगला कदम दिखाता है; सिंक रिकवरी गाइड बताती है कि कौन सी.

अब तक यह आसान आधा हिस्सा है. एक क्रैश की क़ीमत एक रीप्ले है और कुछ भी दो बार नहीं लिखा जाता. बाक़ी आधा हिस्सा दो घटनाओं ने सिखाया.

विफलता के बाद विफलता

एक सिंक बार बार विफल हुआ. किसी तेज़ लूप में नहीं: शेड्यूलर के हर अंतराल में एक बार, रन के बाद रन, हर बार एक ही संदेश के साथ, कि किसी कस्टम आईडी में कोलन नहीं हो सकता. रन कभी डेस्टिनेशन तक नहीं पहुंचा. वह क्यू में डाले जाते समय ही बंद हो गया, किसी रनर के देखने से पहले, इसलिए रनर से जुड़े किसी अलर्ट के पास जुड़ने के लिए कुछ था ही नहीं.

इस सिलसिले पर कोई नज़र नहीं रख रहा था. पूरा निष्कर्ष यही है.

समाधान एक ब्रेकर है. हर रिकॉर्ड हुई विफलता के बाद यह सिंक में पिछले बदलाव के बाद से उसके आख़िरी पांच पूरे हुए रन पढ़ता है. अगर पांचों एरर हों, तो यह सिंक को रोक देता है, circuit_breaker_tripped कारण के साथ एक ऑडिट एंट्री लिखता है, और एक क्रिटिकल अलर्ट भेजता है. सिलसिला सेव किए गए रन से निकाला जाता है, वर्कर के किसी काउंटर से नहीं. किसी भी रास्ते से रिकॉर्ड हुई विफलता गिनी जाती है, उस क्यू वाली विफलता सहित जिससे यह घटना हुई. कोड की टिप्पणी कहती है कि यह उस घटना को पांचवें रन पर रोक देता. एक सफलता सिलसिले को रीसेट कर देती है. सिंक को फिर से चालू करने पर ब्रेकर पुरानी एंट्री पर दोबारा ट्रिप होने के बजाय फिर से तैयार हो जाता है. Shopify कनेक्टर गाइड यही बात एक लाइन में कहती है: लगातार पांच विफल रन के बाद सिंक ख़ुद को रोक देता है और उसका पेज आख़िरी एरर दिखाता है.

ब्रेकर जो नहीं करता, वह है कुछ ठीक करना. Crusoe नोड बदल देता है और बिना पूछे जॉब फिर से शुरू कर देता है. Tajo रुककर पूछता है: रुका हुआ सिंक तब तक रुका रहता है जब तक कोई व्यक्ति कारण ठीक करके उसे दोबारा पब्लिश न करे. यह सतर्क डिफ़ॉल्ट जान बूझकर है: ग्राहक रिकॉर्ड में लिखने वाले सिंक की अपने आप दोबारा कोशिश ख़ुद एक राइट है.

वह ख़ामोशी जिसे किसी ने नहीं देखा

आपत्ति जायज़ है, और वह कुछ यूं है: “पांच विफलताओं के बाद रुकने वाला ब्रेकर दूसरी घटना को भी पकड़ लेता. दूसरा निगरानी तंत्र क्यों बनाया जाए?” क्योंकि दूसरी घटना विफलता जैसी दिखती ही नहीं थी. लंबे समय तक एक वर्कस्पेस में एक सक्रिय सिंक था, एरर एंट्री का बढ़ता ढेर था और कभी एक भी सफलता नहीं, और किसी चीज़ ने अलर्ट नहीं दिया. हर मौजूद सिग्नल किसी गतिविधि से जुड़ा था: कोई रन विफल हुआ, कोई webhook आया, कोई बजट पार हुआ. शून्य सफलताएं कोई घटना नहीं हैं. यह किसी घटना का न होना है, और ब्रेकर तभी चलता है जब कोई विफलता रिकॉर्ड हो. बंद पड़ा शेड्यूलर, अटकी हुई क्यू, कभी शुरू न होने वाला वर्कर: ये कुछ रिकॉर्ड नहीं करते, और ब्रेकर कभी नहीं जागता.

रेल इंजन का डेड मैन हैंडल उल्टे तरीक़े से काम करता है. ड्राइवर को उसे लगातार दबाए रखना पड़ता है, और ख़ामोशी ट्रेन रोक देती है. सिंक वाला वर्ज़न हर घंटे एक बार, हर घंटे के तेरह मिनट बाद चलता है. सक्रिय सिंक वाले हर वर्कस्पेस से यह एक सवाल पूछता है: क्या पिछले चौबीस घंटों में कोई रन सफल हुआ? जिस वर्कस्पेस का सबसे पुराना सक्रिय सिंक एक दिन से कम पहले चालू किया गया हो, उसे छोड़ दिया जाता है; उसे अभी सफल होने के लिए एक दिन नहीं मिला. जो उससे पुराना और ख़ामोश हो, उसे एक क्रिटिकल अलर्ट मिलता है. अलर्ट बताता है कि कौन सी ख़ामोशी है: रन रिकॉर्ड हुए पर कोई सफल नहीं, या कोई रन रिकॉर्ड ही नहीं हुआ. यह हर घटना पर एक बार चलता है. Redis में एक मार्कर उसे पहले सफल रन तक बंद रखता है, फिर हट जाता है, ताकि अगली घटना पर फिर से अलर्ट जाए.

दोनों निगरानी तंत्र घटनाओं के बाद एक ही commit में आए. ब्रेकर की टिप्पणी कहती है कि यह पहली घटना को पांचवें रन पर रोक देता. डेड मैन की टिप्पणी कहती है कि बहुत पहले चालू किया गया और शून्य रन वाला सिंक ज़रूर अलर्ट दे. जब ज़रूरत थी, तब इनमें से कोई मौजूद नहीं था. हमने इन्हें बाद में लिखा, और दोनों घटनाओं को फ़ाइल के हेडर में दर्ज किया ताकि कोई कारण न भूले.

क्या बना है, और क्या नहीं

इस लेख के लिखे जाने तक जो बना है:

  • कर्सर हर साफ़ बैच के अंत में सेव होता है, और क्रैश पर भी दोबारा.
  • हर रिकॉर्ड की डिलीवरी कुंजी पेज कर्सर और उसकी जगह से बनती है, और डेस्टिनेशन को हर कॉल से पहले सात दिन के लेजर में जांची जाती है.
  • हर सिंक के लिए एक लीज़, जिसकी उम्र पंद्रह मिनट है.
  • हर रन के लिए पांच कोशिशें, साठ सेकंड से शुरू होने वाले बैकऑफ़ के साथ.
  • एक ब्रेकर जो लगातार पांच विफलताओं के बाद सिंक रोक देता है और कारण रिकॉर्ड करता है.
  • चौबीस घंटे बिना सफलता के बीतने पर हर घंटे चलने वाला अलर्ट.

जो नहीं बना, और जिस पर भरोसा करने से पहले जानना ज़रूरी है:

  • डेड मैन सिंक को नहीं, वर्कस्पेस को देखता है. एक स्वस्थ सिंक उसी वर्कस्पेस में अपने ख़ामोश साथी को छिपा देता है.
  • इसका अलर्ट Tajo के ऑपरेटरों तक पहुंचता है: एक लॉग लाइन हमेशा, और जहां कॉन्फ़िगर किया गया हो वहां Sentry, एक webhook या एक ईमेल. यह सिंक के पेज पर नहीं दिखता. ब्रेकर का रोकना दिखता है.
  • रुके हुए सिंक को कोई चीज़ फिर से शुरू नहीं करती. एक व्यक्ति करता है.
  • हार्ड किल के बाद रिकवरी तेज़ नहीं है. लीज़ पंद्रह मिनट तक बनी रह सकती है और सफ़ाई एक घंटा इंतज़ार करती है.
  • दोनों सीमाएं, पांच विफलताएं और चौबीस घंटे, डिप्लॉयमेंट सेटिंग्स हैं. आप इन्हें हर सिंक के लिए अलग से सेट नहीं कर सकते.

लगभग सही चरणों की लंबी श्रृंखला फिर भी विफल होती है; बाहर निकलने का रास्ता अंतिम स्थिति तय करना और उसकी जांच करना है. यह लेख उसी का ऑपरेशनल आधा हिस्सा है. तय करें कि एक बंद हुआ रन अपने पीछे क्या छोड़ता है, और उस कुछ-नहीं की जांच करें जो ख़ामोशी अपने पीछे छोड़ती है.

Crusoe की आख़िरी बात यह थी कि “विफलताएं अनिवार्य हैं”. हमारी बात एक क़दम आगे है. क्रैश आसान आधा हिस्सा था, क्योंकि उसने एंट्री छोड़ीं. ख़ामोशी वह आधा हिस्सा थी जिसे हमें ढूंढने निकलना पड़ा.

अक्सर पूछे जाने वाले प्रश्न

जब वर्कर किसी बैच के बीच में बंद हो जाए, तो चल रहे सिंक का क्या होता है?

कर्सर अब भी अधूरे बैच की शुरुआत पर ही रहता है, क्योंकि Tajo उसे तभी आगे बढ़ाता है जब कोई बैच पूरी तरह सही से पहुंच जाए. अगला रन वही पेज फिर से पढ़ता है. हर रिकॉर्ड के पास एक डिलीवरी कुंजी होती है, जो पेज कर्सर और पेज पर उसकी जगह से बनती है, और एक लेजर हर पूरे हुए राइट को सात दिन तक रखता है. इसलिए पहले से लिखे गए रिकॉर्ड डेस्टिनेशन को दूसरी बार भेजे नहीं जाते, बल्कि लेजर से रीप्ले किए जाते हैं.

दोबारा चलाया गया सिंक एक ही कॉन्टैक्ट को दो बार क्यों नहीं लिखता?

डेस्टिनेशन को हर कॉल से पहले रन रिकॉर्ड की डिलीवरी कुंजी को एक लेजर में जांचता है. यह कुंजी सिंक, सोर्स, पेज कर्सर, पेज पर जगह और ख़ुद रिकॉर्ड का hash है, इसलिए क्रैश, एक मिनट बाद की दोबारा कोशिश और अगले दिन का शेड्यूल्ड रन, तीनों एक ही रिकॉर्ड के लिए एक ही कुंजी बनाते हैं. सात दिन की अवधि में पूरी के रूप में चिह्नित की गई कुंजी दोबारा नहीं भेजी जाती.

सिंक के लिए डेड मैन अलर्ट क्या है?

ऐसी जांच जो विफलता पर नहीं, बल्कि किसी चीज़ के न होने पर चलती है. हर घंटे Tajo हर उस वर्कस्पेस को देखता है जिसमें कोई सक्रिय सिंक है, और अलर्ट भेजता है अगर पिछले चौबीस घंटों में कोई रन सफल नहीं हुआ, उस स्थिति सहित जब कोई रन रिकॉर्ड ही नहीं हुआ. यह हर घटना पर एक बार चलता है और पहले सफल रन के बाद फिर से तैयार हो जाता है. यह इसलिए है क्योंकि विफलता अलर्ट सिर्फ़ वही विफलताएं देख सकता है जो रिकॉर्ड हुई हों.

अर्ली एक्सेस का अनुरोध करें

अपना पहला नाम और ईमेल पता दर्ज करें। Tajo एक्सेस अनुरोध की पुष्टि के लिए हम आपको एक लिंक ईमेल करेंगे।

Brevo प्राप्त करें

यह पेज Markdown में: worker-died-sync-didnt.md

Markdown लोड हो रहा है…
Claude में खोलें ChatGPT में खोलें