प्रॉम्प्ट और संदर्भ
एक समवर्ती-सुरक्षित एसिंक हेल्पर, coalesce(key, task) लागू करें। किसी दिए गए key के लिए एक समय में अधिकतम एक task चल सकता है। समान की वाले समवर्ती कॉलर्स को बिल्कुल समान मान या एरर की प्रतीक्षा (await) और प्राप्ति करनी चाहिए; अलग-अलग कीज़ को स्वतंत्र रूप से चलना चाहिए।
टास्क सिंक्रोनस रूप से थ्रो कर सकता है या एसिंक्रोनस रूप से रिजेक्ट हो सकता है। कॉलर अपना स्वयं का वेटिंग टाइमआउट सेट कर सकते हैं। सफलता और विफलता दोनों के बाद प्रविष्टि (entry) को हटा दिया जाना चाहिए ताकि बाद की कॉल पुनः प्रयास (retry) कर सके। कैंसलेशन सेमेंटिक्स, एरर प्रोपेगेशन और परीक्षणों की व्याख्या करें।
इंटरव्यूअर क्या टेस्ट कर रहा है
मुख्य बात डिडुप्लीकेशन को एक सिद्ध करने योग्य समवर्ती अपरिवर्तनीय (concurrency invariant) में बदलना है: एसिंक कार्य शुरू करने से पहले शेयर्ड प्रॉमिस इंस्टॉल करें, केवल तभी डिलीट करें जब मैप अभी भी उसी प्रविष्टि की ओर इशारा कर रहा हो, और कीज़ को स्वतंत्र रखें। इंटरव्यूअर यह भी देखना चाहता है कि क्या आप किसी कॉलर द्वारा अपनी प्रतीक्षा छोड़ने और शेयर्ड कार्य को रद्द करने के बीच अंतर कर सकते हैं, तथा असीमित मेमोरी वृद्धि को संभाल सकते हैं।
स्पष्टीकरण के लिए प्रश्न
- क्या कीज़ गैर-खाली (non-empty) या सामान्यीकृत (normalized) होनी चाहिए? मैं एक खाली की को अस्वीकार करूँगा ताकि असंबंधित अनुरोध गलती से आपस में न जुड़ें।
- क्या कॉलर टाइमआउट को अपस्ट्रीम कार्य रद्द करना चाहिए? डिफ़ॉल्ट रूप से यह केवल उस कॉलर की प्रतीक्षा को रोकता है और अन्य प्रतीक्षा करने वालों के लिए शेयर्ड कार्य को चालू रखता है।
- क्या एरर्स को कैश किया जाना चाहिए? नहीं। सेटलमेंट के बाद डिलीट करें ताकि अगली कॉल पुनः प्रयास कर सके।
- क्या क्रॉस-प्रोसेस कोएलेसिंग आवश्यक है? नहीं; यह प्रॉम्प्ट एकल-प्रक्रिया (single-process) मेमोरी के लिए है। क्रॉस-प्रोसेस समन्वय एक अलग डिज़ाइन है।
30-सेकंड का उत्तर
मैं इन-फ़्लाइट कार्य को एक Map<key, Entry> में रखता हूँ। प्रवेश पर, हिट होने पर मौजूदा प्रॉमिस लौटाएँ। मिस होने पर, प्रॉमिस बनाएँ, await करने से पहले इसे मैप में डालें, और फिर टास्क चलाएँ। finally में, केवल तभी डिलीट करें यदि मैप में अभी भी वही प्रविष्टि शामिल है। समान-की वाले कॉलर एक निष्पादन साझा करते हैं, विभिन्न कीज़ एक-दूसरे को ब्लॉक नहीं करती हैं, और विफलताएं पुनः प्रयास के लिए स्थिति को मुक्त करती हैं। एक कॉलर टाइमआउट शेयर्ड कार्य को रद्द किए बिना अपनी प्रतीक्षा के साथ रेस करता है। परीक्षण डुप्लिकेट कॉल, स्वतंत्र कीज़, सिंक्रोनस थ्रो, रिजेक्शन और पुनः प्रयास, और क्लीनअप रेस को कवर करते हैं।
चरण-दर-चरण गहन विश्लेषण
एक Entry परिभाषित करें जो शेयर्ड प्रॉमिस और यदि आवश्यक हो, तो एक आंतरिक नियंत्रक (controller) रख सके। निष्पादन का क्रम महत्वपूर्ण भाग है:
const inFlight = new Map<string, Promise<unknown>>();
function coalesce<T>(key: string, task: () => Promise<T>): Promise<T> {
if (!key) return Promise.reject(new Error("key must not be empty"));
const existing = inFlight.get(key);
if (existing) return existing as Promise<T>;
let shared: Promise<T>;
try {
shared = Promise.resolve().then(task);
} catch (error) {
shared = Promise.reject(error);
}
inFlight.set(key, shared);
shared.finally(() => {
if (inFlight.get(key) === shared) inFlight.delete(key);
}).catch(() => undefined);
return shared;
}एक ऑब्जेक्ट एंट्री अतिरिक्त रूप से प्रारंभ समय, वेटर काउंट और एक AbortController रिकॉर्ड कर सकती है। Promise.resolve().then(task) सिंक्रोनस थ्रो और एसिंक रिजेक्शन को एक ही पथ का अनुसरण कराता है। मैप इंसर्शन पहले await से पहले होना चाहिए; अन्यथा इवेंट-लूप के दो टर्न दोनों एक मिस देख सकते हैं। पहचान जाँच (identity check) किसी पुराने टास्क के finally को एक नई प्रविष्टि को हटाने से रोकती है।
कॉलर टाइमआउट एक बाहरी नीति (outer policy) है:
function waitWithTimeout<T>(shared: Promise<T>, ms: number): Promise<T> {
return Promise.race([
shared,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error("wait timeout")), ms),
),
]);
}शेयर्ड टास्क अभी भी अन्य वेटर्स के लिए पूरा होता है। यदि उत्पाद के अनुसार सभी के चले जाने पर कैंसलेशन आवश्यक है, तो संदर्भ गणना (reference counting) जोड़ें और अनुबंध एवं परीक्षणों में उस रेस को स्पष्ट रूप से परिभाषित करें।
अपेक्षित मैप संचालन O(1) हैं। K अलग-अलग इन-फ़्लाइट कीज़ के साथ, स्थिति O(K) है; एक रिज़ल्ट वितरित करने की लागत वेटर्स की संख्या के समानुपाती होती है। प्रोडक्शन कोड को की कार्डिनैलिटी को सीमित करना चाहिए और अवधि, टाइमआउट, वेटर-काउंट और एरर मेट्रिक्स को प्रदर्शित करना चाहिए ताकि मैप एक असीमित कैश न बन जाए।
उच्च-गुणवत्ता वाला नमूना उत्तर
मैं पहले सीमा स्पष्ट करूँगा: एकल प्रक्रिया, केवल इन-फ़्लाइट डिडुप्लीकेशन, कोई रिज़ल्ट कैश नहीं। Map प्रत्येक प्रविष्टि को संग्रहीत करता है। मिस होने पर मैं तुरंत प्रॉमिस बनाता और पंजीकृत करता हूँ, फिर यूज़र टास्क को इनवोक करता हूँ। प्रत्येक कॉलर को समान प्रॉमिस प्राप्त होता है, इसलिए मान और एरर समान होते हैं। क्लीनअप ऑब्जेक्ट पहचान की तुलना करता है, जिससे एक पुराना समापन नई पीढ़ी को हटाने से रोकता है।
कैंसलेशन का अर्थ है "प्रतीक्षा रद्द करना, शेयर्ड कार्य नहीं": एक टाइम-आउट कॉलर अन्य वेटर्स को AbortError नहीं भेजता है या एकमात्र अपस्ट्रीम ऑपरेशन को बाधित नहीं करता है। यदि वास्तविक कैंसलेशन की आवश्यकता है, तो मैं एक शेयर्ड कंट्रोलर और वेटर रेफरेंस काउंटिंग का उपयोग करूँगा और केवल तभी रद्द करूँगा जब काउंट शून्य तक पहुँच जाए।
परीक्षणों के लिए, मैं एक साथ कॉल करने वालों को रिलीज़ करने और एक टास्क इनवोकेशन और एक शेयर्ड रिज़ल्ट सुनिश्चित करने के लिए एक बैरियर का उपयोग करता हूँ। मैं समानांतर में स्वतंत्र कीज़, सिंक्रोनस थ्रो, एसिंक्रोनस रिजेक्शन, विफलता के बाद पुनः प्रयास, सफलता के बाद एक नया टास्क, एक नई प्रविष्टि के साथ रेस करने वाले पुराने finally, और एक कॉलर के टाइम आउट होने पर दूसरे के सफल होने का भी परीक्षण करता हूँ। मैं उच्च-कार्डिनैलिटी कीज़ के लिए क्षमता सीमा और मेट्रिक्स के साथ समाप्त करूँगा।
सामान्य गलतियाँ
- मैप में डालने से पहले
task()का await करना, जो डुप्लिकेट निष्पादन की अनुमति देता है। finallyमें की द्वारा बिना शर्त डिलीट करना, जिससे पुराना टास्क नई प्रविष्टि को हटा सकता है।- एक कॉलर के
AbortSignalको सीधे शेयर्ड कार्य में पास करना और प्रत्येक वेटर को रद्द करना। - पुनः प्रयास के लिए हटाने के बजाय रिजेक्टेड प्रॉमिस को हमेशा के लिए रखना।
- एक ग्लोबल लॉक का उपयोग करना और असंबंधित कीज़ को क्रमबद्ध (serialize) करना।
- एक साथ आगमन, सिंक्रोनस थ्रो और क्लीनअप रेस के बजाय केवल अनुक्रमिक (sequential) कॉल का परीक्षण करना।
फॉलो-अप प्रश्न और उत्तर
आप वास्तविक कैंसलेशन का समर्थन कैसे करेंगे?
क्रॉस-प्रोसेस कोएलेसिंग की आवश्यकता कब होती है?
आप उच्च-कार्डिनैलिटी की लीक्स को कैसे रोकते हैं?
वास्तविक कैंसलेशन के लिए एक शेयर्ड कंट्रोलर, रेफरेंस काउंटिंग और एक स्पष्ट शून्य-वेटर नीति की आवश्यकता होती है। क्रॉस-प्रोसेस मामलों में Redis, एक गेटवे, या किसी अन्य समन्वयक के साथ-साथ लीज, लीडर विफलता प्रबंधन और डुप्लिकेट निष्पादन सहनशीलता की आवश्यकता होती है। उच्च-कार्डिनैलिटी कीज़ को क्षमता सीमा, TTL या निष्कासन (eviction) नीति, अस्वीकृति व्यवहार और मेट्रिक्स की आवश्यकता होती है; इन नियंत्रणों को उस मूल नियम को बनाए रखना चाहिए कि केवल इन-फ़्लाइट कार्य ही संग्रहीत किया जाता है।