प्रतिनिधि इंटरव्यू विषय

सामान्य साक्षात्कार: स्ट्रक्चर्ड कॉनक्रेन्सी (Structured Concurrency) को समझाएं और कैंसलेशन सीमाओं को डिज़ाइन करें

सामान्यकठिन
Offer.cc संपादकीय टीमप्रकाशित अपडेट किया गया

प्रश्न

स्ट्रक्चर्ड कॉनक्रेन्सी को समझाएं और एक ऐसी सर्विस के लिए टास्क लाइफटाइम डिज़ाइन करें जो समानांतर में यूज़र प्रोफ़ाइल, ऑर्डर और सिफारिशें (recommendations) फ़ेच करती है। उत्तर को किसी एक भाषा से बांधे बिना पेरेंट-चाइल्ड संबंध, कैंसलेशन प्रोपेगेशन, आंशिक विफलता, डेडलाइन, रिसोर्स क्लीनअप और ऑब्ज़र्वेबिलिटी को कवर करें।

प्रॉम्प्ट और संदर्भ

साक्षात्कारकर्ता आपको एक रिक्वेस्ट हैंडलर देता है जो समानांतर में तीन संबंधित ऑपरेशन्स चलाता है: यूज़र प्रोफ़ाइल फ़ेच करना, ऑर्डर फ़ेच करना और सिफारिशें जनरेट करना। क्लाइंट डिस्कनेक्ट हो जाता है, कोई महत्वपूर्ण चाइल्ड टास्क विफल हो जाता है, या समग्र डेडलाइन समाप्त हो जाती है। सिस्टम को रिक्वेस्ट समाप्त होने के बाद किसी भी अनाथ कार्य (orphan work) को चलते हुए नहीं छोड़ना चाहिए। स्ट्रक्चर्ड कॉनक्रेन्सी को समझाएं, फिर अपने उत्तर को किसी एक API पर निर्भर किए बिना टास्क ग्रुप्स, कोरूटीन स्कोप्स या स्ट्रक्चर्ड टास्क स्कोप्स से मैप करें।

यह प्रश्न मिड-लेवल और सीनियर बैकएंड, प्लेटफ़ॉर्म, मोबाइल और क्रॉस-लैंग्वेज इंफ्रास्ट्रक्चर भूमिकाओं के लिए उपयुक्त है। इसका मुख्य उद्देश्य लाइफसाइकिल रीजनिंग का मूल्यांकन करना है: एंट्री, एग्जिट, विफलता नीति और रिसोर्स ओनरशिप।

साक्षात्कारकर्ता क्या जांच रहा है

साक्षात्कारकर्ता यह जानना चाहता है कि क्या आप कॉनकरेंट कार्य को किसी ग्लोबल पूल में सबमिट करके रिटर्न करने के बजाय एक पेरेंट टास्क के हिस्से के रूप में देखते हैं या नहीं। एक मजबूत उत्तर यह स्पष्ट करता है कि चाइल्ड टास्क अपने स्कोप से अधिक समय तक जीवित नहीं रहते हैं, पेरेंट का कैंसलेशन नीचे की ओर प्रोपेगेट होता है, विफलताएं व्यावसायिक मूल्य के आधार पर फेल-फास्ट या आंशिक हो सकती हैं, और जॉइनिंग तथा क्लीनअप एक दृश्यमान सीमा (visible boundary) के भीतर होते हैं।

एक कमजोर उत्तर केवल इतना कहता है कि "समानांतर में async/await का उपयोग करें।" एक मजबूत उत्तर स्ट्रक्चर्ड कॉनक्रेन्सी को डिटैच्ड फ्यूचर्स (detached futures) से अलग करता है और यह समझाता है कि कार्य को एक नए ओनर के साथ एक अलग ड्यूरेबल क्यू (durable queue) में कब ले जाना चाहिए।

पहले स्पष्ट करने योग्य प्रश्न

  • क्या तीनों चाइल्ड टास्क्स का सफल होना अनिवार्य है, या प्रोफ़ाइल और ऑर्डर आवश्यक हैं जबकि सिफारिशें वैकल्पिक हैं?
  • क्या डेडलाइन एक सख्त (hard) रिक्वेस्ट डेडलाइन है, या सर्वर एक अलग सॉफ्ट बजट के तहत कार्य समाप्त कर सकता है?
  • क्या चाइल्ड केवल कैंसल करने योग्य I/O करते हैं, या वे ऐसे बाहरी साइड इफेक्ट्स उत्पन्न कर सकते हैं जिन्हें पूर्ववत (undo) नहीं किया जा सकता?
  • क्या क्लाइंट के डिस्कनेक्ट होने के बाद कार्य को किसी एसिंक्रोनस नोटिफिकेशन या बैकग्राउंड जॉब में स्थानांतरित किया जा सकता है?
  • क्या विफलता पर एक त्रुटि (error), आंशिक परिणाम, या स्पष्ट रूप से डीग्रेडेड रिस्पॉन्स वापस आना चाहिए?

ये उत्तर स्कोप की सीमा को बदल देते हैं: लघु रिक्वेस्ट कार्य पेरेंट स्कोप के अंदर रहता है; रिक्वेस्ट के बाद जारी रहने वाले कार्य के लिए स्पष्ट ओनरशिप ट्रांसफर की आवश्यकता होती है।

30-सेकंड का उत्तर

मैं रिक्वेस्ट को पेरेंट टास्क और तीनों समानांतर रीड्स को चाइल्ड टास्क मानता हूं। पेरेंट केवल तभी एग्जिट होता है जब चाइल्ड समाप्त हो जाते हैं, विफल हो जाते हैं, या कैंसल होकर साफ़ (clean up) हो जाते हैं। प्रोफ़ाइल या ऑर्डर की विफलता सिब्लिंग्स को कैंसल कर देती है और एक पुनः प्रयास करने योग्य (retryable) त्रुटि लौटाती है; सिफारिश की विफलता एक स्पष्ट डीग्रेडेड रिस्पॉन्स लौटाती है। क्लाइंट डिस्कनेक्ट और डेडलाइन कैंसलेशन को नीचे की ओर प्रोपेगेट करते हैं, और प्रत्येक चाइल्ड अपने क्लीनअप पाथ में कनेक्शन्स और हैंडल्स को बंद कर देता है। यदि कार्य को रिक्वेस्ट के बाद भी जारी रहना है, तो मैं बैकग्राउंड फ्यूचर को अलग करने के बजाय इसे एक ड्यूरेबल क्यू में लिखता हूं और एक नया लाइफसाइकिल शुरू करता हूं। मैं फॉल्ट इंजेक्शन के साथ कैंसलेशन, टाइमआउट, आंशिक विफलता और लीक मेट्रिक्स को सत्यापित करता हूं।

चरण-दर-चरण उत्तर

चरण 1: टास्क ट्री बनाएं

रिक्वेस्ट हैंडलर को रूट (root) बनाएं। प्रोफ़ाइल, ऑर्डर और सिफारिशें इसके डायरेक्ट चाइल्ड हैं। चाइल्ड टास्क पेरेंट की डेडलाइन, ट्रेसिंग संदर्भ और कैंसलेशन सिग्नल को इनहेरिट करते हैं। पेरेंट टास्क वेटिंग, एरर कंपोजिशन और स्कोप क्लोजर का मालिक होता है; कोई भी चाइल्ड टास्क किसी ग्लोबल एक्ज़ीक्यूटर को कार्य सौंपकर पेरेंट को ओनरशिप ट्रांसफर के बिना रिटर्न नहीं होने दे सकता।

मुख्य इनवेरिएंट (invariant) जांचने योग्य है: जब पेरेंट एग्जिट होता है, तो प्रत्येक चाइल्ड या तो पूरा हो चुका होता है, कैंसल हो चुका होता है, या स्पष्ट रूप से किसी अन्य रिकॉर्ड किए गए ओनर को ट्रांसफर किया जा चुका होता है। उस इनवेरिएंट के बिना, थ्रेड लीक, डुप्लिकेट राइट्स और अस्पष्ट टेल लेटेंसी छिपे रहते हैं।

चरण 2: विफलता और आंशिक-परिणाम नीति चुनें

व्यावसायिक मूल्य के आधार पर वर्गीकृत करें। चेकआउट दृश्य के लिए प्रोफ़ाइल और ऑर्डर आवश्यक हैं, इसलिए इनमें से किसी की भी विफलता सिब्लिंग्स को कैंसल कर देती है और एक पुनः प्रयास करने योग्य त्रुटि लौटाती है। सिफारिशें एक अतिरिक्त संवर्द्धन हैं, इसलिए विफलता बिना सिफारिशों के एक रिस्पॉन्स लौटाती है और डीग्रेडेशन को रिकॉर्ड करती है। कम मूल्य वाली विफलता को पूरी रिक्वेस्ट को क्रैश न करने दें, और लापता महत्वपूर्ण डेटा को खाली ऑब्जेक्ट के रूप में न छिपाएं।

स्यूडोकोड बिना किसी भाषा का चयन किए इस नीति को व्यक्त कर सकता है:

text
within request_scope(deadline):
  profile = child(fetch_profile)
  orders = child(fetch_orders)
  recommendations = child(fetch_recommendations)
  wait(profile, orders)
  if profile.failed or orders.failed:
    cancel_all_children()
    return retryable_error
  return compose(profile, orders, recommendations.or_empty)

चरण 3: सुनिश्चित करें कि कैंसलेशन रिसोर्स सीमाओं तक पहुंचे

कैंसलेशन किसी टास्क ऑब्जेक्ट पर केवल एक बूलियन फ़्लैग नहीं है। नेटवर्क क्लाइंट्स, डेटाबेस ड्राइवर्स और फ़ाइल रीड्स को इस सिग्नल का पालन करना चाहिए; वेट्स को इंटरप्ट करने योग्य होना चाहिए; पुनः प्रयास लूप्स को दूसरे प्रयास से पहले डेडलाइन की दोबारा जांच करनी चाहिए। किसी अपरिवर्तनीय साइड इफेक्ट के लिए, जैसे कि स्वीकृत भुगतान या भेजा गया ईमेल, कैंसलेशन बाद के चरणों को रोक सकता है लेकिन यह दावा नहीं कर सकता कि पूरा हो चुका साइड इफेक्ट वापस (roll back) कर दिया गया था।

जब क्लाइंट डिस्कनेक्ट होता है, तो एंट्री पॉइंट रूट को कैंसल कर देता है। रूट कैंसलेशन को प्रोपेगेट करता है, और चाइल्ड टास्क्स अपने क्लीनअप पाथ में रिस्पॉन्स बॉडीज़, कनेक्शन्स, सब्सक्रिप्शन और टेम्पररी फ़ाइलों को बंद करते हैं। क्लीनअप की भी एक समय सीमा होनी चाहिए ताकि "क्लीनअप की प्रतीक्षा" रिक्वेस्ट शटडाउन को हमेशा के लिए ब्लॉक न कर दे।

चरण 4: टाइमआउट, कैंसलेशन और विफलता को अलग करें

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

स्कोप के बाहर आंख मूंदकर पुनः प्रयास या sleep न लगाएं। वे समाप्त हो चुके बजट का उपभोग करते हैं और पेरेंट के रिटर्न होने के बाद भी लोड उत्पन्न करते रहते हैं।

चरण 5: सिद्ध करें कि संरचना सुरक्षित है

सामान्य समापन, महत्वपूर्ण चाइल्ड विफलता, वैकल्पिक चाइल्ड विफलता, पेरेंट कैंसलेशन, डेडलाइन समाप्ति और चाइल्ड शुरू होने से पहले कैंसलेशन का परीक्षण करें। प्रत्येक परीक्षण यह जांचता है कि सक्रिय चाइल्ड शून्य पर वापस आ जाएं, डाउनस्ट्रीम कनेक्शन्स बंद हो जाएं, ट्रेस में पेरेंट-चाइल्ड संबंध दिखाई दें, साइड इफेक्ट्स डुप्लिकेट न हों और एरर वर्गीकरण यूज़र को दिखने वाली स्थिति से मेल खाता हो।

सक्रिय टास्क्स, कैंसलेशन प्रोपेगेशन लेटेंसी, डेडलाइन टाइमआउट दर, चाइल्ड अपवाद, क्लीनअप अवधि, कैंसलेशन के बाद भी चल रहे टास्क्स और प्रति रिक्वेस्ट अधिकतम फ़ैन-आउट को ट्रैक करें। केवल एक सफल रिस्पॉन्स स्ट्रक्चर्ड कॉनक्रेन्सी को सिद्ध नहीं करता है।

मॉडल उच्च-गुणवत्ता वाला उत्तर

"मैं एक रिक्वेस्ट को पेरेंट टास्क मानूंगा और तीनों रीड्स को एक डेडलाइन-बाउंड स्कोप में रखूंगा। पेरेंट चाइल्ड्स को बनाता है, उनका इंतज़ार करता है और उन्हें बंद करता है; कोई भी चाइल्ड उस स्कोप से अधिक समय तक जीवित नहीं रह सकता। प्रोफ़ाइल और ऑर्डर महत्वपूर्ण निर्भरताएं हैं, इसलिए इनमें से किसी की भी विफलता सिब्लिंग्स को कैंसल करती है और एक पुनः प्रयास करने योग्य त्रुटि लौटाती है। सिफारिशें वैकल्पिक हैं; विफलता पर मैं एक स्पष्ट खाली सिफारिश स्थिति लौटाता हूं और डीग्रेडेशन रिकॉर्ड करता हूं।

क्लाइंट डिस्कनेक्ट, पेरेंट टाइमआउट, या अपस्ट्रीम कैंसलेशन टास्क ट्री में नीचे की ओर प्रोपेगेट होता है। प्रत्येक I/O कॉल एक कैंसल करने योग्य इंटरफ़ेस का उपयोग करती है, पुनः प्रयास शेष बजट की जांच करते हैं, और क्लीनअप कनेक्शन्स और सब्सक्रिप्शन को बंद करता है। मैं यह ढोंग नहीं करता कि कोई बाहरी साइड इफेक्ट होने के बाद उसे रोल बैक किया जा सकता है। यदि कार्य को रिक्वेस्ट के बाद जारी रहना है, तो मैं पहले इसे एक ड्यूरेबल क्यू में लिखता हूं और एक कंज्यूमर को एक नया टास्क ट्री बनाने देता हूं।

मैं महत्वपूर्ण विफलता, वैकल्पिक विफलता, कैंसलेशन रेस और क्लीनअप टाइमआउट को इंजेक्ट करूंगा, फिर सक्रिय टास्क्स, कैंसलेशन लेटेंसी, लीक्स और पेरेंट-चाइल्ड ट्रेसेस का निरीक्षण करूंगा। महत्वपूर्ण हिस्सा लाइफसाइकिल और ओनरशिप है, न कि कोई विशिष्ट Java, Kotlin या Swift API।"

सामान्य विफलता मोड

  • स्ट्रक्चर्ड कॉनक्रेन्सी को समानांतर निष्पादन के बराबर मानना → आप केवल टास्क्स को एक साथ शुरू करने का वर्णन करते हैं और लाइफटाइम छोड़ देते हैं → स्कोप, जॉइन, कैंसलेशन और क्लीनअप सीमाओं को दिखाएं।
  • ग्लोबल एक्ज़ीक्यूटर में सबमिट करना और रिटर्न करना → पेरेंट के चले जाने के बाद कार्य रिक्वेस्ट के संसाधनों तक पहुंच सकता है → इसे रिक्वेस्ट स्कोप में रखें या इसे स्पष्ट रूप से ड्यूरेबल क्यू में ट्रांसफर करें।
  • कैंसलेशन को जबरन बंद करने (forced kill) के रूप में मानना → बाहरी प्रभाव पहले ही पूरे हो चुके हो सकते हैं और संसाधन स्वचालित रूप से बंद नहीं हो सकते हैं → सहकारी कैंसलेशन (cooperative cancellation), अपरिवर्तनीय कार्य और क्लीनअप ओनरशिप का उल्लेख करें।
  • प्रत्येक विफलता के लिए खाली परिणाम लौटाना → महत्वपूर्ण डेटा की अनुपस्थिति कॉल करने वालों से छिप जाती है → व्यावसायिक महत्ता के आधार पर फेल-फास्ट और आंशिक-परिणाम नियम परिभाषित करें।
  • केवल सफलता का परीक्षण करना → कैंसलेशन रेस और अनाथ कार्य अदृश्य रहते हैं → डिस्कनेक्ट, डेडलाइन, सिब्लिंग विफलताएं और बार-बार कैंसलेशन इंजेक्ट करें, फिर जांचें कि सक्रिय कार्य शून्य तक पहुंचता है।

फॉलो-अप प्रश्न

फॉलो-अप 1: सिफारिशें जनरेट करने में सेकंड लगते हैं, लेकिन रिक्वेस्ट पहले ही समाप्त हो चुकी है। आप क्या करेंगे?

पहले पूछें कि क्या यूज़र को अभी भी परिणाम की आवश्यकता है। यदि यह केवल वर्तमान पृष्ठ के लिए है, तो इसे पेरेंट के साथ कैंसल कर दें। यदि व्यवसाय एसिंक्रोनस जनरेशन चाहता है, तो इनपुट और एक आइडम्पोटेंसी की (idempotency key) को सुरक्षित रखें (persist), फिर एक कंज्यूमर को नया स्कोप बनाने दें। वह कंज्यूमर एक नई डेडलाइन, पुनः प्रयास नीति और अलर्टिंग का मालिक होता है; यह समाप्त हो चुके रिक्वेस्ट संदर्भ को उधार नहीं ले सकता।

फॉलो-अप 2: एक चाइल्ड के विफल होने के बाद अन्य चाइल्ड्स को पूरा क्यों नहीं होने दिया जाता?

आप ऐसा कर सकते हैं, यदि शेष कार्य का कोई मूल्य है और वह बजट में फिट बैठता है। महत्वपूर्ण प्रोफ़ाइल डेटा विफल होने के बाद ऑर्डर लुकअप जारी रखना आमतौर पर कनेक्शन्स और डाउनस्ट्रीम क्षमता को बर्बाद करता है; एक स्वतंत्र कैश रीफ्रेश या ऑडिट रिकॉर्ड पूरा करना उचित हो सकता है। फेल-फास्ट को सार्वभौमिक मानने के बजाय कैंसलेशन की स्थिति स्पष्ट रूप से बताएं।

फॉलो-अप 3: कैंसलेशन सिग्नल भेजा गया था, लेकिन डेटाबेस क्वेरी अभी भी चल रही है। आप इसे कैसे संभालते हैं?

जांचें कि क्या ड्राइवर कैंसलेशन का समर्थन करता है और कनेक्शन लौटाता है। यदि ऐसा नहीं है, तो एक स्वतंत्र क्वेरी टाइमआउट लागू करें, पूल को अलग करें, और देर से आने वाले परिणाम का उपयोग करने से सुरक्षा करें। कैंसलेशन से रिसोर्स रिलीज तक के समय को मापें; सीमा पार करने पर डीग्रेडेशन, आइसोलेशन या अलर्ट ट्रिगर होना चाहिए। एक गैर-कैंसल करने योग्य क्वेरी को रिक्वेस्ट-स्तरीय क्षमता को अनिश्चित काल तक रोककर नहीं रखना चाहिए।

फॉलो-अप 4: क्या स्ट्रक्चर्ड कॉनक्रेन्सी हर थ्रेड पूल और मैसेज क्यू को बदल देती है?

नहीं। यह स्पष्ट पेरेंट-चाइल्ड संबंध और साझा कैंसलेशन तथा क्लीनअप वाले अल्पकालिक कार्यों के लिए उपयुक्त है। क्रॉस-रिक्वेस्ट कार्य, शेड्यूल्ड कार्य और ड्यूरेबल पुनः प्रयासों के लिए अभी भी एक क्यू या वर्कफ़्लो की आवश्यकता होती है। एक साझा पूल निष्पादन संसाधन प्रदान कर सकता है, लेकिन सबमिट करने वाले स्कोप को यह परिभाषित करना होगा कि कौन इंतज़ार करता है, कौन कैंसल करता है और परिणाम का मालिक कौन है।

सार्वजनिक स्रोत

संबंधित प्रश्न