Cursor Pagination एक क्रमबद्ध रिकॉर्ड सेट में नेविगेट करने के लिए एक अद्वितीय कर्सर का उपयोग करके डेटा को पृष्ठांकित रूप से लोड करने की एक विधि है। GraphQL Specification (2025) के अनुसार, कर्सर-आधारित पेजिनेशन गतिशील डेटा के साथ काम करने वाले API के लिए अनुशंसित मानक है। Cursor pagination Offset दृष्टिकोण की मुख्य कमियों को समाप्त करता है: इंसर्शन के दौरान अस्थिरता और बड़े ऑफसेट पर प्रदर्शन में गिरावट।
मुख्य बिंदु
Cursor Pagination एक पृष्ठांकित लोडिंग विधि है जिसमें सर्वर डेटा के साथ एक विशेष संकेतक — कर्सर — लौटाता है। क्लाइंट अगले अनुरोध में इस कर्सर का उपयोग रिकॉर्ड के अगले बैच को प्राप्त करने के लिए करता है। कर्सर वर्तमान पृष्ठ के अंतिम तत्व का एक अद्वितीय पहचानकर्ता है।
Offset पेजिनेशन के विपरीत, जहाँ क्लाइंट कहता है “मुझे 20 रिकॉर्ड के साथ पृष्ठ 5 दें,” कर्सर पेजिनेशन अलग तरीके से काम करता है: “मुझे ID = 83 वाले रिकॉर्ड के बाद 20 रिकॉर्ड दें।” सर्वर WHERE id > 83 और LIMIT 20 के साथ एक क्वेरी निष्पादित करता है। यह दृष्टिकोण गारंटी देता है कि प्रत्येक रिकॉर्ड इंसर्शन की परवाह किए बिना ठीक एक पृष्ठ में आता है।
कर्सर पेजिनेशन की अवधारणा को Relay Connection (GraphQL) विनिर्देश के कारण व्यापक रूप से अपनाया गया, जिसने कर्सर-आधारित पेजिनेशन को आधुनिक API के लिए मानक बना दिया। Relay प्रतिक्रिया प्रारूप को परिभाषित करता है: edges (कर्सर के साथ रिकॉर्ड की सरणी), pageInfo (hasNextPage, hasPreviousPage, startCursor, endCursor)।
कर्सर पेजिनेशन कोई नई तकनीक नहीं है — इसका उपयोग वेब से बहुत पहले डेटाबेस में किया जाता था। SQL में इसे keyset pagination या seek method कहा जाता है। यह विधि 2015 में Relay विनिर्देश के प्रकाशन के बाद API में लोकप्रिय हुई, जिसने HTTP ट्रांसपोर्ट पर एकरूपता के लिए कर्सर प्रारूप को base64-एन्कोडेड स्ट्रिंग के रूप में औपचारिक रूप दिया।
कर्सर पेजिनेशन का मूल सिद्धांत यह है कि क्वेरी स्थिति निर्धारण के लिए ऑफ़सेट के बजाय एक अनुक्रमित फ़ील्ड पर WHERE शर्त का उपयोग करती है। आगे की दिशा के लिए WHERE id > last_id का उपयोग किया जाता है; पीछे की दिशा के लिए WHERE id < first_id का। B-tree इंडेक्स कर्सर के बाद पहला रिकॉर्ड O(log n) में ढूँढता है, जो स्थिर प्रतिक्रिया समय प्रदान करता है।
-- कर्सर '83' के बाद 20 रिकॉर्ड प्राप्त करें
SELECT id, title, created_at
FROM posts
WHERE id < 83
ORDER BY id DESC
LIMIT 20;
-- कर्सर '83' से पहले 20 रिकॉर्ड प्राप्त करें (पीछे)
SELECT id, title, created_at
FROM posts
WHERE id > 83
ORDER BY id ASC
LIMIT 20;
कर्सर सरल (एक ID मान) या जटिल (कई फ़ील्ड से संयुक्त) हो सकता है। सरल कर्सर रिकॉर्ड की प्राथमिक कुंजी होते हैं, उदाहरण के लिए, ऑटो-इंक्रीमेंट id या UUID। संयुक्त कर्सर का उपयोग गैर-अद्वितीय फ़ील्ड द्वारा सॉर्टिंग के लिए किया जाता है, उदाहरण के लिए (created_at, id), जहाँ id समान टाइमस्टैम्प होने पर विशिष्टता सुनिश्चित करता है।
एक विशिष्ट API प्रारूप base64-एन्कोडेड स्ट्रिंग के रूप में कर्सर है। सर्वर कर्सर को डीकोड करता है, मान निकालता है, और SQL क्वेरी बनाता है। Base64 एन्कोडिंग कर्सर की आंतरिक संरचना को क्लाइंट से छिपाती है और पिछड़ी संगतता को तोड़े बिना प्रारूप बदलने की अनुमति देती है। क्लाइंट प्रतिक्रिया के endCursor फ़ील्ड में कर्सर प्राप्त करता है और उन्हें अगले अनुरोध में स्ट्रिंग के रूप में पास करता है।
कर्सर पेजिनेशन द्विदिश नेविगेशन का समर्थन करता है। आगे की गति (next) के लिए, वर्तमान पृष्ठ के अंतिम तत्व के कर्सर का उपयोग किया जाता है; पीछे (previous) के लिए, पहले तत्व के कर्सर का। अनुरोध में after और before पैरामीटर दिशा निर्धारित करते हैं: after कर्सर के बाद रिकॉर्ड लेता है, before कर्सर से पहले रिकॉर्ड लेता है।
कर्सर और Offset पेजिनेशन के बीच चयन API डिज़ाइन करते समय प्रमुख वास्तुशिल्प निर्णयों में से एक है। प्रत्येक विधि में ताकत और कमज़ोरियाँ हैं जो इसकी प्रयोज्यता निर्धारित करती हैं। Cursor pagination गतिशील डेटा वाले परिदृश्यों में जीतता है; Offset मनमाना नेविगेशन वाले परिदृश्यों में।
| विशेषता | Cursor | Offset |
|---|---|---|
| इंसर्शन पर स्थिरता | उच्च (कोई डुप्लिकेट नहीं) | निम्न (पृष्ठ स्थानांतरण) |
| बड़े डेटासेट पर प्रदर्शन | O(log n) — स्थिर | O(n) — वृद्धि के साथ घटता है |
| पृष्ठ संख्या द्वारा नेविगेशन | नहीं | हाँ (page=5) |
| कार्यान्वयन जटिलता | मध्यम | निम्न |
| REST समर्थन | cursor/before/after | page/offset |
| GraphQL समर्थन | Relay मानक | अनुशंसित नहीं |
Offset पेजिनेशन OFFSET स्थिति तक पूर्ण तालिका स्कैन करता है। offset=100000 पर, डेटाबेस 100000 पंक्तियाँ पढ़ता और छोड़ता है, भले ही LIMIT 20 हो। MySQL और PostgreSQL OFFSET को अनुकूलित नहीं कर सकते — यह SQL में LIMIT/OFFSET कार्यान्वयन की एक विशेषता है। कर्सर पेजिनेशन B-tree इंडेक्स का उपयोग करता है जो O(log n) में स्थिति ढूँढता है।
एक अतिरिक्त Offset समस्या पीछे की ओर पेजिनेट करते समय रिकॉर्ड “छोड़ना” है। यदि किसी उपयोगकर्ता ने पृष्ठ 5 लोड किया और उसी समय नए रिकॉर्ड जोड़े गए, तो पृष्ठ 6 का अनुरोध करने पर वे पृष्ठ 5 के रिकॉर्ड को फिर से देखेंगे या नए को खो देंगे। Cursor pagination इस परिदृश्य को पूरी तरह से समाप्त करता है: कर्सर सेट में एक विशिष्ट स्थान को इंगित करता है, और इंसर्शन स्थिति नहीं बदलते हैं।
आइए बैकएंड (Kotlin + Spring) और क्लाइंट (Android + Retrofit) पर कर्सर पेजिनेशन कार्यान्वयन देखें। सर्वर after, before, limit पैरामीटर स्वीकार करता है और कर्सर और pageInfo के साथ रिकॉर्ड की सूची लौटाता है। एक विशिष्ट प्रतिक्रिया में पेजिनेशन UI प्रबंधित करने के लिए hasNextPage और hasPreviousPage होते हैं।
@GetMapping("/posts")
fun getPosts(
@RequestParam after: Long?,
@RequestParam(defaultValue = "20") limit: Int
): CursorResponse<Post> {
val cursor = after ?: Long.MAX_VALUE
val posts = repository.findByIdLessThanOrderByIdDesc(
cursor, PageRequest.of(0, limit)
)
val endCursor = posts.lastOrNull()?.id
return CursorResponse(
data = posts,
pageInfo = PageInfo(
hasNextPage = posts.size == limit,
endCursor = endCursor
)
)
}
क्लाइंट पर, कर्सर पेजिनेशन Paging 3 से PagingSource के माध्यम से कार्यान्वित किया जाता है, जहाँ कुंजी एक कर्सर (Long) है। PagingSource.load LoadParams.key प्राप्त करता है — अंतिम लोड किए गए रिकॉर्ड का कर्सर। LoadResult.Page डेटा और nextKey — अगले पृष्ठ के लिए कर्सर — लौटाता है। जब nextKey = null, पेजिनेशन पूरा हो जाता है।
// Retrofit API
interface PostApi {
@GET("posts")
suspend fun getPosts(
@Query("after") after: Long?,
@Query("limit") limit: Int = 20
): CursorResponse<Post>
}
// कर्सर कुंजी के साथ PagingSource
class PostPagingSource(
private val api: PostApi
) : PagingSource<Long, Post>() {
override suspend fun load(
params: LoadParams<Long>
): LoadResult<Long, Post> = try {
val response = api.getPosts(
after = params.key,
limit = params.loadSize
)
val nextKey = response.pageInfo.endCursor
LoadResult.Page(
data = response.data,
prevKey = null,
nextKey = nextKey
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
GraphQL में, कर्सर पेजिनेशन Relay Connection पैटर्न के माध्यम से कार्यान्वित किया जाता है। प्रत्येक प्रकार में एक Connection (pageInfo और edges के साथ) और एक Edge (node + cursor) होता है। क्वेरी first, after, last, before पैरामीटर पास करती है। सर्वर लौटाता है कर्सर के साथ edges की एक सरणी और hasNextPage/hasPreviousPage के साथ pageInfo।
कर्सर पेजिनेशन गतिशील डेटा के साथ काम करने वाले API के लिए अनुशंसित है जहाँ रिकॉर्ड बार-बार जोड़े या हटाए जाते हैं। उत्कृष्ट उदाहरण: सोशल नेटवर्क में समाचार फ़ीड, चैट संदेश, लेन-देन इतिहास, पोस्ट टिप्पणियाँ। इन सभी परिदृश्यों में, संगति और डुप्लिकेट की अनुपस्थिति महत्वपूर्ण है।
ऐसे परिदृश्य हैं जहाँ Offset पेजिनेशन अधिक सुविधाजनक है: एडमिन पैनल जहाँ पृष्ठ संख्या द्वारा नेविगेशन की आवश्यकता है; पेजिनेशन के साथ खोज जहाँ परिणाम बदल सकते हैं; रिपोर्ट और विश्लेषण जहाँ पृष्ठ 5 के लिए एक निश्चित लिंक की आवश्यकता है। इन मामलों में, कर्सर के लाभ कार्यान्वयन जटिलता से अधिक नहीं होते हैं।
कर्सर पेजिनेशन एक मनमाना पृष्ठ पर “कूदने” का समर्थन नहीं करता — उपयोगकर्ता “पृष्ठ 5” पर क्लिक करके वहाँ नहीं जा सकता। यह एक वास्तुशिल्प सीमा है: पृष्ठों की कुल संख्या गिनने के लिए एक अलग COUNT क्वेरी की आवश्यकता होती है, जो बड़ी तालिकाओं के लिए महँगी हो सकती है। ऐसे मामलों में, एक हाइब्रिड दृष्टिकोण: डेटा के लिए कर्सर + पेजिनेशन के लिए count।
अक्सर पूछे जाने वाले प्रश्न
कर्सर एक अद्वितीय रिकॉर्ड पहचानकर्ता है जो डेटा सेट में एक स्थान को इंगित करता है। यह सरल (एक रिकॉर्ड ID) या संयुक्त (कई फ़ील्ड) हो सकता है। क्लाइंट पृष्ठ पर अंतिम रिकॉर्ड का कर्सर प्राप्त करता है और अगला बैच प्राप्त करने के लिए इसे अगले अनुरोध में पास करता है।
Cursor pagination स्थानांतरण के अधीन नहीं है जब नए रिकॉर्ड जोड़े जाते हैं — प्रत्येक तत्व ठीक एक पृष्ठ में आता है। यह पहली n पंक्तियों को स्कैन करने के बजाय इंडेक्स का उपयोग करके बड़े वॉल्यूम पर गति भी बनाए रखता है। Offset सरल है लेकिन गतिशील डेटा के लिए अस्थिर है।
हाँ, कर्सर पेजिनेशन GraphQL से बंधा नहीं है। इसे किसी भी REST API में कर्सर को क्वेरी पैरामीटर ?after=83&limit=20 के रूप में पास करके लागू किया जा सकता है। प्रतिक्रिया में endCursor और hasNextPage के साथ pageInfo होना चाहिए — यह क्लाइंट को आंतरिक कर्सर संरचना को जाने बिना लोडिंग प्रबंधित करने की अनुमति देता है।
ऑटो-इंक्रीमेंट ID सबसे अच्छा विकल्प है: एकरस रूप से बढ़ता है, बदलता नहीं है, कुशलतापूर्वक अनुक्रमित होता है। UUID v7 (समय-क्रमबद्ध) भी काम करता है। टाइमस्टैम्प एक ही समय में डुप्लिकेट उत्पन्न कर सकते हैं, इसलिए इसे ID के साथ जोड़ें: (created_at, id) कर्सर विशिष्टता की गारंटी के लिए।
कर्सर पेजिनेशन पृष्ठों की कुल संख्या प्रदान नहीं करता — यह इसकी सीमा है। यदि आपको कुल जानकारी चाहिए, तो समान फ़िल्टर के साथ एक अलग COUNT क्वेरी निष्पादित करें। बड़ी तालिकाओं के लिए, EXPLAIN के माध्यम से अनुमानित गणना या एनालिटिक्स से कैश्ड कुल का उपयोग करें।
सारांश
हम एक मोबाइल एप्लिकेशन टर्नकी विकसित करेंगे
IT Sectr 2017 से स्टार्टअप और व्यवसायों के लिए iOS और Android एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें