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

Java कंकरेंसी इंटरव्यू: वर्चुअल थ्रेड्स कब मदद करते हैं, और कब विफल होते हैं?

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

प्रश्न

Java 21 पर चलने वाली एक सर्विस प्रति रिक्वेस्ट तीन डाउनस्ट्रीम API कॉल्स करती है, और एक ट्रेडिशनल थ्रेड पूल हाई कंकरेंसी के दौरान भारी कतारें (queueing) बनाता है। टीम हर पूल को वर्चुअल थ्रेड्स से बदलना चाहती है। बताएं कि वर्चुअल थ्रेड्स क्या हल करते हैं, क्या हल नहीं करते हैं, और आप डेटाबेस कनेक्शन्स को कैसे सीमित करेंगे और पिनिंग का पता कैसे लगाएंगे।

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

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

इंटरव्यूअर क्या आकलन करता है

  • क्या आप यह स्पष्ट करते हैं कि वर्चुअल थ्रेड्स स्केलेबल कंकरेंसी और थ्रूपुट में सुधार करते हैं, न कि किसी सिंगल-टास्क के निष्पादन की गति (execution speed) में।
  • क्या आप synchronized या नेटिव कॉल्स से होने वाली पिनिंग और CPU-बाउंड कार्यों द्वारा लगाई गई सीमाओं की पहचान करते हैं।
  • क्या आप प्रति टास्क एक वर्चुअल थ्रेड के साथ फैन-आउट को लागू करते हैं और सीमित डाउनस्ट्रीम रिसोर्सेस को सेमाफोर या पूल द्वारा नियंत्रित करते हैं।
  • क्या आप JFR, थ्रेड डंप्स, लेटेंसी, कैरियर यूटिलाइज़ेशन और डाउनस्ट्रीम वेट टाइम का उपयोग करके सत्यापन (validation) की रूपरेखा तैयार करते हैं।

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

पूछें कि क्या रिक्वेस्ट्स का अधिकांश समय नेटवर्क, डेटाबेस या CPU कार्य पर इंतज़ार करने में बीतता है; क्या तीनों कॉल्स समानांतर (parallel) में चल सकती हैं; और डाउनस्ट्रीम सर्विसेज़ क्या कंकरेंसी और कनेक्शन सीमाएं लागू करती हैं। यह भी सुनिश्चित करें कि क्या फ्रेमवर्क और ड्राइवर्स ब्लॉकिंग APIs का समर्थन करते हैं, क्या लंबे synchronized या नेटिव सेक्शन्स मौजूद हैं, क्या लक्ष्य कम p99 है या उच्च थ्रूपुट, और क्या कैनरी के दौरान पुराना एक्ज़ीक्यूटर बना रह सकता है।

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

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

चरण-दर-चरण समाधान

1. इंतज़ार के दौरान कंकरेंसी को मुख्य लाभ के रूप में परिभाषित करें

एक प्लेटफॉर्म थ्रेड I/O के इंतज़ार के दौरान OS थ्रेड से बंधा रहता है; जबकि एक वर्चुअल थ्रेड ब्लॉकिंग I/O के दौरान निलंबित (suspend) हो सकता है और उसका कैरियर थ्रेड किसी अन्य वर्चुअल थ्रेड को निष्पादित कर सकता है। यह उन सर्विसेज़ के लिए बहुत उपयुक्त है जिनकी रिक्वेस्ट्स का अधिकांश समय नेटवर्क या डेटाबेस पर इंतज़ार करने में बीतता है। CPU-बाउंड कोड अभी भी कोर और शेड्यूलर सीमाओं के अधीन रहता है; वर्चुअल थ्रेड्स एल्गोरिदम की जटिलता या प्रति-रिक्वेस्ट CPU समय को कम नहीं करते हैं।

2. प्रति टास्क एक वर्चुअल थ्रेड बनाएं, वर्चुअल-थ्रेड पूल नहीं

वर्चुअल थ्रेड्स टास्क के हल्के (cheap) रिप्रेजेंटेशन हैं। सबमिट किए गए प्रत्येक टास्क के लिए एक नया थ्रेड बनाने के लिए Executors.newVirtualThreadPerTaskExecutor() का उपयोग करें। एक फिक्स्ड वर्चुअल-थ्रेड पूल दुर्लभ कैरियर्स का पुन: उपयोग किए बिना फिर से कतारें (queueing) बनाने लगता है; वर्चुअल थ्रेड्स को पूल करने के बजाय बाहरी रिसोर्सेस को सीमित करें।

java
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  Future<Profile> profile = executor.submit(() -> profileClient.fetch(id));
  Future<Orders> orders = executor.submit(() -> orderClient.fetch(id));
  Future<Quota> quota = executor.submit(() -> quotaClient.fetch(id));
  return merge(profile.get(), orders.get(), quota.get());
}

3. एक समर्पित सिग्नल के साथ डाउनस्ट्रीम कंकरेंसी को सीमित करें

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

4. पिनिंग और अनमाउंट न होने वाले काम की पहचान करें

synchronized या नेटिव/फॉरेन कॉल के अंदर ब्लॉक होने के दौरान एक वर्चुअल थ्रेड अपने कैरियर पर माउंटेड रह सकता है (पिन हो सकता है)। एक छोटा इन-मेमोरी लॉक आमतौर पर ठीक होता है; लेकिन एक लंबा I/O लॉक कैरियर को रोके रखता है और थ्रूपुट को कम करता है। हर synchronized ब्लॉक को आंख मूंदकर बदलने के बजाय, JFR पिंड इवेंट्स या डायग्नोस्टिक्स के आधार पर हॉट पाथ में ब्लॉकिंग काम के चारों ओर लगे मॉनिटर को उपयुक्त ReentrantLock से बदलें।

5. कैंसिलेशन, डेडलाइन्स और एरर प्रोपेगेशन को संभालें

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

6. मेट्रिक्स और कंट्रोल ग्रुप के साथ परिणाम सिद्ध करें

समान ट्रैफ़िक पर थ्रूपुट, p50/p99, CPU, कैरियर पैरेललिज़्म, वर्चुअल-थ्रेड काउंट, कनेक्शन-पूल वेट, सेमाफोर वेट और एरर्स की तुलना करें। JFR के साथ jdk.VirtualThreadPinned और स्टार्ट/एंड इवेंट्स रिकॉर्ड करें; jcmd थ्रेड डंप्स के साथ स्टैक्स का निरीक्षण करें। अलग-अलग I/O-वेट, CPU-बाउंड, डाउनस्ट्रीम-रेट-लिमिटेड और लॉक-कंटेंशन वर्कलोड्स चलाएं। I/O मामलों में सुधार होना लेकिन CPU मामलों में न होना ही अपेक्षित परिणाम है।

उच्च-गुणवत्ता वाला नमूना उत्तर

मैं वर्चुअल थ्रेड्स को केवल तेज़ थ्रेड पूल के रूप में नहीं देखूंगा। वे उन सर्विसेज़ के लिए उपयुक्त हैं जिनकी रिक्वेस्ट्स का अधिकांश समय ब्लॉकिंग I/O में बीतता है क्योंकि वर्चुअल थ्रेड सस्पेंड होकर अपने कैरियर को मुक्त कर सकता है; CPU-बाउंड काम अभी भी कोर सीमाओं के अधीन रहता है। तीन पैरेलल डाउनस्ट्रीम कॉल्स के लिए, मैं एक रिक्वेस्ट डेडलाइन और कैंसिलेशन के साथ प्रति-टास्क वर्चुअल-थ्रेड एक्ज़ीक्यूटर का उपयोग करूंगा; कनेक्शन पूल्स या सेमाफोर डेटाबेस और प्रोवाइडर सीमाओं को लागू करेंगे। मैं ड्राइवर्स, लॉक्स और नेटिव कॉल्स की जांच करूंगा ताकि लंबा I/O ब्लॉक synchronized के अंदर न रहे और कैरियर्स को पिन न करे। माइग्रेशन को बड़े पैमाने पर लागू करने से पहले एक कैनरी थ्रूपुट, p99, कैरियर यूटिलाइज़ेशन, डाउनस्ट्रीम वेट, JFR पिंड इवेंट्स और थ्रेड डंप्स की तुलना करेगी।

सामान्य गलतियाँ

  • यह कहना कि वर्चुअल थ्रेड्स CPU कोड को तेज़ बनाते हैं → वे मुख्य रूप से इंतज़ार करने वाले काम के लिए कंकरेंसी में सुधार करते हैं और कोई नए CPU कोर नहीं जोड़ते → थ्रूपुट, लेटेंसी और CPU काम को अलग-अलग मापें।
  • एक फिक्स्ड वर्चुअल-थ्रेड पूल बनाना → यह थ्रेड पूलिंग को रिसोर्स लिमिटिंग के साथ भ्रमित करता है → प्रति टास्क एक वर्चुअल थ्रेड बनाएं और डाउनस्ट्रीम में सेमाफोर या पूल का उपयोग करें।
  • प्रत्येक synchronized ब्लॉक को बदलना → छोटे मेमोरी क्रिटिकल सेक्शन्स अपने आप में हानिकारक नहीं होते, और बिना सोचे-समझे बदलाव जटिलता बढ़ाता है → JFR साक्ष्यों के आधार पर ही ब्लॉकिंग लॉक पाथ्स को बदलें।
  • डाउनस्ट्रीम कनेक्शन सीमाओं को नज़रअंदाज़ करना → अधिक वर्चुअल थ्रेड्स नए कनेक्शन्स या कोटा नहीं बनाते → बजट, डेडलाइन्स और फॉलबैक व्यवहार को परिभाषित करें।
  • केवल एक हाई-कंकरेंसी लोड टेस्ट चलाना → पिनिंग, कैंसिलेशन लीक्स या CPU सैचुरेशन छिपे रह सकते हैं → I/O, CPU, लॉक्स, लिमिट्स और रिकवरी का अलग-अलग परीक्षण करें।

फ़ॉलो-अप और उत्तर

वर्चुअल थ्रेड्स और रिएक्टिव प्रोग्रामिंग के बीच क्या समझौता (trade-off) है?

वर्चुअल थ्रेड्स आज्ञासूचक (imperative) ब्लॉकिंग कोड और परिचित डायग्नोस्टिक्स को बनाए रखते हैं, जो I/O-भारी सर्विसेज़ के लिए उपयुक्त है जिन्हें पढ़ने योग्य स्टैक्स की आवश्यकता होती है। रिएक्टिव कोड इवेंट-स्ट्रीम कंपोज़िशन, अत्यधिक उच्च कनेक्शन संख्या या एक स्थापित नॉन-ब्लॉकिंग इकोसिस्टम के लिए उपयुक्त हो सकता है। यह मान लेने के बजाय कि जो नया है वह तेज़ होगा, ड्राइवर सपोर्ट, मेंटेनेंस लागत, लेटेंसी लक्ष्यों और ऑब्जर्वेबिलिटी के आधार पर चुनाव करें।

आप एक प्रोवाइडर को दस समवर्ती (concurrent) कॉल्स तक कैसे सीमित करते हैं?

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

कैरियर भुखमरी (carrier starvation) अभी भी क्यों हो सकती है?

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

क्या वर्चुअल थ्रेड्स डेटाबेस कनेक्शन पूल की जगह ले सकते हैं?

नहीं। वर्चुअल थ्रेड्स टास्क को ले जाते हैं; डेटाबेस कनेक्शन्स सीमित बाहरी रिसोर्स हैं। एक पूल, डेडलाइन्स, ट्रांज़ैक्शन सीमाएं और पूल-वेट मेट्रिक्स बनाए रखें। असीमित वर्चुअल थ्रेड्स को कनेक्शन्स के लिए इंतज़ार करने देने से दबाव केवल मेमोरी और रिक्वेस्ट डेडलाइन्स पर शिफ्ट हो जाता है।

आप कैसे साबित करते हैं कि माइग्रेशन ने लेटेंसी को खराब नहीं किया?

इनपुट्स, डाउनस्ट्रीम रिस्पॉन्स डिस्ट्रीब्यूशन और एरर रेट्स को स्थिर रखें। पुराने और वर्चुअल-थ्रेड कैनरीज़ की तुलना p50/p99, थ्रूपुट, CPU, कैरियर यूटिलाइज़ेशन, पूल वेट, कैंसिलेशन कंप्लीशन और पिंड इवेंट्स पर करें। ट्रैफ़िक बढ़ाने से पहले रोलबैक सीमाओं के साथ स्थिर, बर्स्टी, धीमे-डाउनस्ट्रीम, पूल-एग्जॉशन और रीस्टार्ट मामलों को शामिल करें।

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

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

संबंधित इंटरव्यू टूल

कोडिंग प्रॉम्प्ट के लिए स्क्रीनशॉट का उपयोग करें

समस्या को कैप्चर करें, फिर क्रम से प्रतिबंधों (constraints), समाधान, कोड, एज केस और जटिलता पर काम करें।

टूल देखें