तेज़ एडमिन स्क्रीन API के लिए कर्सर बनाम ऑफ़सेट पेजिनेशन
सीखें कि कर्सर बनाम ऑफ़सेट पेजिनेशन कैसे काम करता है और एक सुसंगत API अनुबंध (सॉर्टिंग, फ़िल्टर, टोटल्स) कैसे बनाएं ताकि वेब और मोबाइल पर एडमिन स्क्रीन तेज़ रहें।

क्यों पेजिनेशन एडमिन स्क्रीन को धीमा महसूस करा सकता है
एडमिन स्क्रीन अक्सर एक साधारण टेबल के रूप में शुरू होती हैं: पहले 25 पंक्तियाँ लोड करो, एक सर्च बॉक्स जोड़ो, हो गया। कुछ सौ रिकॉर्ड्स के साथ यह त्वरित महसूस होता है। फिर डेटासेट बढ़ता है, और वही स्क्रीन ठिठकने लगती है।
आम समस्या UI की नहीं होती। समस्या यह होती है कि API को पेज 12 लौटाने से पहले क्या करना पड़ता है जब सॉर्ट और फ़िल्टर लागू हैं। जैसे-जैसे तालिका बड़ी होती है, बैकएंड को मिलान करने, उनकी गिनती करने और पहले के परिणामों को स्किप करने में अधिक समय लगता है। अगर हर क्लिक पर भारी क्वेरी चलती है, तो स्क्रीन सोचती हुई लगती है बजाय तुरंत रिस्पॉन्स करने के।
आप इसे वहीँ महसूस करते हैं: पेज बदलना धीरे हो जाता है, सॉर्टिंग सुस्त हो जाती है, सर्च पन्नों में असंगत लगता है, और अनंत स्क्रॉल तेज़-धीमा लोड करता है। व्यस्त सिस्टम्स में डेटा बदलने पर डुप्लिकेट या गायब पंक्तियाँ भी दिख सकती हैं।
वेब और मोबाइल UI भी पेजिनेशन को अलग तरह से धकेलते हैं। एक वेब एडमिन टेबल पेज नंबर्स पर कूदने और कई कॉलम के अनुसार सॉर्ट करने के लिए प्रोत्साहित करती है। मोबाइल स्क्रीन सामान्यतः अगले खंड को लोड करने वाली अनंत सूची का उपयोग करती हैं, और उपयोगकर्ता उम्मीद करते हैं कि हर लोड समान रूप से तेज़ हो। अगर आपका API केवल पेज नंबरों के इर्द-गिर्द बना है, तो मोबाइल अक्सर प्रभावित होता है। और अगर यह केवल next/after के इर्द-गिर्द बना है, तो वेब टेबल सीमित महसूस कर सकती है।
लक्ष्य सिर्फ 25 आइटम लौटाना नहीं है। तेज़, voorsig्य पेजिंग चाहिए जो डेटा बढ़ने के साथ स्थिर रहे, और नियम ऐसे हों जो टेबल और अनंत सूची दोनों के लिए समान रूप से काम करें।
पेजिनेशन के मूल जो आपकी UI पर निर्भर करते हैं
पेजिनेशन लंबे लिस्ट को छोटे हिस्सों में बाँटना है ताकि स्क्रीन जल्दी लोड और रेंडर कर सके। UI API से हर रिकॉर्ड माँगने की बजाय अगले स्लाइस के लिए अनुरोध करती है।
सबसे महत्वपूर्ण नियंत्रण पेज साइज़ है (अक्सर limit कहा जाता है)। छोटे पेज सर्वर का काम घटाते हैं और ऐप कम पंक्तियाँ रेंडर करता है, इसलिए तेज़ महसूस होते हैं। लेकिन पेज बहुत छोटे हों तो उपयोगकर्ताओं को बार-बार क्लिक या स्क्रोल करना पड़ता है। कई एडमिन टेबल्स के लिए 25 से 100 आइटम व्यावहारिक रेंज है, मोबाइल आमतौर पर निचले सिरे को पसंद करता है।
एक स्थिर सॉर्ट ऑर्डर ज़्यादातर टीमों की अपेक्षा से अधिक मायने रखता है। अगर अनुरोधों के बीच ऑर्डर बदल सकता है तो उपयोगकर्ता पेजिंग के दौरान डुप्लिकेट या गायब पंक्तियाँ देखेंगे। स्थिर सॉर्टिंग आमतौर पर एक प्राथमिक फ़ील्ड (जैसे created_at) और एक टाई-ब्रेकर (जैसे id) से मिलकर बनती है। यह ज़रूरी है चाहे आप ऑफ़सेट या कर्सर पेजिनेशन उपयोग कर रहे हों।
क्लाइंट के दृष्टिकोण से, पेजिनेटेड रिस्पॉन्स में आइटम्स, एक नेक्स्ट-पेज संकेत (पेज नंबर या कर्सर टोकन), और केवल वही काउंट होने चाहिए जिनकी UI को वास्तव में ज़रूरत है। कुछ स्क्रीन को “1-50 of 12,340” जैसा सटीक टोटल चाहिए। दूसरे सिर्फ has_more चाहते हैं।
ऑफ़सेट पेजिनेशन: यह कैसे काम करता है और कहाँ दर्द देता है
ऑफ़सेट पेजिनेशन क्लासिक पेज N तरीका है। क्लाइंट निश्चित संख्या की पंक्तियाँ मांगता है और API को बताता है कि पहले कितनी पंक्तियाँ छोड़नी हैं। आपको यह limit और offset के रूप में दिखेगा, या page और pageSize के रूप में जिसे सर्वर ऑफ़सेट में बदल देता है।
एक सामान्य अनुरोध कुछ ऐसा दिखता है:
GET /tickets?limit=50&offset=950- “मुझे 50 टिकट दें, पहले 950 छोड़कर।”
यह सामान्य एडमिन जरूरतों से मेल खाता है: पेज 20 पर कूदना, पुराने रिकॉर्ड स्कैन करना, या बड़े लिस्ट को चंक्स में एक्सपोर्ट करना। यह आंतरिक रूप से भी समझने में आसान है: “पेज 3 देखो और तुम इसे देखोगे।”
समस्या गहरे पृष्ठों पर दिखती है। कई डेटाबेस अभी भी स्किप की गई पंक्तियों को पार करना पड़ता है, खासकर जब सॉर्ट ऑर्डर एक तंग इंडेक्स पर नहीं है। पेज 1 तेज़ हो सकता है, पर पेज 200 काफी धीमा हो सकता है, और यही बात एडमिन स्क्रीन को सुस्त बनाती है जब उपयोगकर्ता स्क्रॉल या कूदते हैं।
दूसरी समस्या डेटा बदलने पर स्थिरता है। कल्पना करें एक सपोर्ट मैनेजर पेज 5 खोलता है और नवीनतम के अनुसार सॉर्ट किया गया है। वे देखते-देखते नए टिकट आ जाते हैं या पुराने हट जाते हैं। इंसर्शन आइटम्स को आगे धकेल सकते हैं (पेजों के बीच डुप्लिकेट)। डिलीशन आइटम्स को पीछे धकेल सकते हैं (रिकॉर्ड्स गायब दिख सकते हैं)।
ऑफ़सेट पेजिनेशन छोटे टेबल्स, स्थिर डेटासेट्स, या एक-बार के एक्सपोर्ट्स के लिए ठीक रह सकता है। बड़े, सक्रिय तालिकाओं पर एज केस जल्दी दिखते हैं।
कर्सर पेजिनेशन: यह कैसे काम करता है और यह क्यों स्थिर रहता है
कर्सर पेजिनेशन बुकमार्क के रूप में एक कर्सर का उपयोग करता है। “मुझे पेज 7 दे” कहने की बजाय क्लाइंट कहता है “इस सही आइटम के बाद जारी रखें।” कर्सर आमतौर पर आखिरी आइटम के सॉर्ट मानों को एन्कोड करता है (उदाहरण के लिए created_at और id), ताकि सर्वर सही जगह से फिर से शुरू कर सके।
अनुरोध आमतौर पर केवल:
limit: कितने आइटम लौटाने हैंcursor: पिछली रिस्पॉन्स से एक ओपेक टोकन (अक्सरafterकहा जाता है)
रिस्पॉन्स आइटम्स के साथ एक नया कर्सर भी लौटाता है जो उस स्लाइस के अंत की ओर इशारा करता है। व्यावहारिक अंतर यह है कि कर्सर डेटाबेस से पंक्तियों की गिनती और स्किप नहीं मांगता। यह एक ज्ञात स्थिति से शुरू करने के लिए कहता है।
यही वजह है कि कर्सर पेजिनेशन आगे की ओर स्क्रॉल करने वाली सूचियों के लिए तेज़ रहता है। एक अच्छे इंडेक्स के साथ, डेटाबेस “X के बाद की आइटम्स” पर कूद सकता है और फिर अगले limit पंक्तियाँ पढ़ सकता है। ऑफ़सेट के साथ, सर्वर को अक्सर जैसे-जैसे ऑफ़सेट बढ़ता है, अधिक पंक्तियों को स्कैन या स्किप करना पड़ता है।
UI व्यवहार के लिए, कर्सर पेजिनेशन “Next” को नैचुरल बनाता है: आप लौटाए गए कर्सर को लेते हैं और अगली रिक्वेस्ट में भेज देते हैं। “Previous” वैकल्पिक और थोड़ा जटिल है। कुछ APIs before कर्सर सपोर्ट करते हैं, जबकि अन्य रिवर्स में फेच करके परिणाम पलट देते हैं।
कब कर्सर, ऑफ़सेट, या हाइब्रिड चुनें
चुनाव यह देख कर शुरू होता है कि लोग सूची का वास्तव में कैसे उपयोग करते हैं।
कर्सर पेजिनेशन तब सबसे अच्छा फिट बैठता है जब उपयोगकर्ता ज्यादातर आगे बढ़ते हैं और गति सबसे महत्वपूर्ण है: activity logs, chats, orders, tickets, audit trails, और अधिकांश मोबाइल अनंत स्क्रॉल। यह उन स्थितियों में भी बेहतर व्यवहार करता है जब ब्राउज़ करते हुए नए रोज़ जोड़े या हटाए जाते हैं।
ऑफ़सेट पेजिनेशन तब समझ में आता है जब उपयोगकर्ता अक्सर इधर-उधर कूदते हैं: क्लासिक एडमिन टेबल्स जिनमें पेज नंबर, गो-टू-पेज, और तेज़ बॅक-एंड-फोर्थ नेविगेशन होता है। यह समझाने में सरल है, पर बड़े डेटासेट्स पर धीमा और डेटा बदलने पर कम स्थिर हो सकता है।
निवेश करने का व्यावहारिक तरीका:
- मुख्य क्रिया “नेक्स्ट, नेक्स्ट, नेक्स्ट” हो तो कर्सर चुनें।
- जब “पेज N पर जाओ” असली आवश्यकता हो तो ऑफ़सेट चुनें।
- टोटल्स को वैकल्पिक मानें; बड़े तालिकाओं पर सटीक टोटल महंगा हो सकता है।
हाइब्रिड सामान्य हैं। एक दृष्टिकोण है नेक्स्ट/प्रेव के लिए कर्सर-आधारित और तेज़ी के लिए, साथ में एक वैकल्पिक पेज-जंप मोड छोटे, फ़िल्टर किए गए subsets के लिए जहाँ ऑफ़सेट अभी भी तेज़ रहे। दूसरा तरीका है कॅश्ड स्नैपशॉट पर आधारित पेज नंबर्स दोनों को महसूस कराने के लिए।
वेब और मोबाइल पर काम करने वाला एक सुसंगत API अनुबंध
एडमिन UI तभी तेज़ महसूस करते हैं जब हर लिस्ट एंडपॉइंट एक जैसा व्यवहार करे। UI बदल सकती है (वेब टेबल या मोबाइल अनंत स्क्रॉल), पर API अनुबंध स्थिर होना चाहिए ताकि आपको हर स्क्रीन के लिए अलग पेजिनेशन नियम सीखने न पड़े।
एक व्यावहारिक अनुबंध में तीन हिस्से हों: rows, paging state, और वैकल्पिक totals। एंडपॉइंट्स केAcross नाम समान रखें (tickets, users, orders), भले ही अंडरलाइनिंग पेजिंग मोड अलग हो।
यहाँ एक रिस्पॉन्स शेप है जो वेब और मोबाइल दोनों के लिए अच्छी तरह काम करती है:
{
"data": [ { "id": "...", "createdAt": "..." } ],
"page": {
"mode": "cursor",
"limit": 50,
"nextCursor": "...",
"prevCursor": null,
"hasNext": true,
"hasPrev": false
},
"totals": {
"count": 12345,
"filteredCount": 120
}
}
कुछ विवरण इसे पुन:उपयोग करने योग्य बनाते हैं:
page.modeक्लाइंट को बताता है कि सर्वर क्या कर रहा है बिना फील्ड नाम बदले।limitहमेशा रिक्वेस्ट किया गया पेज साइज है।nextCursorऔरprevCursorतब भी मौजूद रहे जब एकnullहो।totalsवैकल्पिक है। अगर यह महंगा है तो केवल क्लाइंट के पूछने पर लौटाएँ।
एक वेब टेबल अभी भी “Page 3” दिखा सकती है अपनी खुद की पेज इंडेक्स रखकर और API को बार-बार कॉल करके। एक मोबाइल सूची पेज नंबर को अनदेखा कर सकती है और बस अगले खंड का अनुरोध करती है।
अगर आप वेब और मोबाइल दोनों एडमिन UIs AppMaster में बना रहे हैं, तो ऐसा स्थिर अनुबंध जल्दी लाभ देता है। एक ही लिस्ट व्यवहार स्क्रीन केAcross reuse किया जा सकता है बिना हर एंडपॉइंट के लिए कस्टम पेजिनेशन लॉजिक बनाए।
सॉर्टिंग नियम जो पेजिनेशन को स्थिर रखते हैं
सॉर्टिंग वह जगह है जहाँ पेजिनेशन आमतौर पर टूटता है। अगर ऑर्डर अनुरोधों के बीच बदल सकता है, तो उपयोगकर्ता डुप्लिकेट, गैप, या “गायब” पंक्तियाँ देखेंगे।
सॉर्टिंग को सुझाव न मानकर एक अनुबंध बनाओ। अनुमत सॉर्ट फ़ील्ड्स और दिशाएँ प्रकाशित करो, और बाकी को रिजेक्ट करो। इससे आपका API पूर्वानुमान योग्य रहेगा और क्लाइंट्स विकास में हानिरहित दिखने वाले धीमे सॉर्ट न माँगें।
एक स्थिर सॉर्ट के लिए यूनिक टाई-ब्रेकर ज़रूरी है। अगर आप created_at से सॉर्ट करते हैं और दो रिकॉर्ड का टाइमस्टैम्प एक जैसा है, तो अंतिम सॉर्ट की-में id जोड़ें। इसके बिना डेटाबेस बराबर मानों की किसी भी क्रम में लौट सकता है।
व्यावहारिक नियम जो टिकते हैं:
- केवल इंडेक्स किए गए, स्पष्ट फ़ील्ड्स पर सॉर्ट की अनुमति दें (उदा.
created_at,updated_at,status,priority)। - हमेशा अंतिम की के रूप में एक यूनिक टाई-ब्रेकर शामिल करें (उदा.
id ASC)। - एक डिफ़ॉल्ट सॉर्ट परिभाषित रखें (उदा.
created_at DESC, id DESC) और क्लाइंट्स में इसे कंसिस्टेंट रखें। - नल मान कैसे सॉर्ट होंगे इसका दस्तावेज़ीकरण करें (उदा. तारीखों और संख्याओं के लिए “nulls last”)।
सॉर्टिंग कर्सर जनरेशन को भी नियंत्रित करती है। कर्सर को आखिरी आइटम के सॉर्ट मानों को क्रम में एन्कोड करना चाहिए, टाई-ब्रेकर सहित, ताकि अगला पेज उस tuple के “after” के लिए क्वेरी कर सके। अगर सॉर्ट बदलता है तो पुराने कर्सर अमान्य हो जाते हैं। सॉर्ट पैरामीटर कर्सर अनुबंध का हिस्सा मानें।
फ़िल्टर और टोटल बिना अनुबंध तोड़े कैसे रखें
फ़िल्टरिंग को पेजिनेशन से अलग महसूस होना चाहिए। UI बोल रही है, “मुझे एक अलग सेट दिखाओ,” और तभी वह बताती है, “उस सेट में पेज कर दो।” अगर आप फ़िल्टर फ़ील्ड को पेजिनेशन टोकन में मिला देंगे या फ़िल्टर को वैकल्पिक-और-अमान्य रखेंगे, तो आपको मुश्किल से डिबग होने वाला व्यवहार मिलेगा: खाली पेज, डुप्लिकेट, या ऐसा कर्सर जो अचानक किसी अलग डेटासेट पर इशारा करे।
एक सरल नियम: फ़िल्टर सामान्य क्वेरी पैरामीटर्स (या POST के लिए बॉडी) में रहें, और कर्सर ओपेक रहे और केवल उसी सटीक फ़िल्टर + सॉर्ट कॉम्बिनेशन के लिए मान्य हो। यदि यूजर किसी भी फ़िल्टर (status, date range, assignee) को बदलता है, क्लाइंट को पुराना कर्सर ड्रॉप करके शुरुआत से शुरू करना चाहिए।
क्या फ़िल्टर स्वीकार किए जाएँ इस पर सख्ती रखें। यह प्रदर्शन की रक्षा करती है और व्यवहार को पूर्वानुमान योग्य बनाती है:
- अज्ञात फ़िल्टर फ़ील्ड्स को रिजेक्ट करें (उन्हें चुपचाप अनदेखा न करें)।
- प्रकार और रेंज वेलिडेट करें (तिथियाँ, enums, IDs)।
- वाइड फ़िल्टरों को कैप करें (उदा. IN सूची में अधिकतम 50 IDs)।
- डेटा और टोटल्स पर वही फ़िल्टर लागू करें (कोई mismatch न हो)।
टोटल्स वहाँ अक्सर APIs को धीमा कर देते हैं। सटीक काउंट बड़े तालिकाओं पर महंगा हो सकता है, खासकर कई फ़िल्टर के साथ। सामान्यतः आपके पास तीन विकल्प हैं: सटीक, अनुमानित, या none। सटीक छोटे डेटासेट या जब उपयोगकर्ता सचमुच “showing 1-25 of 12,431” चाहता है तब अच्छा है। अनुमानित अक्सर एडमिन स्क्रीन के लिए पर्याप्त है। None तब ठीक है जब आपको केवल “Load more” चाहिए।
हर अनुरोध को धीमा करने से बचने के लिए, टोटल्स वैकल्पिक रखें: केवल क्लाइंट के पूछने पर ही गणना करें (उदा. includeTotal=true), फ़िल्टर सेट के लिए उन्हें थोड़ी देर के लिए कैश करें, या केवल पहले पेज पर टोटल लौटाएँ।
स्टेप बाई स्टेप: एंडपॉइंट डिज़ाइन और इम्प्लीमेंट
डिफ़ॉल्ट से शुरू करें। एक लिस्ट एंडपॉइंट को स्थिर सॉर्ट ऑर्डर चाहिए, साथ में एक टाई-ब्रेकर। उदाहरण: createdAt DESC, id DESC। टाई-ब्रेकर (id) नए रिकॉर्ड जोड़ने पर डुप्लिकेट और गैप रोकता है।
एक अनुरोध शेप परिभाषित करें और उसे साधारण रखें। सामान्य पैरामीटर हैं limit, cursor (या offset), sort, और filters। अगर आप दोनों मोड सपोर्ट करते हैं, तो उन्हें परस्पर असंभव बनाएं: क्लाइंट या तो cursor भेजेगा, या offset, लेकिन दोनों नहीं।
एक सुसंगत रिस्पॉन्स कॉन्ट्रैक्ट रखें ताकि वेब और मोबाइल UI एक ही लिस्ट लॉजिक शेयर कर सकें:
items: रिकॉर्ड्स का पेजnextCursor: अगले पृष्ठ के लिए कर्सर (याnull)hasMore: boolean ताकि UI तय कर सके “Load more” दिखाना है या नहींtotal: मिलते हुए रिकॉर्ड्स की कुल संख्या (nullजब तक माँगा न गया हो अगर काउंट महंगा है)
इम्प्लीमेंटेशन वह जगह है जहाँ दोनों अप्रोच अलग होती हैं।
ऑफ़सेट क्वेरी आमतौर पर ORDER BY ... LIMIT ... OFFSET ... होती हैं, जो बड़े टेबल्स पर धीमी हो सकती हैं।
कर्सर क्वेरीज़ अंतिम आइटम के आधार पर seek कंडीशंस इस्तेमाल करती हैं: “मुझे उन आइटम्स दो जहाँ (createdAt, id) आखिरी (createdAt, id) से छोटे हैं।” इससे प्रदर्शन अधिक स्थिर रहता है क्योंकि डेटाबेस इंडेक्स का उपयोग कर सकता है।
शिप करने से पहले गार्डरेल जोड़ें:
limitकैप करें (उदा. अधिकतम 100) और डिफ़ॉल्ट सेट करें।sortको allowlist के विरुद्ध वेलिडेट करें।- फ़िल्टर को प्रकार के अनुसार वेलिडेट करें और अज्ञात कीज़ रिजेक्ट करें।
cursorको ओपेक बनाएं (आखिरी सॉर्ट मान एन्कोड करें) और malformed कर्सर रिजेक्ट करें।- तय करें कि
totalकैसे माँगा जाता है।
डेटा बदलते हुए टेस्ट करें। अनुरोधों के बीच रिकॉर्ड बनाएं और डिलीट करें, सॉर्ट को प्रभावित करने वाले फ़ील्ड अपडेट करें, और सुनिश्चित करें कि आप डुप्लिकेट या गायब पंक्तियाँ नहीं देखते।
उदाहरण: टिकट्स लिस्ट जो वेब और मोबाइल पर तेज़ रहे
एक सपोर्ट टीम एक एडमिन स्क्रीन खोलती है सबसे नए टिकट रिव्यू करने के लिए। उन्हें सूची तुरंत महसूस होनी चाहिए, भले ही नए टिकट आ रहे हों और एजेंट पुराने अपडेट कर रहे हों।
वेब पर UI एक टेबल है। डिफ़ॉल्ट सॉर्ट updated_at (नवीनतम पहले) है, और टीम अक्सर Open या Pending पर फ़िल्टर करती है। वही एंडपॉइंट एक स्थिर सॉर्ट और कर्सर टोकन के साथ दोनों कार्यों को सपोर्ट कर सकता है।
GET /tickets?status=open&sort=-updated_at&limit=50&cursor=eyJ1cGRhdGVkX2F0IjoiMjAyNi0wMS0yNVQxMTo0NTo0MloiLCJpZCI6IjE2OTMifQ==
रिस्पॉन्स UI के लिए पूर्वानुमेय रहता है:
{
"items": [{"id": 1693, "subject": "Login issue", "status": "open", "updated_at": "2026-01-25T11:45:42Z"}],
"page": {"next_cursor": "...", "has_more": true},
"meta": {"total": 128}
}
मोबाइल पर वही एंडपॉइंट अनंत स्क्रॉल को पावर करता है। ऐप एक बार में 20 टिकट लोड करता है, फिर next_cursor भेजकर अगला बैच लेता है। कोई पेज-नंबर लॉजिक नहीं, और रिकॉर्ड बदलते वक्त कम सरप्राइज़।
कुंजी यह है कि कर्सर आखिरी-देखी पोज़िशन एन्कोड करता है (उदाहरण के लिए updated_at और टाई-ब्रेकर के रूप में id)। अगर कोई टिकट स्क्रॉल करते समय अपडेट होता है, तो वह अगले रिफ्रेश पर ऊपर की ओर आ सकता है, पर यह पहले से स्क्रॉल किए हुए फीड में डुप्लिकेट या गैप नहीं पैदा करेगा।
टोटल्स उपयोगी हैं, पर बड़े डेटासेट पर महंगे होते हैं। एक सरल नियम है कि meta.total केवल तब लौटाएँ जब यूजर फ़िल्टर लागू करे (जैसे status=open) या स्पष्ट रूप से माँगे।
सामान्य गलतियाँ जो डुप्लिकेट, गैप, और लैग पैदा करती हैं
ज़्यादातर पेजिनेशन बग्स डेटाबेस में नहीं हैं। वे उन छोटे API निर्णयों से आते हैं जो परीक्षण में ठीक दिखते हैं, पर अनुरोधों के बीच डेटा बदलने पर टूट जाते हैं।
डुप्लिकेट (या गायब पंक्तियों) का सबसे सामान्य कारण वह फ़ील्ड है जिस पर सॉर्ट यूनिक नहीं है। यदि आप created_at पर सॉर्ट करते हैं और दो आइटम का टाइमस्टैम्प समान है, तो ऑर्डर अनुरोधों के बीच पलट सकता है। हल सरल है: हमेशा एक स्थिर टाई-ब्रेकर जोड़ें, आमतौर पर प्राइमरी की, और सॉर्ट को एक पेयर की तरह मानें जैसे (created_at desc, id desc)।
एक और सामान्य समस्या क्लाइंट को किसी भी पेज साइज की अनुमति देना है। एक बड़ा अनुरोध CPU, मेमोरी और रिस्पॉन्स टाइम स्पाइक कर सकता है, जो हर एडमिन स्क्रीन को धीमा कर देता है। एक समझदार डिफ़ॉल्ट और एक हार्ड मैक्स चुनें, और जब क्लाइंट ज्यादा माँगे तो एरर लौटाएँ।
टोटल्स भी नुकसान पहुँचा सकते हैं। हर अनुरोध पर सभी मिलते हुए रोज़ गिनना सबसे धीमी चीज़ बन सकती है, खासकर फ़िल्टर के साथ। अगर UI को टोटल चाहिए तो केवल माँग पर लें (या अनुमान लौटाएँ), और सूची स्क्रोलिंग को पूरे काउंट पर ब्लॉक न करें।
सबसे सामान्य गलतियाँ जो गैप, डुप्लिकेट, और लैग बनाती हैं:
- यूनिक टाई-ब्रेकर के बिना सॉर्टिंग (अस्थिर क्रम)
- अनलिमिटेड पेज साइज (सर्वर ओवरलोड)
- हर बार टोटल लौटाना (धीमी क्वेरीज़)
- एक ही एंडपॉइंट में ऑफ़सेट और कर्सर नियमों को मिलाना (क्लाइंट व्यवहार उलझा देना)
- फ़िल्टर या सॉर्ट बदलने पर वही कर्सर दुबारा उपयोग करना (गलत परिणाम)
जब भी फ़िल्टर या सॉर्ट बदलें, पेजिनेशन रीसेट करें। नए फ़िल्टर को एक नई खोज मानें: कर्सर/ऑफ़सेट साफ़ करें और पहले पृष्ठ से शुरू करें।
शिप करने से पहले त्वरित चेकलिस्ट
API और UI साथ में रखकर यह एक बार चला लें। ज़्यादातर समस्याएँ लिस्ट स्क्रीन और सर्वर के बीच अनुबंध में होती हैं।
- डिफ़ॉल्ट सॉर्ट स्थिर है और यूनिक टाई-ब्रेकर शामिल है (उदा.
created_at DESC, id DESC)। - सॉर्ट फ़ील्ड्स और दिशाओं को व्हाइटलिस्ट किया गया है।
- एक अधिकतम पेज साइज़ लागू है, साथ में समझदार डिफ़ॉल्ट।
- कर्सर टोकन ओपेक हैं, और अमान्य कर्सर पूर्वानुमेय तरीके से फ़ेल करते हैं।
- किसी भी फ़िल्टर या सॉर्ट परिवर्तन पर पेजिनेशन स्टेट रीसेट होता है।
- टोटल्स का व्यवहार स्पष्ट है: सटीक, अनुमानित, या छोड़ा गया।
- वही अनुबंध टेबल और अनंत स्क्रॉल दोनों को बिना स्पेशल केस के सपोर्ट करता है।
अगले कदम: अपनी सूचियों को स्टैंडर्डाइज़ करें और स्थिर रखें
एक ऐसा एडमिन लिस्ट चुनें जिसे लोग हर दिन उपयोग करते हैं और उसे अपना गोल्ड स्टैंडर्ड बनाइए। एक व्यस्त टेबल जैसे Tickets, Orders, या Users अच्छी शुरुआत है। एक बार जब वह एंडपॉइंट तेज़ और पूर्वानुमेय हो जाए, तो वही अनुबंध बाकी एडमिन स्क्रीन पर कॉपी करें।
अनुबंध लिखकर रखें, भले ही संक्षेप में ही क्यों न हो। स्पष्ट रूप से परिभाषित करें कि API क्या स्वीकार करता है और क्या लौटाता है ताकि UI टीम अनुमान न लगाए और गलती से हर एंडपॉइंट के लिए अलग नियम आविष्कार न कर दे।
हर लिस्ट एंडपॉइंट पर लागू करने के लिए एक सरल मानक:
- Allowed sorts: सटीक फील्ड नाम, दिशा, और एक स्पष्ट डिफ़ॉल्ट (साथ में
idजैसा टाई-ब्रेकर)। - Allowed filters: कौन से फील्ड फ़िल्टर हो सकते हैं, मानों का फॉर्मेट, और अमान्य फ़िल्टर पर क्या होगा।
- Totals behavior: कब आपने काउंट लौटाया, कब “unknown” लौटाते हैं, और कब छोड़ देते हैं।
- Response shape: सुसंगत कुंजियाँ (
items, पेजिंग info, applied sort/filters, totals)। - Error rules: सुसंगत स्टेटस कोड और पठनीय वेलिडेशन संदेश।
अगर आप AppMaster (appmaster.io) के साथ ये एडमिन स्क्रीन बना रहे हैं, तो पेजिनेशन अनुबंध को जल्दी स्टैंडर्डाइज़ करने से लाभ मिलता है। आप वही लिस्ट व्यवहार वेब ऐप और नेटिव मोबाइल ऐप दोनों में reuse कर सकेंगे और बाद में पेजिनेशन एज केस्स पर कम समय बिताएँगे।
सामान्य प्रश्न
Offset pagination limit और offset (या page/pageSize) का उपयोग करके पंक्तियों को छोड़ता है, इसलिए गहरे पन्नों पर डेटाबेस को अधिक रिकॉर्ड्स पार करना पड़ता है और वह अक्सर धीमा हो जाता है। Cursor पेजिनेशन पिछले आइटम के सॉर्ट मानों पर आधारित after टोकन का उपयोग करता है, इसलिए यह एक ज्ञात स्थान से आगे कूद कर तेज़ बनी रहती है।
क्योंकि पेज 1 आमतौर पर सस्ता होता है, लेकिन पेज 200 डेटाबेस से बहुत सारी पंक्तियों को स्किप करने को कहता है। यदि आप सॉर्ट और फ़िल्टर भी लगा रहे हैं, तो काम और बढ़ जाता है, इसलिए हर क्लिक भारी क्वेरी जैसा लगने लगता है।
हमेशा एक स्थिर सॉर्ट उपयोग करें जिसमें एक यूनिक टाई-ब्रेकर हो, जैसे created_at DESC, id DESC या updated_at DESC, id DESC. बिना टाई-ब्रेकर के वही टाइमस्टैम्प वाले रिकॉर्ड्स अनुरोधों के बीच क्रम बदल सकते हैं और डुप्लिकेट या गायब पंक्तियाँ दिखा सकते हैं।
कर्सर पेजिनेशन उन सूचियों के लिए चुनें जहाँ लोग ज़्यादातर आगे बढ़ते हैं और गति महत्वपूर्ण है — जैसे गतिविधि लॉग, टिकट, ऑर्डर और मोबाइल अनंत स्क्रॉल। यह नए रिकॉर्ड जोड़ने या हटाने पर भी अधिक स्थिर रहता है क्योंकि कर्सर अगले पृष्ठ को एक सटीक अंतिम-दिखे स्थान से एंकर करता है।
जब UI में “पेज N पर जाओ” असली फ़ीचर हो और उपयोगकर्ता अक्सर बीच-बीच में कूदते हों। यह छोटे टेबल या स्थिर डेटासेट के लिए भी सुविधाजनक है, जहाँ गहरा-पेज धीमा होना और परिणामों का स्थानांतरण महत्वहीन होता है।
एक समान उत्तर संरचना रखें और items, पेजिंग स्थिति, और वैकल्पिक टोटल्स शामिल करें। प्रायोगिक डिफ़ॉल्ट: items, एक page ऑब्जेक्ट (जिसमें limit, nextCursor/prevCursor या offset हो), और hasNext जैसी एक हल्की फ्लैग ताकि वेब टेबल और मोबाइल सूची समान क्लाइंट लॉजिक reuse कर सकें।
क्योंकि बड़े, फ़िल्टर्ड डेटासेट पर सटीक COUNT(*) हर अनुरोध को धीमा कर सकता है और हर पेज बदलने को लैगी बना सकता है। एक सुरक्षित डिफ़ॉल्ट यह है कि टोटल्स वैकल्पिक हों: केवल जब क्लाइंट पूछे तब दें, या सिर्फ has_more लौटाएँ जब UI को केवल “Load more” चाहिये।
फ़िल्टर को डेटासेट का हिस्सा मानें, और कर्सर को केवल उसी सटीक फ़िल्टर और सॉर्ट कॉम्बिनेशन के लिए मान्य मानें। यदि उपयोगकर्ता कोई फ़िल्टर या सॉर्ट बदलता है, तो पेजिनेशन रीसेट करें और पहले पृष्ठ से शुरू करें; पुराने कर्सर को दुबारा उपयोग करने से खाली पेज या भ्रमजनक परिणाम मिलते हैं।
सॉर्ट फ़ील्ड और दिशाओं को व्हाइटलिस्ट करें और बाकी को रिजेक्ट करें ताकि क्लाइंट गलती से धीमा या अस्थिर ऑर्डर न माँग सकें। इंडेक्स वाले फ़ील्ड पर सॉर्ट करना बेहतर है और हमेशा एक यूनिक टाई-ब्रेकर जैसे id जोड़ें, ताकि अनुरोधों के बीच क्रम निर्धारक रहे।
एक अधिकतम limit लागू करें, फ़िल्टर और सॉर्ट पैरामीटर वेलिडेट करें, और कर्सर टोकन को अपारदर्शी (opaque) और सख्ती से सत्यापित रखें। अगर आप AppMaster में एडमिन स्क्रीन बना रहे हैं, तो इन नियमों को सभी लिस्ट एंडपॉइंट्स पर लागू करने से हर स्क्रीन पर अलग पेजिनेशन बग्स कम होंगे।


