अलर्ट एक बार चलें, हर सर्वर पर एक बार नहीं
हमारे ऑपरेशनल अलर्ट में से एक, बिल्डर के पब्लिश किए गए ऐप के लिए "low credits", ज़्यादा से ज़्यादा दिन में एक बार हम तक पहुँचना चाहिए। एक दोपहर यह पाँच बार आ गया। कारण: हमारे API की हर कॉपी अपनी मेमोरी में याद रखती थी कि उसने कौन से अलर्ट पहले भेजे हैं, इसलिए दो सर्वरों ने इसे दो बार भेजा, और हर डिप्लॉय उस मेमोरी को मिटा देता था और उन्हें दोबारा भेजने देता था। यही पैटर्न एक बैकग्राउंड जॉब में भी था, जो अपना काम दो बार कर रहा था। अब दोनों एक साझा स्टोर के ज़रिए तालमेल रखते हैं, और अगर वह स्टोर उपलब्ध न हो तो दोनों सुरक्षित पक्ष में विफल होते हैं।
लक्षण
जब कुछ ध्यान माँगता है, तब Mythex अपनी टीम को अलर्ट भेजता है: कोई सेवा बंद है, कोई जॉब रुकी है, या किसी बिल्डर का क्रेडिट बैलेंस कम हो रहा है जबकि उनका ऐप पब्लिश है। हर अलर्ट की एक डेडुप विंडो होती है, यानी वह समय-सीमा जिसमें वही अलर्ट केवल एक बार भेजा जाता है। low-credits अलर्ट के लिए यह विंडो 24 घंटे है।
एक ही दोपहर में एक ही अलर्ट की पाँच कॉपियों का मतलब था कि विंडो टिक नहीं रही थी। इससे भी बुरा यह था कि वे जोड़ियों में आ रही थीं, लगभग 11 मिनट के अंतर पर।
ऐसा क्यों हुआ
हमारे alerts पैकेज ने अपनी डेडुप मेमोरी में रखी थी: एक टेबल, "alert key → आखिरी बार कब भेजा गया", चलते प्रोसेस के अंदर। यह तब ठीक चलता है जब एक ही प्रोसेस हो जो कभी रीस्टार्ट न हो। प्रोडक्शन में न एक प्रोसेस है, न कोई ऐसा प्रोसेस जो रीस्टार्ट न होता हो:
- API के दो रेप्लिका। प्रोडक्शन में API की दो कॉपियाँ चलती हैं। हर एक की अपनी टेबल थी, इसलिए हर एक ने अलर्ट एक बार भेजा। यही वे जोड़ियाँ थीं।
- डिप्लॉय। हर डिप्लॉय दोनों रेप्लिका को नए प्रोसेस और खाली टेबल से बदल देता था। डिप्लॉय के बाद अलर्ट फिर से गया, जैसे वह पहले कभी भेजा ही न गया हो।
हर जोड़ी के दो अलर्ट के बीच का लगभग 11 मिनट का अंतर उस जॉब से आ रहा था जो यह अलर्ट उठाती है। वह दोनों रेप्लिका पर चलती है और एक लॉक लेती है, इसलिए रेप्लिका बारी-बारी से जीतती हैं, और हर एक ने अपनी बारी आने पर अलर्ट उठाया।
उसी कोड में एक दूसरा, ज़्यादा चुपचाप चलने वाला बग था। टेबल को हमेशा के लिए बढ़ने से रोकने के लिए वह खुद को तब साफ़ करती थी जब उसमें 500 keys हो जातीं: हर ऐसी एंट्री हटा देती थी जो 15 मिनट से पुरानी हो, चाहे उसकी विंडो कितनी भी हो। लोड के तहत 24 घंटे की डेडुप सवा घंटे तक ही चल पाती थी।
समाधान
हर अलर्ट का एक साझा रिकॉर्ड
अब API अलर्ट भेजने से पहले उसकी डेडुप की Redis में क्लेम करती है। Redis एक साझा इन-मेमोरी स्टोर है जिससे दोनों रेप्लिका बात करते हैं, और जो API के रीस्टार्ट होने पर रीस्टार्ट नहीं होता। यह क्लेम एक ही ऑपरेशन है: "यह key तभी सेट करो जब वह मौजूद न हो, और विंडो के बाद उसे expire कर दो।" जो रेप्लिका इसे पहले क्लेम करती है वह अलर्ट भेजती है; दूसरी को key पहले से लिया हुआ मिलता है और वह चुप रहती है। डिप्लॉय के बाद कोई नया प्रोसेस पाता है कि key अब भी मौजूद है।
Key और विंडो उसी नियम से आते हैं जिसे alerts पैकेज इस्तेमाल करता है, अब दोनों के बीच साझा, ताकि दोनों परतें इस पर असहमत न हों कि "वही अलर्ट" किसे माना जाए।
विफल होने पर खुला रहें, बंद नहीं
सबसे महत्वपूर्ण फ़ैसला यह था कि जब Redis तक पहुँच न हो तो क्या करें। हमने तय किया कि अलर्ट फिर भी भेजा जाए। अगर Redis बंद है, या 1.5 सेकंड में जवाब नहीं देता, तो अलर्ट चला जाता है। डुप्लीकेट अलर्ट शोर है। छूटा हुआ अलर्ट मतलब किसी को पता ही नहीं चला।
सबसे गंभीर अलर्ट, P0, डेडुप से कभी नहीं रोके जाते।
हर एंट्री अपनी विंडो रखती है
alerts पैकेज की अपनी इन-मेमोरी डेडुप में अब हर एंट्री यह भी दर्ज करती है कि उसकी विंडो कब खत्म होती है, और सफ़ाई केवल उन्हीं एंट्री को हटाती है जिनकी विंडो खत्म हो चुकी है। 24 घंटे की key 24 घंटे तक रहती है, चाहे और कितने भी अलर्ट आएँ।
वही बग, कहीं और
यही पैटर्न बैकग्राउंड के एक काम को भी दोगुना कर रहा था: एक reconciliation जॉब, जो हर workspace की स्थिति का हमारा रिकॉर्ड हमारे सैंडबॉक्स प्रोवाइडर से मिलाकर रखती है। हर पास अकाउंट के हर सैंडबॉक्स की लिस्ट बनाता है (प्रोडक्शन में हज़ारों, लगभग पाँच सेकंड), साथ ही हर चलते या रुके हुए प्रोजेक्ट की भी। दोनों रेप्लिका इसे हर दो मिनट में चलाती थीं।
इसके राइट्स idempotent हैं, यानी उन्हें दो बार चलाने से वही नतीजा मिलता है जो एक बार चलाने से, इसलिए कुछ टूटा नहीं था। बस काम दोगुना हो रहा था। अब हर दो मिनट की विंडो को उसी तरह Redis में क्लेम किया जाता है जैसे हमारे usage मीटर अपनी विंडो क्लेम करते हैं, और केवल वही रेप्लिका पास चलाती है जो उसे क्लेम करती है। अगर Redis तक पहुँच न हो, तो पास फिर भी चलता है: काम दो बार करना नुकसानदेह नहीं है, काम न करना है।
हमने इसे कैसे टेस्ट किया
टेस्ट असली सेटअप को मॉडल करते हैं। वे alert मॉड्यूल को दो बार लोड करते हैं, जैसे दो अलग "रेप्लिका", दोनों को एक साझा स्टोर की ओर इशारा करते हैं, हर एक से एक ही अलर्ट उठाते हैं, फिर "डिप्लॉय के बाद" का स्थान लेने के लिए एक तीसरी ताज़ा कॉपी लोड करके उसे फिर से उठाते हैं। ठीक एक अलर्ट भेजा जाता है। दूसरे टेस्ट जाँचते हैं कि किसी दूसरे बिल्डर का अलर्ट अब भी जाता है, कि P0 दोनों रेप्लिका से भेजा जाता है, और कि जब स्टोर बंद हो या टाइमआउट से ज़्यादा लटक जाए तब भी अलर्ट भेजा जाता है। सफ़ाई के फ़िक्स का अपना टेस्ट है: 24 घंटे की key 20 मिनट बाद आए 600 दूसरे अलर्ट के बाद भी बची रहती है।
reconciliation जॉब के लिए, एक ही समय पर टिक करने वाली दो रेप्लिका, और उसी विंडो में एक तीसरा टिक, मिलकर एक ही लिस्टिंग पैदा करते हैं।
हमने इससे क्या सीखा
- इन-मेमोरी स्टेट हर प्रोसेस के लिए अलग होता है। जो कुछ सर्वरों के बीच सच रहना चाहिए, जैसे "एक बार भेजा गया" या "एक बार चला", उसे किसी ऐसी जगह रहना होगा जिसे वे साझा करते हों।
- डिप्लॉय भी रीस्टार्ट हैं। जो स्टेट केवल अगले रीस्टार्ट तक बचती है, वह एक सामान्य कार्यदिवस तक भी नहीं बचती।
- तय करें कि किस तरफ़ विफल होना है। अलर्ट के लिए, साझा स्टोर बंद होने का मतलब ज़्यादा अलर्ट होना चाहिए, कम नहीं।
- केवल इंस्टेंस नहीं, पैटर्न देखें। अलर्ट ऊँचा शोर था; दोगुनी reconciliation जॉब वही बग थी, जो चुपचाप काम की कीमत चुका रही थी।
इनमें से कुछ भी Mythex के अंदर से दिखाई नहीं देता। प्लेटफ़ॉर्म को स्थिर रखने के बारे में और जानने के लिए, हम डिप्लॉय को शून्य 502 एरर तक कैसे लाए और गायब स्वैप फ़ाइल देखें। अगर आप किसी सेवा की एक से ज़्यादा कॉपी चलाने के विचार में नए हैं, तो हमारी सर्वर क्या है गाइड बुनियादी बातें समझाती है।