प्रश्न और संदर्भ
यह प्रश्न यह जांचता है कि क्या एक बैकएंड इंजीनियर डीप-पेज परफॉर्मेंस, समवर्ती राइट्स (concurrent writes) के तहत स्थिर क्रम (stable ordering) और एक स्पष्ट कर्सर अनुबंध (cursor contract) को एक साथ संभाल सकता है या नहीं। मान लें कि एक क्लाइंट घटते समय क्रम (descending time order) में ऑर्डर या बदलते फ़ीड को 50 पंक्तियाँ प्रति पेज के हिसाब से ब्राउज़ करता है, जिसमें लगातार नई पंक्तियाँ आ रही हैं; उसे मुख्य रूप से किसी भी पेज पर रैंडम जंप करने के बजाय अगले पेज की आवश्यकता होती है।
यह बैकएंड, डेटा-सर्विस और प्लेटफ़ॉर्म भूमिकाओं के लिए उपयुक्त है। सीधे यह दावा न करें कि कर्सर हमेशा तेज़ होता है। सबसे पहले ऑर्डरिंग, पेज जंप, एक्सपोर्ट, कंसिस्टेंसी, विलोपन सिमेंटिक्स (deletion semantics), रेप्लिकस और इंडेक्स को स्पष्ट करें क्योंकि प्रत्येक बाधा (constraint) निर्णय को बदल देती है।
इंटरव्यूअर क्या मूल्यांकन कर रहा है
एक बेहतरीन उत्तर यह समझाता है कि डीप OFFSET पहले की पंक्तियों को स्कैन और डिस्कार्ड क्यों करता है, समवर्ती राइट्स के तहत एक गैर-यूनीक सॉर्ट की क्यों ड्रिफ्ट होती है, और keyset पेजिनेशन को एक स्थिर टोटल ऑर्डर और मैचिंग इंडेक्स की आवश्यकता क्यों होती है। यह एक स्टेटलेस keyset टोकन को एक ट्रांजेक्शन बनाए रखने वाले डेटाबेस कर्सर से भी अलग करता है, और डुप्लिकेट्स, गैप्स, डिलीट की गई सीमा पंक्ति (boundary row) और जाली टोकन (forged tokens) के लिए परीक्षण प्रस्तावित करता है।
पूछे जाने वाले स्पष्टीकरण प्रश्न
- क्या उपयोगकर्ताओं को पेज N पर जंप करना है या कुल संख्या देखनी है? शुद्ध keyset पेजिनेशन इसे सीधे प्रदान नहीं करता है।
- सॉर्ट क्रम क्या है? क्या टाइमस्टैम्प यूनीक और इम्यूटेबल है, या इसे टाई-ब्रेकर के रूप में एक यूनीक ID की आवश्यकता है?
- क्या क्लाइंट एक ही ट्रैवर्सल के लिए लाइव व्यू चाहता है या स्नैपशॉट? इंसर्ट और डिलीट दोनों के तहत अलग दिखते हैं।
- क्या क्वेरी रेप्लिकस, शार्ड्स या टेनेंट फ़िल्टर तक फैली हुई है? इंडेक्स और टोकन को उन शर्तों को बाइंड करना चाहिए।
- क्या क्लाइंट कर्सर को डिकोड, संशोधित या रीप्ले कर सकते हैं? यह हस्ताक्षर (signing), समाप्ति (expiry) और संस्करण (versioning) तय करता है।
30-सेकंड का उत्तर फ़्रेमवर्क
“मैं सबसे पहले ऑर्डरिंग, पेज जंप और कंसिस्टेंसी सिमेंटिक्स को स्पष्ट करता हूँ। एक बड़ी और बदलती सूची के लिए मैं एक मैचिंग कंपोजिट इंडेक्स द्वारा समर्थित यूनीक टाई-ब्रेकर, जैसे created_at DESC, id DESC के साथ keyset पेजिनेशन का उपयोग करूँगा; अगला अनुरोध डीप OFFSET के बजाय अंतिम पंक्ति की सॉर्ट कीज़ को ले जाता है। कर्सर फ़िल्टर, सॉर्ट वर्शन और बाउंड्री को एनकोड करता है, फिर इसे साइन किया जाता है और एक एक्सपायरी दी जाती है। यदि ट्रैवर्सल को एक निश्चित स्नैपशॉट की आवश्यकता होती है, तो मैं टाइमस्टैम्प या ट्रांजैक्शनल डेटाबेस कर्सर पर विचार करूँगा और इसकी रिसोर्स लागत समझाऊँगा। मैं समवर्ती इंसर्ट, डिलीट, डुप्लिकेट टाइमस्टैम्प और छेड़छाड़ किए गए टोकन के साथ अनुबंध को मान्य करूँगा।”
चरण-दर-चरण उत्तर
चरण 1: पेजिनेशन अनुबंध को परिभाषित करें
limit कैप, डिफ़ॉल्ट क्रम, next_cursor, has_more और फ़िल्टर निर्दिष्ट करें। स्थिर ऑर्डरिंग के लिए टोटल ऑर्डर की आवश्यकता होती है; जब पंक्तियाँ एक ही टाइमस्टैम्प साझा करती हैं, तो केवल created_at द्वारा ऑर्डर करना अस्पष्ट होता है, इसलिए एक इम्यूटेबल यूनीक ID जोड़ें। यदि क्लाइंट को पिछले पेज की आवश्यकता है, तो अगले पेज की क्वेरी को केवल उलटने के बजाय रिवर्स तुलना और बाउंड्री हैंडलिंग को स्पष्ट रूप से डिज़ाइन करें।
चरण 2: OFFSET परफॉर्मेंस और ड्रिफ्ट की व्याख्या करें
OFFSET k LIMIT n को आमतौर पर डेटाबेस को पिछली k पंक्तियों को खोजने और छोड़ने (skip) की आवश्यकता होती है, इसलिए डीप-पेज का काम k के साथ बढ़ता है। यदि पहले से पढ़े गए पेज से पहले कोई इंसर्ट या डिलीट होता है, तो बाद के ऑफ़सेट स्थानांतरित हो जाते हैं और डुप्लिकेट या गैप उत्पन्न कर सकते हैं। एक छोटी, अधिकतर स्थिर एडमिन सूची जिसे पेज जंप की आवश्यकता होती है, वह OFFSET को स्वीकार कर सकती है, लेकिन इसकी गहराई सीमित करें और निष्पादन योजना (execution plan) के साथ लागत को सत्यापित करें।
चरण 3: एक स्थिर टोटल ऑर्डर पर keyset लागू करें
घटते क्रम वाले (created_at, id) के लिए, पहले पेज की कोई सीमा नहीं होती है और अगला पेज अंतिम पंक्ति की कीज़ का उपयोग करता है:
-- first page
SELECT id, title, created_at
FROM posts
ORDER BY created_at DESC, id DESC
LIMIT 50;
-- next page: boundary comes from the last returned row
SELECT id, title, created_at
FROM posts
WHERE (created_at, id) < (:last_created_at, :last_id)
ORDER BY created_at DESC, id DESC
LIMIT 50;क्वेरी एक इंडेक्स बाउंड्री से सीख (seek) करती है और हर पिछले पेज को छोड़ने के बजाय सीमित संख्या में पंक्तियों को स्कैन करती है। कंपोजिट-इंडेक्स कॉलम ऑर्डर, फ़िल्टर और दिशा क्वेरी से मेल खाने चाहिए; अन्यथा सैद्धांतिक रूप से अच्छी keyset क्वेरी भी एक बड़ी रेंज को स्कैन कर सकती है।
चरण 4: एक वेरिफ़ाइएबल कर्सर टोकन डिज़ाइन करें
आंतरिक ऑफ़सेट को कर्सर न मानें। बाउंड्री कीज़, एक फ़िल्टर डाइजेस्ट, सॉर्ट वर्शन और समाप्ति शामिल करें; टोकन पर हस्ताक्षर करें या प्रमाणित एन्क्रिप्शन का उपयोग करें ताकि क्लाइंट इसे संशोधित न कर सकें। प्राप्त होने पर, अनुरोध के विरुद्ध वर्शन, टेनेंट और फ़िल्टर को मान्य करें। यदि ऑर्डरिंग बदलती है, तो उसी टोकन को किसी भिन्न क्रम के तहत व्याख्यायित करने के बजाय पुराने वर्शन को अस्वीकार करें या स्पष्ट रूप से पुनरारंभ करें।
चरण 5: लाइव या स्नैपशॉट सिमेंटिक्स चुनें
लाइव keyset पेजिनेशन नई पंक्तियों को शीर्ष पर दिखने की अनुमति देता है; जो पंक्तियाँ पहले से ही सीमा से परे हैं वे सामान्य रूप से दोहराई नहीं जाती हैं। बाउंड्री रो को हटाने से यह दोबारा नहीं दिखती है, हालांकि कुल संख्या बदल जाती है। एक निश्चित सेट की आवश्यकता वाले निर्यात या ऑडिट के लिए, अनुबंध में एक रीड टाइमस्टैम्प, स्नैपशॉट वर्शन या डेटाबेस कर्सर शामिल करें। एक डेटाबेस कर्सर ट्रांजेक्शन और संसाधनों को रोककर रख सकता है, इसलिए यह लंबे समय तक चलने वाले HTTP पेजिनेशन के लिए स्वचालित रूप से उपयुक्त नहीं है।
चरण 6: रेप्लिकस, शार्ड्स और बाउंड्री पंक्तियों को संभालें
रेप्लिका लैग अस्थायी रूप से उस पंक्ति को छिपा सकता है जो प्राइमरी द्वारा लौटाई गई थी। रूट को पिन करें, एक पुष्ट समय सीमा ले जाएं, या अंतिम निरंतरता (eventual consistency) का दस्तावेजीकरण करें। शार्ड्स में, प्रत्येक शार्ड एक स्थानीय keyset उत्पन्न कर सकता है और सेवा कर्सर-जागरूक k-वे मर्ज कर सकती है। अंतिम बाउंड्री पंक्ति के विलोपन, समान टाइमस्टैम्प और फ़िल्टर परिवर्तनों का परीक्षण करें।
चरण 7: परफॉर्मेंस और शुद्धता सत्यापित करें
परफॉर्मेंस जांच में डीप-पेज रो स्कैन, P95 लेटेंसी और इंडेक्स उपयोग शामिल हैं। शुद्धता परीक्षण अनुरोधों के बीच पंक्तियों को इंसर्ट, डिलीट और अपडेट करते हैं और वादा किए गए डुप्लिकेट और गैप व्यवहार की पुष्टि करते हैं। प्रत्येक पेज के फ़िल्टर, सॉर्ट वर्शन, पहली और अंतिम कीज़ और टोकन वर्शन को लॉग करें ताकि प्रोडक्शन ड्रिफ्ट को एक ठोस बाउंड्री पर मैप किया जा सके।
एक उत्कृष्ट उत्तर का उदाहरण
“मैं सबसे पहले यह स्पष्ट करूँगा कि क्या उपयोगकर्ताओं को पेज जंप, कुल संख्या या एक निश्चित स्नैपशॉट की आवश्यकता है, और क्या सॉर्ट फ़ील्ड यूनीक है। लगातार लिखी जाने वाली सूची के लिए जिसे ज्यादातर आगे ब्राउज़ किया जाता है, मैं keyset पेजिनेशन चुनूँगा: टोटल ऑर्डर और कंपोजिट इंडेक्स के लिए इम्यूटेबल created_at और यूनीक id का उपयोग करें, फिर अंतिम पंक्ति की दो कीज़ को अगली रेंज बाउंड्री के रूप में उपयोग करें। डीप पेज पिछली सभी पंक्तियों को स्कैन और डिस्कार्ड नहीं करते हैं, और बाउंड्री से पहले का इंसर्ट बाद के प्रत्येक पेज को स्थानांतरित नहीं करता है।
मैं फ़िल्टर, सॉर्ट वर्शन, बाउंड्री कीज़ और एक्सपायरी को एक हस्ताक्षरित टोकन में एनकोड करूँगा, फिर अनुरोध से मेल खाने के लिए टोकन के टेनेंट और फ़िल्टर की आवश्यकता होगी। लाइव सिमेंटिक्स के तहत नई पंक्तियाँ शीर्ष पर दिखाई देती हैं; ऑडिट-स्तर के निश्चित परिणामों के लिए मैं टाइमस्टैम्प या नियंत्रित ट्रांजैक्शनल कर्सर पर विचार करूँगा और लॉन्ग-ट्रांजेक्शन लागतों की व्याख्या करूँगा। रेप्लिकस के साथ मैं रूटिंग पिन करूँगा या विजिबिलिटी बाउंड्री का दस्तावेजीकरण करूँगा।
अंत में, मैं निष्पादन योजनाओं और डीप स्कैन, डुप्लिकेट टाइमस्टैम्प, इंसर्ट, डिलीट, बाउंड्री पंक्तियों के अपडेट, रेप्लिका लैग और टोकन छेड़छाड़ को कवर करने वाले समवर्ती परीक्षणों को सत्यापित करूँगा। एक छोटी एडमिन सूची जिसे पेज जंप करना आवश्यक है, वह OFFSET रख सकती है, लेकिन मैं गहराई सीमित करूँगा और लेटेंसी की निगरानी करूँगा।”
सामान्य गलतियाँ
- यह दावा करना कि कर्सर हमेशा तेज़ होते हैं → जंप और लॉन्ग-ट्रांजेक्शन लागत की उपेक्षा करता है → बाधाओं के आधार पर OFFSET, keyset और डेटाबेस कर्सर की तुलना करें।
- केवल समय के आधार पर सॉर्ट करना → टाई होने पर अस्थिर क्रम होता है → एक इम्यूटेबल यूनीक ID जोड़ें।
- टोकन में एक ऑफ़सेट संख्या डालना → डीप पेज अभी भी ड्रिफ्ट होते हैं और अधिक लागत आती है → सॉर्ट बाउंड्री और फ़िल्टर डाइजेस्ट को ले जाएं।
- लाइव/स्नैपशॉट को अपरिभाषित छोड़ना → क्लाइंट इंसर्ट या डिलीट की व्याख्या नहीं कर सकते हैं → API अनुबंध में विजिबिलिटी सिमेंटिक्स का उल्लेख करें।
- इंडेक्स दिशा और फ़िल्टर की उपेक्षा करना → keyset अभी भी कई पंक्तियों को स्कैन करता है → निष्पादन योजना के साथ कंपोजिट इंडेक्स को सत्यापित करें।
- किसी भी क्लाइंट कर्सर को स्वीकार करना → टेनेंट या पुरानी सीमाओं को जाली बनाया जा सकता है → हस्ताक्षर करें, वर्शन को मान्य करें और टोकन समाप्त करें।
फॉलो-अप प्रश्न और उत्तर
फॉलो-अप 1: उपयोगकर्ताओं को पेज 500 पर जंप करना है। आप क्या करेंगे?
एक छोटी या अधिकतर स्थिर एडमिन सूची के लिए, बाउंडेड OFFSET उचित है। एक बड़ी सूची के लिए, प्रीकंप्यूटेड पेज सीमाओं या सर्च और सॉर्ट फ़िल्टर का उपयोग करें ताकि उपयोगकर्ता मनमानी गहराई पर कम लेटेंसी का वादा करने के बजाय वांछित क्षेत्र का पता लगा सकें। किसी भी विकल्प की लागत और कंसिस्टेंसी सीमाओं का दस्तावेजीकरण करें।
फॉलो-अप 2: created_at को संपादित किया जा सकता है। क्या कर्सर स्थिर है?
नहीं। इम्यूटेबल निर्माण समय और एक यूनीक ID का उपयोग करें, या म्यूटेबल सॉर्ट फ़ील्ड को स्नैपशॉट वर्शन में फ्रीज करें। यदि व्यवसाय को एक म्यूटेबल फ़ील्ड द्वारा सॉर्ट करना ही है, तो लाइव व्यू में हलचल को स्वीकार करें या अतिरिक्त स्टोरेज और रीड लागत के साथ एक निश्चित वर्शन का उपयोग करें।
फॉलो-अप 3: एक पिछड़ता हुआ रेप्लिका (lagging replica) अगले पेज को छोटा बना देता है। आप इसे कैसे संभालते हैं?
कंसिस्टेंसी आवश्यकता के अनुसार प्राइमरी या उसी रेप्लिका पर रीड्स को पिन करें, या "समय/LSN से पहले नहीं" की सीमा रखें और प्रतीक्षा समय समाप्त होने के बाद एक स्पष्ट स्थिति लौटाएं। गुम हुई पंक्तियों को सूची के अंत के रूप में चुपचाप न मानें; अस्थायी अदृश्यता को has_more=false से अलग करें।
फॉलो-अप 4: आप कैसे साबित करेंगे कि कोई डुप्लिकेट या गैप नहीं है?
नियंत्रित समवर्ती परीक्षण बनाएं: पेज एक पढ़ें, बाउंड्री से पहले इंसर्ट करें, बाउंड्री रो डिलीट करें, समान सॉर्ट कीज़ इंसर्ट करें, और पंक्तियों को अपडेट करें; फिर यूनीक-ID सेट, ऑर्डरिंग और वादा की गई विजिबिलिटी का निरीक्षण करें। पेज सीमाओं, टोकन संस्करणों और स्नैपशॉट पहचानकर्ताओं को लॉग करें ताकि विफलता को सटीक सीमा पर पुन: पेश किया जा सके।