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

आप एक स्टेबल कर्सर पेजिनेशन API कैसे डिज़ाइन करते हैं?

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

प्रश्न

एक orders टेबल में 200 मिलियन पंक्तियाँ हैं और लगातार राइट्स होते हैं, जो created_at DESC, id DESC द्वारा क्रमित हैं। एक ऐसी API डिज़ाइन करें जो प्रति पृष्ठ 50 पंक्तियाँ लौटाती हो, आगे और पीछे नेविगेशन का समर्थन करती हो, डीप-पेजिनेशन डिग्रेडेशन से बचाती हो, और कंकरेंट इंसर्ट्स, डिलीट्स और स्टेटस परिवर्तनों के तहत अपनी गारंटियों को स्पष्ट करती हो।

समस्या और दायरा

एक मल्टी-टेनेंट ऑर्डर सर्विस PostgreSQL का उपयोग करती है। इसकी orders टेबल में 200 मिलियन पंक्तियाँ हैं और इस पर लगातार इंसर्ट्स और स्टेटस परिवर्तन होते रहते हैं। एक ऑपरेशंस कंसोल ऑर्डर्स को टेनेंट और स्टेटस द्वारा फ़िल्टर करता है, हमेशा created_at DESC, id DESC द्वारा सॉर्ट करता है, और प्रति पृष्ठ अधिकतम 50 पंक्तियाँ लौटाता है। created_at और id दोनों नॉन-नल हैं और निर्माण के बाद इम्यूटेबल हैं; id यूनिक है। प्रोडक्ट को अगले-पृष्ठ और पिछले-पृष्ठ नेविगेशन की आवश्यकता है लेकिन पृष्ठ 500 पर सीधे जाने (जंप करने) की आवश्यकता नहीं है।

पेजिनेशन API, कर्सर पेलोड, क्वेरीज़ और इंडेक्स डिज़ाइन करें। डिज़ाइन को समान निर्माण टाइमस्टैम्प, डीप पेजिनेशन, अनुरोधों के बीच इंसर्ट्स और डिलीट्स, बदलते स्टेटस की सदस्यता, कर्सर से छेड़छाड़ (tampering), और दोनों दिशाओं में नेविगेशन को संभालना होगा। इसे लाइव ट्रैवर्सल और एक स्थिर स्नैपशॉट के बीच भी अंतर स्पष्ट करना चाहिए।

200 मिलियन पंक्तियाँ और 50-पंक्ति का पेज साइज़ इंटरव्यू की पूर्वधारणाएँ (assumptions) हैं। मुख्य कौशल API सिमेंटिक्स को डेटाबेस एक्सेस पाथ, कंकरेंसी बाउंड्री और वेरिफिकेशन प्लान में बदलना है, इसलिए यह एक बैकएंड प्रश्न है। क्रॉस-रीजन रेप्लिकेशन, शार्डिंग और क्लाइंट-साइड लिस्ट स्टेट पहले दौर के दायरे से बाहर हैं।

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

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

दूसरा संकेत टोटल ऑर्डर है। यदि क्वेरी केवल created_at द्वारा सॉर्ट करती है, तो कई ऑर्डर्स में टाई हो सकती है और डेटाबेस उन्हें मनमाने ढंग से व्यवस्थित कर सकता है। दूसरे सॉर्ट की के रूप में एक यूनिक id, कर्सर में दोनों मानों के साथ, पिछले पृष्ठ की अंतिम पंक्ति के बाद की स्थिति की सटीक पहचान करता है।

तीसरा संकेत यह है कि क्या क्वेरी, इंडेक्स और कर्सर समान क्रम लागू करते हैं। tenant_id और status इक्वेलिटी फ़िल्टर हैं। created_at और id रेंज बाउंड्री और सॉर्ट ऑर्डर बनाते हैं। इसलिए एक उम्मीदवार B-tree इंडेक्स (tenant_id, status, created_at DESC, id DESC) है। तुलना प्रेडिकेट, इंडेक्स और रिवर्स क्वेरी के बिना "offset" शब्द को "cursor" से बदलना अधूरा है।

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

स्पष्टीकरण हेतु प्रश्न

  • क्या यह एक लाइव सूची है या एक स्थिर स्नैपशॉट? एक लाइव सूची बाद के पृष्ठों पर कुछ परिवर्तनों को दर्शा सकती है। एक निश्चित स्नैपशॉट के लिए ट्रैवर्सल को एक सेट देखने की आवश्यकता होती है, आमतौर पर वर्ज़न्ड डेटा, मटेरियलाइज़्ड रिज़ल्ट या एक स्नैपशॉट तंत्र के माध्यम से जो अनुरोधों के दौरान बना रहता है। लागत और समाप्ति नीतियां भिन्न होती हैं।
  • क्या उपयोगकर्ताओं को मनमाने पृष्ठों पर जंप करने की आवश्यकता है? कीसेट पेजिनेशन अनुक्रमिक अगले और पिछले नेविगेशन में अच्छा है, लेकिन केवल "page 500" एक की बाउंड्री प्रकट नहीं करता है। यदि पेज जंप अनिवार्य हैं, तो एक सीमित गहराई के लिए ऑफ़सेट बनाए रखें या एंकरों की प्रीकंप्यूटिंग करें।
  • क्या सॉर्ट या फ़िल्टर फ़ील्ड बदल सकते हैं? created_at और id स्थिर रहने चाहिए। म्यूटेबल updated_at, कीमत या स्कोर द्वारा सॉर्ट करने से एक रिकॉर्ड पहले से देखी जा चुकी बाउंड्री को पार कर सकता है। स्टेटस परिवर्तन भी परिणाम सदस्यता को बदलते हैं और सार्वजनिक अनुबंध का हिस्सा होने चाहिए।
  • क्या प्रोडक्ट को सटीक कुल योग और अंतिम पृष्ठ की आवश्यकता है? एक अतिरिक्त पंक्ति लाने से hasNextPage निर्धारित किया जा सकता है; सटीक totalCount एक अलग एग्रीगेशन लागत है। एक लोड-मोर इंटरफ़ेस को प्रत्येक पृष्ठ पर पूर्ण गणना (full count) के लिए बाध्य नहीं करना चाहिए।
  • कर्सर कब तक मान्य रह सकता है? स्कीमा परिवर्तन, फ़िल्टर-नियम परिवर्तन, साइनिंग-की रोटेशन और स्नैपशॉट प्रतिधारण सभी वैधता को प्रभावित करते हैं। सर्वर को एक एक्सपायरी एरर और पहले पृष्ठ से पुनरारंभ करने के स्पष्ट तरीके की आवश्यकता होती है।
  • क्या पेजिंग के दौरान टेनेंट एक्सेस या अनुमतियाँ बदल सकती हैं? कर्सर कभी भी वर्तमान अनुरोध के लिए प्रमाणीकरण और प्राधिकरण को प्रतिस्थापित नहीं करता है। प्रत्येक पृष्ठ वर्तमान प्रिंसिपल की पुनर्जांच करता है, और कर्सर को सामान्यीकृत फ़िल्टर और स्कोप से बांधा जाता है ताकि इसे विभिन्न टेनेंट्स में पुन: उपयोग न किया जा सके।

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

"मैं पहले यह पुष्टि करूँगा कि यह एक लाइव ऑर्डर्ड ट्रैवर्सल है, न कि अनुरोधों के दौरान एक डेटाबेस स्नैपशॉट। मैं इम्यूटेबल created_at DESC, id DESC का उपयोग करूँगा, जिसमें यूनिक ID टाइमस्टैम्प टाई को तोड़ेगी। एक नेक्स्ट-पेज कर्सर पिछले पेज के अंतिम दो मान, एक वर्ज़न और एक फ़िल्टर फ़िंगरप्रिंट ले जाता है, फिर URL-सेफ़ एन्कोडिंग और एक HMAC सिग्नेचर का उपयोग करता है। क्वेरी (created_at, id) < (cursor_time, cursor_id), समान अवरोही क्रम, और LIMIT 51 लागू करती है; यह 50 पंक्तियाँ लौटाती है और hasNextPage के लिए अतिरिक्त पंक्ति का उपयोग करती है। मैचिंग इंडेक्स (tenant_id, status, created_at DESC, id DESC) है। पिछले पृष्ठ के लिए, मैं तुलना और डेटाबेस क्रम को उलट देता हूँ, फिर प्रतिक्रिया को उलट देता हूँ। नए इंसर्ट बाद के पृष्ठों को शिफ्ट नहीं करते हैं, लेकिन डिलीट्स और स्टेटस परिवर्तन फ़्रीज़ नहीं होते हैं। सख्त स्नैपशॉट के लिए वर्ज़न्ड डेटा या मटेरियलाइज़्ड सत्र की आवश्यकता होती है। मैं टाई टाइमस्टैम्प, कंकरेंट राइट्स, बाउंड्री विलोपन, कर्सर से छेड़छाड़ और डीप-पेज प्लान्स का परीक्षण करूँगा।"

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

API अनुबंध को परिभाषित करके शुरुआत करें। पहले पृष्ठ में कोई कर्सर नहीं होता है। फ़ॉरवर्ड नेविगेशन after स्वीकार करता है; बैकवर्ड नेविगेशन before स्वीकार करता है; एक अनुरोध में दोनों शामिल नहीं हो सकते हैं। डिफ़ॉल्ट पेज साइज़ 50 है, जिसमें सर्वर द्वारा लागू की गई 1 से 100 की सीमा है। एक प्रतिक्रिया इस आकार का उपयोग कर सकती है:

json
{
  "data": [],
  "pageInfo": {
    "startCursor": "...",
    "endCursor": "...",
    "hasNextPage": true,
    "hasPreviousPage": false
  }
}

startCursor और endCursor लौटाई गई पहली और अंतिम पंक्तियों से आते हैं। hasNextPage निर्धारित करने के लिए 51 पंक्तियों का अनुरोध करें और केवल 50 लौटाएं। यदि विपरीत-दिशा फ़्लैग सटीक होना चाहिए, तो दूसरी बाउंड्री से एक छोटी इंडेक्स की गई EXISTS क्वेरी चलाएं। केवल after पैरामीटर की उपस्थिति से इसका अनुमान लगाना सभी पूर्ववर्ती पंक्तियों के हटाए जाने के बाद गलत हो सकता है।

पोज़िशनल ऑफ़सेट्स को एक कंपाउंड बाउंड्री से बदलें

पहले पृष्ठ की क्वेरी है:

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
ORDER BY created_at DESC, id DESC
LIMIT 51;

अगले पृष्ठ के लिए, पिछले पृष्ठ के अंतिम (created_at, id) युग्म को एक स्ट्रिक्ट लेस-दैन प्रेडिकेट में रखें:

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
  AND (created_at, id) < ($3, $4)
ORDER BY created_at DESC, id DESC
LIMIT 51;

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

मैचिंग इंडेक्स है:

sql
CREATE INDEX CONCURRENTLY orders_tenant_status_created_id_idx
ON orders (tenant_id, status, created_at DESC, id DESC);

अग्रणी इक्वेलिटी कॉलम स्कैन को एक टेनेंट और स्टेटस तक सीमित करते हैं। अंतिम दो कॉलम बाउंड्री और ऑर्डर प्रदान करते हैं। क्या रिस्पांस फ़ील्ड्स को शामिल कॉलम (included columns) के रूप में जोड़ना है, यह मापी गई पंक्ति की चौड़ाई, हीप फ़ेच और राइट एम्प्लीफिकेशन पर निर्भर करता है; प्रत्येक रिस्पांस फ़ील्ड को INCLUDE में कॉपी करना कोई डिफ़ॉल्ट नहीं है। लगातार स्टेटस परिवर्तन भी इस इंडेक्स को बनाए रखते हैं, इसलिए राइट लेटेंसी, WAL वॉल्यूम और इंडेक्स साइज़ को मापें।

कर्सर को इवॉल्वेबल और छेड़छाड़-स्पष्ट (Tamper-Evident) बनाएं

कर्सर एक अपारदर्शी (opaque) प्रोटोकॉल है, न कि क्लाइंट के लिए created_at मान को असेंबल करने का अनुरोध। इसका लॉजिकल पेलोड हो सकता है:

json
{
  "v": 1,
  "createdAt": "2026-07-16T02:30:00.123Z",
  "id": "ord_01J...",
  "filterHash": "sha256:...",
  "expiresAt": "2026-07-17T02:30:00Z"
}

सर्वर एक कैनोनिकल पेलोड पर URL-सेफ़ एन्कोडिंग लागू करता है और एक HMAC संलग्न करता है। Base64 एन्कोडिंग है, गोपनीयता (confidentiality) नहीं। यदि बाउंड्री मान संवेदनशील हैं, तो उन्हें क्लाइंट टोकन में उजागर न करें; इसके बजाय उन्हें सर्वर-साइड पर एक रैंडम कर्सर ID के तहत स्टोर करें। filterHash में कम से कम ऑर्डरिंग वर्ज़न, स्टेटस फ़िल्टर और एक ऑथराइजेशन-स्कोप पहचानकर्ता शामिल होना चाहिए। सर्वर द्वारा सिग्नेचर, वर्ज़न, एक्सपायरी और फ़िल्टर फ़िंगरप्रिंट को सत्यापित करने से पहले प्रत्येक अनुरोध को वर्तमान प्रिंसिपल के विरुद्ध अधिकृत किया जाता है। कोई भी बेमेल एक स्थिर invalid_cursor प्रतिक्रिया लौटाता है और पहले पृष्ठ से पुनरारंभ करने की आवश्यकता होती है; इसे चुपचाप नए फ़िल्टरों पर कर्सर लागू नहीं करना चाहिए।

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

पिछले पृष्ठ को सममितीय रूप से (Symmetrically) लागू करें

वर्तमान पृष्ठ की पहली पंक्ति नए रिकॉर्ड्स की ओर नेविगेट करने की बाउंड्री है। प्रेडिकेट को > में बदलें और आरोही क्रम में क्वेरी करें ताकि डेटाबेस पहले उस बाउंड्री के निकटतम 50 पंक्तियों को लौटाए:

sql
SELECT id, created_at, total_cents, status
FROM orders
WHERE tenant_id = $1
  AND status = $2
  AND (created_at, id) > ($3, $4)
ORDER BY created_at ASC, id ASC
LIMIT 51;

51वीं प्रोब पंक्ति को हटाने के बाद, सर्वर परिणाम को उलट देता है और सार्वजनिक अवरोही क्रम लौटाता है। LIMIT के साथ अवरोही क्रम रखने से संपूर्ण परिणाम सेट में पहली 50 पंक्तियाँ चुनी जाएंगी, न कि बाउंड्री के ठीक पहले का पृष्ठ। दोनों दिशाओं में समान फ़िल्टर फ़िंगरप्रिंट और ऑर्डरिंग वर्ज़न साझा होना चाहिए।

कंकरेंट परिवर्तनों के तहत गारंटियों का उल्लेख करें

लाइव सिमेंटिक्स के तहत, कर्सर का अर्थ है "इस इम्यूटेबल बाउंड्री के ठीक नीचे की पंक्तियों के साथ जारी रखें।" यदि उपयोगकर्ता द्वारा पृष्ठ एक पढ़ने के बाद 100 नए ऑर्डर आते हैं, तो पृष्ठ दो अभी भी पुरानी बाउंड्री से फिर से शुरू होता है। नई पंक्तियाँ पृष्ठ एक की अंतिम पंक्ति को पृष्ठ दो पर नहीं धकेलती हैं। इसके बाद बाउंड्री पंक्ति को हटाने से भी ट्रैवर्सल नहीं टूटता है क्योंकि कर्सर मानों को बनाए रखता है; क्वेरी को उस पंक्ति को फिर से खोजने की आवश्यकता नहीं होती है।

यह एक स्थिर स्नैपशॉट नहीं है। एक अपठित ऑर्डर जो किसी अन्य स्टेटस से चयनित स्टेटस में बदलता है, बाद में दिखाई दे सकता है यदि उसकी सॉर्ट स्थिति वर्तमान बाउंड्री से नीचे है। फ़िल्टर सेट छोड़ने वाला ऑर्डर दिखाई नहीं देगा। विलोपन (deletions) ट्रैवर्सल द्वारा देखी गई पंक्तियों को कम कर सकते हैं। कर्सर में "first-page start time" डालने से केवल नए इंसर्ट्स सीमित होते हैं; यह डिलीट्स या स्टेटस परिवर्तनों को फ़्रीज़ नहीं करता है।

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

ऑफ़सेट पेजिनेशन को उसकी उपयोगी बाउंड्री के भीतर रखें

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

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

एडवरसैरियल मामलों के साथ अनुबंध सिद्ध करें

विलंबता (latency) मापने से पहले सदस्यता और क्रम को सत्यापित करें:

  1. समान created_at वाले 120 ऑर्डर डालें। पुष्टि करें कि यूनिक id पृष्ठों के बीच डुप्लिकेट को रोकता है और संयुक्त ट्रैवर्सल कड़ाई से अवरोही है।
  2. पृष्ठ एक पढ़ें, फिर 100 नए ऑर्डर डालें। ऑफ़सेट के साथ पोज़िशनल शिफ्ट को पुन: प्रस्तुत करें और पुष्टि करें कि कीसेट पृष्ठ दो पृष्ठ-एक की पंक्ति को नहीं दोहराता है।
  3. जारी रखने से पहले पृष्ठ एक की अंतिम पंक्ति हटाएं और पुष्टि करें कि संग्रहीत बाउंड्री अभी भी सही अगला पृष्ठ लौटाती है।
  4. अनुरोधों के बीच एक अपठित ऑर्डर का status बदलें और पुष्टि करें कि परिणाम स्नैपशॉट के रूप में रिपोर्ट किए जाने के बजाय प्रलेखित लाइव सिमेंटिक्स का पालन करता है।
  5. एक कर्सर बाइट संशोधित करें, इसे टेनेंट्स के बीच पुन: उपयोग करें, स्टेटस फ़िल्टर बदलें, और एक समाप्त हो चुके वर्ज़न को सबमिट करें। प्रत्येक अनुरोध को अस्वीकार कर दिया जाना चाहिए।
  6. ID और क्रम की जाँच करते हुए तीन पृष्ठ आगे और तीन पृष्ठ पीछे जाएँ। 0, 1, 50 और 51 के परिणाम आकार शामिल करें।
  7. पहले पृष्ठ, दूसरे पृष्ठ और एक गहरी बाउंड्री के लिए EXPLAIN (ANALYZE, BUFFERS) चलाएँ। अपेक्षित कंपाउंड इंडेक्स, पेज साइज़ के करीब स्कैन की गई पंक्ति संख्या की पुष्टि करें, और रीड p95, राइट p95, WAL वॉल्यूम और इंडेक्स साइज़ रिकॉर्ड करें।

एक मजबूत उत्तर का उदाहरण

"मैं पहले गारंटी को सीमित करूँगा। इस ऑपरेशंस सूची को लाइव सीक्वेंशियल ब्राउज़िंग की आवश्यकता है, सीधे पेज जंप की नहीं, और कई HTTP अनुरोधों में डेटाबेस स्नैपशॉट की नहीं। इसलिए मैं ऑफ़सेट के बजाय कीसेट पेजिनेशन का उपयोग करूँगा।

क्रम इम्यूटेबल created_at DESC, id DESC है। ID यूनिक टाई-ब्रेकर है; इसके बिना, एक ही मिलीसेकंड में बनाए गए ऑर्डर्स का कोई स्थिर क्रम नहीं होता है। मैं 51 पंक्तियाँ फ़ेच करता हूँ और 50 लौटाता हूँ। अगला कर्सर अंतिम पंक्ति के टाइमस्टैम्प और ID को संग्रहीत करता है, और अगली क्वेरी बिल्कुल उसी क्रम के साथ (created_at, id) < (?, ?) का उपयोग करती है। इक्वेलिटी फ़िल्टर (tenant_id, status, created_at DESC, id DESC) में पहले आते हैं। पिछले पृष्ठ के लिए, वर्तमान पहली पंक्ति बाउंड्री है; मैं > का उपयोग करता हूँ, निकटतम 51 पंक्तियों के लिए आरोही क्वेरी करता हूँ, फिर प्रतिक्रिया को उलट देता हूँ।

कर्सर में एक वर्ज़न, दो बाउंड्री मान, एक फ़िल्टर फ़िंगरप्रिंट और एक एक्सपायरी शामिल होती है। यह URL-सेफ़ एन्कोडेड और HMAC-साइन्ड है। सर्वर प्रत्येक पृष्ठ को फिर से अधिकृत करता है और छेड़छाड़ किए गए, समाप्त हो चुके, या फ़िल्टर-बेमेल कर्सर को अस्वीकार कर देता है। यह इस बात पर निर्भर नहीं करता है कि बाउंड्री पंक्ति अभी भी मौजूद है, इसलिए उस पंक्ति को हटाने से नेविगेशन नहीं रुकता है।

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

अंत में, मैं टाई टाइमस्टैम्प, कंकरेंट इंसर्ट्स, बाउंड्री विलोपन, स्टेटस परिवर्तन, कर्सर से छेड़छाड़ और आगे/पीछे राउंड ट्रिप्स का परीक्षण करूँगा। मैं फिर पृष्ठ एक, पृष्ठ दो और एक गहरी बाउंड्री के लिए योजनाओं की तुलना करूँगा, साथ ही राइट लेटेंसी और WAL पर नए इंडेक्स के प्रभाव की तुलना करूँगा।"

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

  • पेज नंबर को Base64-एन्कोड करना → सर्वर अभी भी OFFSET निष्पादित करता है, इसलिए न तो प्रदर्शन और न ही पोज़िशनल ड्रिफ्ट बदलता है → कर्सर को एक इंडेक्स की गई ऑर्डरिंग बाउंड्री का प्रतिनिधित्व करने दें।
  • केवल created_at का उपयोग करना → टाई टाइमस्टैम्प कुल क्रम नहीं बनाते हैं और दोहराए जा सकते हैं या छोड़े जा सकते हैं → क्वेरी और कर्सर दोनों में एक यूनिक, स्थिर id जोड़ें।
  • सॉर्ट क्रम से असहमत प्रेडिकेट का उपयोग करना → बाउंड्री अब सार्वजनिक क्रम का प्रतिनिधित्व नहीं करती है, जिससे ओवरलैप या गैप बनते हैं → एक लेक्सिकोग्राफ़िक क्रम से ORDER BY, तुलना ऑपरेटर और इंडेक्स प्राप्त करें।
  • Base64 को छेड़छाड़ सुरक्षा मानना → एक क्लाइंट टाइमस्टैम्प, ID या फ़िल्टर स्कोप को बदल सकता है → अखंडता के लिए HMAC का उपयोग करें और वर्तमान प्राधिकरण को फिर से चलाएं।
  • फ़िल्टर को कर्सर बाइंडिंग से बाहर छोड़ना → पेड-ऑर्डर क्वेरी के लिए पेंडिंग-ऑर्डर कर्सर का पुन: उपयोग करने से अस्पष्ट गैप बनते हैं → हस्ताक्षरित फ़िंगरप्रिंट में सामान्यीकृत फ़िल्टर, ऑर्डरिंग वर्ज़न और स्कोप शामिल करें।
  • पिछले पृष्ठ LIMIT के लिए अवरोही क्रम बनाए रखना → क्वेरी बाउंड्री के बगल वाले सेगमेंट के बजाय पूरे सेट के शीर्ष (head) को लौटाती है → डेटाबेस क्रम को उलटें, ट्रिम करें, और प्रतिक्रिया को उलटें।
  • यह दावा करना कि कीसेट एक पूर्ण स्नैपशॉट है → डिलीट्स, स्टेटस परिवर्तन और म्यूटेबल सॉर्ट कीज़ अभी भी सदस्यता को बदलते हैं → लाइव सिमेंटिक्स प्रकाशित करें; सख्त कंसिस्टेंसी की आवश्यकता होने पर वर्ज़न्ड या मटेरियलाइज़्ड स्नैपशॉट जोड़ें।
  • प्रत्येक पृष्ठ पर पूर्ण परिणाम की गणना करना → पेजिनेशन तेज़ हो जाता है जबकि totalCount नया धीमा पाथ बन जाता है → गणना को एक अलग, कैशेबल या अनुमानित उत्पाद क्षमता के रूप में मानें।
  • केवल पहले पृष्ठ का परीक्षण करना → कंपाउंड बाउंड्री का उपयोग पहले पृष्ठ दो पर किया जाता है, जहाँ एक इंडेक्स बेमेल सामने आ सकता है → पहले दो पृष्ठों, गहरे पृष्ठों और दोनों दिशाओं के लिए योजनाओं का निरीक्षण करें।

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

क्या होगा यदि उत्पाद को म्यूटेबल total_cents द्वारा सॉर्ट करना आवश्यक हो?

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

क्या होगा यदि उपयोगकर्ताओं को सीधे पृष्ठ 500 पर जंप करना हो?

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

क्या यह API सभी 200 मिलियन पंक्तियों को निर्यात कर सकता है?

एक इंटरैक्टिव API का अल्पकालिक कर्सर और लाइव सिमेंटिक्स एक लंबे ऑडिट निर्यात के लिए उपयुक्त नहीं हैं। एक बैकग्राउंड निर्यात जॉब बनाएं, एक इम्यूटेबल प्राइमरी की के टुकड़ों में एक निश्चित स्नैपशॉट या वर्ज़न बाउंड्री पढ़ें, ऑब्जेक्ट स्टोरेज में लिखें, और चेकपॉइंट और सत्यापन गणनाओं को बनाए रखें। रिट्रीज़, एक्सपायरी, रिसोर्स लिमिट्स और पूर्णता जांच का तब एक HTTP कर्सर को ऑनलाइन इंडेक्स और साइनिंग प्रारूप से अनिश्चित काल तक बांधने के बजाय अपना स्वयं का जीवनचक्र होता है।

पेजिनेशन के दौरान आप साइनिंग कीज़ को कैसे रोटेट करते हैं?

कर्सर में एक वर्ज़न या की पहचानकर्ता शामिल करें। अधिकतम कर्सर लाइफटाइम के लिए, सत्यापनकर्ता वर्तमान और पिछली सत्यापन कुंजियों को बनाए रखता है जबकि नए कर्सर पर केवल वर्तमान कुंजी के साथ हस्ताक्षर किए जाते हैं। उस विंडो के बाद पुरानी कुंजी को हटा दें। यदि किसी सुरक्षा घटना के लिए तत्काल निरसन की आवश्यकता होती है, तो लगातार invalid_cursor लौटाएं और सहज नेविगेशन के लिए एक से समझौता की गई कुंजी को स्वीकार करने के बजाय क्लाइंट को पृष्ठ एक पर पुनरारंभ करें।

बाउंड्री पंक्ति को हटाना काम क्यों करता है जबकि इसकी सॉर्ट की बदलना काम नहीं करता?

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

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

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