
जिस दिन हमारे ऐप ने खुद पर ही DDoS कर दिया
लंबित अपलोड का एक ढेर, जो स्टार्टअप पर एक साथ छोड़ दिया गया, हर GeekBye क्लाइंट को हमारे अपने सर्वरों पर एक छोटे से डिनायल-ऑफ-सर्विस हमले में बदल गया। वह फिक्स — और जो connection-liveness सीढ़ी उसने हमसे बनवाई — v2 ने हमें जो सबसे उपयोगी चीज़ें सिखाईं, उनमें से एक है।
हर डिस्ट्रिब्यूटेड-सिस्टम्स इंजीनियर आखिरकार भीड़-हमले (thundering herd) से मिलता ही है: क्लाइंट्स का एक हुजूम एक ही पल में एक ही काम करता है, और उनके पीछे का साझा संसाधन ढह जाता है। आमतौर पर यह किसी और के क्लाइंट होते हैं। GeekBye v2.0.1 में यह हमारे थे, और हमलावर हमारा अपना ऐप था।
एक notetaker खुद पर कैसे हमला करता है
GeekBye रिकॉर्डिंग को आपके Google Drive पर अपलोड कर सकता है। अगर कोई रिकॉर्डिंग तुरंत अपलोड न हो पाए — आप ऑफलाइन थे, ऐप बंद हो गया, Drive ने अटकल दी — तो वह बाद में रीट्राई करने के लिए एक बैकलॉग में चली जाती है। समझदारी की बात है।
गड़बड़ इसमें थी कि वह "बाद में" कब होता है। अगली बार लॉन्च पर, ऐप पूरे बैकलॉग को एक साथ निकालने की कोशिश करता। एक हफ्ते की लंबित रिकॉर्डिंग वाला एक यूज़र, ऐप खोलते ही, एक साथ भेजे गए अपलोड रिक्वेस्ट के एक विस्फोट में बदल जाता। इसे सुबह लॉन्च करने वाले हर यूज़र से गुणा कीजिए, और हमारे बैकएंड से उन्हीं चंद सेकंडों में authenticate करने, rate जांचने, और ट्रैफिक की एक दीवार को प्रोसेस करने को कहा जा रहा था — उन क्लाइंट्स से जो, तकनीकी तौर पर, सब के सब सलीके से बर्ताव कर रहे थे।
हमारे बैकएंड ने सही काम किया और उस बाढ़ को rate-limit कर दिया। और वहीं से दूसरे दर्जे का नुकसान शुरू हुआ। एक rate-limit रिस्पॉन्स एक HTTP 429 है, और 429, अगर आप सावधान न हों, बहुत हद तक दूसरे फेल्योर्स जैसा दिखता है:
- स्टार्टअप पर होने वाले प्रोफाइल फ़ेच को एक
429मिला और ऐप ने इसे auth फेल की तरह लिया — यूज़र्स को एक बिलकुल वैध सेशन से पल भर के लिए लॉग आउट कर दिया। - ट्रांसक्रिप्शन कनेक्शन को एक सामान्य
429मिला और उसने एक ऑडियो-लिमिट-पूरी-हो-गई अपग्रेड प्रॉम्प्ट दिखाया — पैसे देने वाले यूज़र्स को बताया कि वे एक ऐसे quota से टकरा गए हैं जिससे वे टकराए ही नहीं थे।
तो एक मूल कारण — एक बिना-लय वाला बैकलॉग — ने तीन दिखने वाले लक्षण पैदा किए: सर्वर पर दबाव, झूठे लॉगआउट, और झूठे अपसेल। खुद-पर-DoS का यही क्लासिक रूप है: लोड बुरा है, पर यूज़र्स असल में जो महसूस करते हैं, वह उस लोड के लक्षणों की गलत व्याख्या है।
फिक्स, दो परतों में
भगदड़ रोको। अपलोड बैकलॉग अब लय में है — रिक्वेस्ट backoff के साथ फैलती हैं, और किसी भी 429 के बाद एक cooldown, ताकि क्लाइंट एक दबे हुए सर्वर पर और ज़ोर से टेक लगाने के बजाय उससे पीछे हट जाए। एक भीड़-हमला (thundering herd) एक सलीकेदार कतार बन जाता है।
गलत व्याख्या रोको। स्टार्टअप के दौरान एक क्षणिक 429 या 5xx अब आपको लॉग आउट नहीं करता — क्लाइंट "सर्वर बस पल भर के लिए व्यस्त है" को "आपका सेशन अमान्य है" से अलग करता है। और ट्रांसक्रिप्शन रास्ते पर एक सामान्य rate-limit 429 अब एक ऑडियो-quota एरर का भेस नहीं धरता; सिर्फ एक सच्चा quota रिस्पॉन्स ही अपग्रेड प्रॉम्प्ट दिखाता है। जो सबक टिका रहा: एक error code, एक error का मतलब नहीं है। 429 का मतलब है "धीमे हो जाओ", न कि "आप अनधिकृत हैं" और न ही "आपका quota खत्म है" — और क्लाइंट को यह फर्क पता होना चाहिए।
जो liveness सीढ़ी उसके बाद बनी
भीड़ को लय में लाने से एक ज़्यादा खामोश समस्या उजागर हुई: जब एक कनेक्शन लोड के नीचे सचमुच खराब हो गया, तो हमें कितनी जल्दी पता चला? हम जितनी चाहते थे, उससे धीरे। तो v2.0.4 ने रियल-टाइम ट्रांसक्रिप्शन के लिए एक connection-liveness सीढ़ी बनाई:
- Heartbeat — ट्रांसक्रिप्शन सॉकेट को एक तय अंतराल पर ping किया जाता है, ताकि एक चुपचाप मरा कनेक्शन अगले ऑडियो चंक के फेल होने का इंतज़ार करने के बजाय कुछ सेकंडों में पकड़ा जाए।
- नेटवर्क वापस आने पर एक reconnect किक — जब OS बताता है कि नेटवर्क लौट आया, तो ऐप उस खोज पर ठोकर खाने का इंतज़ार करने के बजाय सक्रिय रूप से कनेक्शन फिर से बनाता है।
- Control ping/pong liveness — एक एप्लिकेशन-स्तर का आना-जाना जो सिर्फ "सॉकेट खुला है" नहीं, बल्कि "दूसरा सिरा सचमुच जवाब दे रहा है" की पुष्टि करता है।
- एक service के मालिकाने वाला reconnect गेट — एक ही जगह तय करती है कि reconnect करना है या नहीं, बजाय इसके कि ऐप के कई हिस्से इसे करने के लिए दौड़ें और एक-दूसरे को कुचलें।
इनमें से कुछ भी चमकदार नहीं है। यह सब मिलकर ही वह वजह है कि एक v2 सेशन ऐसा महसूस होता है जैसे वह बस… जुड़ा रहता है।
यह इस हफ्ते फिर हुआ — एक स्तर नीचे
यही वह हिस्सा है जो इसे महज़ एक जंग-की-कहानी से ज़्यादा बनाता है। यही फेल्योर क्लास कुछ दिन पहले फिर उभरी, हमसे एक स्तर नीचे। एक अकेले क्लाइंट ने 400 मिलीसेकंड से कम में 21 ट्रांसक्रिप्शन-सेशन स्टार्ट दागे — और हमारे स्पीच प्रोवाइडर की अकाउंट-भर की 15 समवर्ती रिक्वेस्ट की लिमिट से टकरा गया। साझा इन्फ्रास्ट्रक्चर पर एक भगदड़, बिलकुल उस अपलोड बैकलॉग की तरह, बस अपने बैकएंड के बजाय एक dependency पर तानी हुई।
आकार एक जैसा है, और फिक्स भी: भगदड़ मचाते क्लाइंट को एक single-flight गार्ड चाहिए ताकि वह एक इरादे के लिए बीस स्टार्ट न दाग सके, और साझा संसाधन को एक प्रति-यूज़र सीमा चाहिए ताकि एक क्लाइंट पूरा पूल न खा जाए। हमने इसका क्लाइंट-साइड वर्ज़न पहले बनाया है — वह अपलोड pacer ही यह पैटर्न है। अब हम इसे सेशन स्टार्ट पर लगाते हैं। भीड़-हमला (thundering herd) एक ऐसा bug नहीं जिसे आप एक बार ठीक करते हैं; यह एक आकार है जिसे आप हर उस जगह पहचानना सीखते हैं जहां क्लाइंट एक साझा सीमा से मिलते हैं।
तीन बातें साथ ले जाने के लिए
- आपके अपने क्लाइंट एक ऐसा लोड टेस्ट हैं जो आपने शेड्यूल नहीं किया। जो भी चीज़ स्टार्टअप-पर-बैच करती है, लॉन्च-पर-रीट्राई करती है, या जागने-पर-रीकनेक्ट करती है, वह पर्याप्त यूज़र्स का इंतज़ार करता एक भीड़-हमला (thundering herd) है। यूज़र्स होने से पहले उसे लय में लाइए।
- कोड को मतलब से अलग करो।
429अस्तित्व में सबसे ज़्यादा गलत पढ़ा जाने वाला स्टेटस है। "धीमे हो जाओ" न तो "लॉग आउट" है और न "हमें पैसे दो"। हर फेल्योर को उसी तक रूट करो जो वह असल में मतलब रखता है। - liveness एक सीढ़ी है, एक flag नहीं। "क्या कनेक्शन ज़िंदा है?" के अलग-अलग परतों पर कई ईमानदार जवाब हैं — सॉकेट खुला है, बाइट बह रहे हैं, दूसरा सिरा जवाब दे रहा है, नेटवर्क मौजूद है। एक मज़बूत ऐप एक से ज़्यादा जांचता है।
यही GeekBye v2 की शांति के नीचे की बे-चमक मशीनरी है। जिन विश्वसनीयता फ़ीचर्स को इसने मुमकिन बनाया, उनके लिए देखें आपका AI notetaker खराब Wi-Fi पर क्यों रुक जाता है और जब फ़ायरवॉल WebSocket को ब्लॉक करे तब लाइव ट्रांसक्रिप्शन (v2.0.8)। इस सीरीज़ की पड़ोसी रिलीज़ों के लिए देखें आपका AI notetaker मीटिंग के बीच में रिकॉर्डिंग क्यों बंद कर देता है (v2.0.9)।