Cursor Pagination মোবাইল ডেভেলপমেন্টে — এটি কী, নীতি এবং বাস্তবায়ন

লেখক: IT Sectr প্রকাশিত: 2026-03-11 পড়ার সময়: 9 মিনিট

Cursor Pagination হল একটি পৃষ্ঠাভিত্তিক ডেটা লোডিং পদ্ধতি যা একটি সুশৃঙ্খল রেকর্ড সেটে নেভিগেট করার জন্য একটি অনন্য কার্সার ব্যবহার করে। GraphQL Specification (2025) অনুসারে, কার্সার-ভিত্তিক পেজিনেশন গতিশীল ডেটার সাথে কাজ করা API-গুলির জন্য প্রস্তাবিত মান। Cursor pagination Offset পদ্ধতির প্রধান ত্রুটিগুলি দূর করে: সন্নিবেশের সময় অস্থিরতা এবং বড় অফসেটে কর্মক্ষমতার অবনতি।

মূল পয়েন্ট

  • Cursor Pagination একটি পেজিনেশন পদ্ধতি যেখানে প্রতিটি রেকর্ডে নেভিগেশনের জন্য একটি অনন্য কার্সার শনাক্তকারী থাকে।
  • কার্সার একটি ডেটা সেটে একটি অনন্য অবস্থান চিহ্নিতকারী (সাধারণত ID, UUID, timestamp) যা সন্নিবেশের সময় পরিবর্তিত হয় না।
  • স্থিতিশীলতা — অনুরোধের মধ্যে যুক্ত নতুন রেকর্ডগুলি কার্সারকে সরিয়ে দেয় না, ডুপ্লিকেট এবং ফাঁক দূর করে।
  • কর্মক্ষমতা — WHERE id > cursor কোয়েরি বড় ডেটাসেটে গতি না হারিয়ে কার্যকরভাবে ইনডেক্স ব্যবহার করে।
  • সীমাবদ্ধতা — কার্সার পেজিনেশন পৃষ্ঠা নম্বর দ্বারা নেভিগেশন সমর্থন করে না (আপনি পৃষ্ঠা 5-এ যেতে পারবেন না)।

Cursor Pagination কী?

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)-এ কার্সারের পরে প্রথম রেকর্ড খুঁজে পায়, যা স্থিতিশীল প্রতিক্রিয়া সময় প্রদান করে।

sql
-- কার্সার '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 কার্সারের আগে রেকর্ড নেয়।

Cursor বনাম Offset পেজিনেশন

কার্সার এবং Offset পেজিনেশনের মধ্যে নির্বাচন একটি API ডিজাইন করার সময় মূল স্থাপত্য সিদ্ধান্তগুলির মধ্যে একটি। প্রতিটি পদ্ধতির শক্তি এবং দুর্বলতা রয়েছে যা এর প্রযোজ্যতা নির্ধারণ করে। Cursor pagination গতিশীল ডেটার পরিস্থিতিতে জয়ী হয়; Offset নির্বিচার নেভিগেশনের পরিস্থিতিতে।

বৈশিষ্ট্যCursorOffset
সন্নিবেশে স্থিতিশীলতাউচ্চ (কোনও ডুপ্লিকেট নয়)নিম্ন (পৃষ্ঠা সরণ)
বড় ডেটাসেটে কর্মক্ষমতাO(log n) — স্থিতিশীলO(n) — বৃদ্ধির সাথে হ্রাস পায়
পৃষ্ঠা নম্বর দ্বারা নেভিগেশননাহ্যাঁ (page=5)
বাস্তবায়ন জটিলতামধ্যমনিম্ন
REST সমর্থনcursor/before/afterpage/offset
GraphQL সমর্থনRelay মানসুপারিশকৃত নয়

কেন Offset বড় পরিসরে ব্যর্থ হয়

Offset পেজিনেশন OFFSET অবস্থান পর্যন্ত সম্পূর্ণ টেবিল স্ক্যান করে। offset=100000-এ, ডেটাবেস 100000 সারি পড়ে এবং এড়িয়ে যায়, এমনকি LIMIT 20 হলেও। MySQL এবং PostgreSQL OFFSET অপটিমাইজ করতে পারে না — এটি SQL-এ LIMIT/OFFSET বাস্তবায়নের একটি বৈশিষ্ট্য। কার্সার পেজিনেশন B-tree ইনডেক্স ব্যবহার করে যা O(log n)-এ অবস্থান খুঁজে পায়।

একটি অতিরিক্ত Offset সমস্যা হল পিছনের দিকে পৃষ্ঠাভুক্ত করার সময় রেকর্ড “বাদ দেওয়া”। যদি একজন ব্যবহারকারী পৃষ্ঠা 5 লোড করে এবং সেই মুহূর্তে নতুন রেকর্ড যুক্ত করা হয়, পৃষ্ঠা 6-এর অনুরোধ করলে তারা পৃষ্ঠা 5-এর রেকর্ড আবার দেখবে বা নতুনগুলি মিস করবে। Cursor pagination এই পরিস্থিতি সম্পূর্ণরূপে দূর করে: কার্সার সেটের একটি নির্দিষ্ট স্থান নির্দেশ করে এবং সন্নিবেশ অবস্থান পরিবর্তন করে না।

Cursor Pagination বাস্তবায়ন

আসুন ব্যাকএন্ড (Kotlin + Spring) এবং ক্লায়েন্টে (Android + Retrofit) কার্সার পেজিনেশন বাস্তবায়ন দেখি। সার্ভার after, before, limit প্যারামিটার গ্রহণ করে এবং কার্সার ও pageInfo-সহ রেকর্ডের একটি তালিকা ফেরত দেয়। একটি সাধারণ প্রতিক্রিয়ায় পেজিনেশন UI পরিচালনার জন্য hasNextPage এবং hasPreviousPage থাকে।

kotlin
@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
        )
    )
}

Android-এ ক্লায়েন্ট বাস্তবায়ন

ক্লায়েন্টে, কার্সার পেজিনেশন Paging 3 থেকে PagingSource-এর মাধ্যমে বাস্তবায়িত হয়, যেখানে কী একটি কার্সার (Long)। PagingSource.load LoadParams.key গ্রহণ করে — শেষ লোড করা রেকর্ডের কার্সার। LoadResult.Page ডেটা এবং nextKey — পরবর্তী পৃষ্ঠার জন্য কার্সার — ফেরত দেয়। যখন nextKey = null, পেজিনেশন সম্পূর্ণ হয়।

kotlin
// 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)
    }
}

Relay-এর মাধ্যমে GraphQL বাস্তবায়ন

GraphQL-এ, কার্সার পেজিনেশন Relay Connection প্যাটার্নের মাধ্যমে বাস্তবায়িত হয়। প্রতিটি প্রকারের একটি Connection (pageInfo এবং edges-সহ) এবং একটি Edge (node + cursor) থাকে। কোয়েরিটি first, after, last, before প্যারামিটার পাস করে। সার্ভার ফেরত দেয় কার্সার-সহ edges-এর একটি অ্যারে এবং hasNextPage/hasPreviousPage-সহ pageInfo।

কখন Cursor Pagination ব্যবহার করবেন

কার্সার পেজিনেশন গতিশীল ডেটা নিয়ে কাজ করা API-গুলির জন্য সুপারিশ করা হয় যেখানে রেকর্ড ঘন ঘন যোগ বা মুছে ফেলা হয়। ক্লাসিক উদাহরণ: সোশ্যাল নেটওয়ার্কে নিউজ ফিড, চ্যাট বার্তা, লেনদেন ইতিহাস, পোস্ট মন্তব্য। এই সমস্ত পরিস্থিতিতে, ধারাবাহিকতা এবং ডুপ্লিকেটের অনুপস্থিতি গুরুত্বপূর্ণ।

  • চ্যাট এবং মেসেঞ্জার — প্রতিটি নতুন বার্তা তালিকার শীর্ষে যুক্ত হয়। Offset পেজিনেশন প্রতিটি নতুন বার্তার সাথে ব্যাহত হয়।
  • সোশ্যাল নেটওয়ার্ক এবং ফিড — পোস্টগুলি ক্রমাগত প্রকাশিত হয়। কার্সার পেজিনেশন নিশ্চিত করে যে ব্যবহারকারী একটি পোস্টও মিস না করে।
  • অর্ডার এবং লেনদেন ইতিহাস — ডেটা কম ঘন ঘন পরিবর্তিত হয়, কিন্তু আর্থিক রিপোর্টিংয়ের জন্য ধারাবাহিকতা গুরুত্বপূর্ণ।
  • বৃহৎ ডেটা ভলিউমের API — লক্ষ লক্ষ রেকর্ড। Cursor pagination কর্মক্ষমতা বজায় রাখে যেখানে Offset ধীর হতে শুরু করে।
  • GraphQL APIs — Relay মান স্পেসিফিকেশন মেনে চলার জন্য কার্সার-ভিত্তিক পেজিনেশন প্রয়োজন।
  • অন্তহীন স্ক্রলযুক্ত মোবাইল অ্যাপ — ব্যবহারকারী নিচে স্ক্রল করে নতুন ব্যাচ লোড করে। কার্সার পদ্ধতি ডুপ্লিকেট ছাড়া একটি মসৃণ UX প্রদান করে।

কখন কার্সার পেজিনেশন উপযুক্ত নয়

এমন পরিস্থিতি রয়েছে যেখানে Offset পেজিনেশন বেশি সুবিধাজনক: অ্যাডমিন প্যানেল যেখানে পৃষ্ঠা নম্বর দ্বারা নেভিগেশন প্রয়োজন; পেজিনেশনসহ অনুসন্ধান যেখানে ফলাফল পরিবর্তিত হতে পারে; রিপোর্ট এবং বিশ্লেষণ যেখানে পৃষ্ঠা 5-এর একটি নির্দিষ্ট লিঙ্ক প্রয়োজন। এই ক্ষেত্রে, কার্সারের সুবিধাগুলি বাস্তবায়ন জটিলতাকে অতিক্রম করে না।

কার্সার পেজিনেশন একটি নির্বিচার পৃষ্ঠায় “ঝাঁপ দেওয়া” সমর্থন করে না — ব্যবহারকারী “পৃষ্ঠা 5” ক্লিক করে সেখানে যেতে পারে না। এটি একটি স্থাপত্য সীমাবদ্ধতা: মোট পৃষ্ঠার সংখ্যা গণনা করার জন্য একটি পৃথক COUNT কোয়েরি প্রয়োজন, যা বড় টেবিলের জন্য ব্যয়বহুল হতে পারে। এই ধরনের ক্ষেত্রে, একটি হাইব্রিড পদ্ধতি: ডেটার জন্য কার্সার + পেজিনেশনের জন্য count।

সচরাচর জিজ্ঞাসিত প্রশ্ন

Cursor Pagination-এ কার্সার কী?

একটি কার্সার একটি অনন্য রেকর্ড শনাক্তকারী যা ডেটা সেটের একটি অবস্থান নির্দেশ করে। এটি সরল (একটি রেকর্ড ID) বা যৌগিক (একাধিক ক্ষেত্র) হতে পারে। ক্লায়েন্ট পৃষ্ঠার শেষ রেকর্ডের কার্সার পায় এবং পরবর্তী ব্যাচ পেতে পরবর্তী অনুরোধে এটি পাস করে।

কেন Cursor Pagination Offset-এর চেয়ে ভাল?

Cursor pagination সরণের অধীন নয় যখন নতুন রেকর্ড যুক্ত করা হয় — প্রতিটি উপাদান ঠিক একটি পৃষ্ঠায় পড়ে। এটি প্রথম n সারি স্ক্যান না করে ইনডেক্স ব্যবহার করে বড় ভলিউমে গতি বজায় রাখে। Offset সহজ কিন্তু গতিশীল ডেটার জন্য অস্থির।

GraphQL ছাড়া কি কার্সার পেজিনেশন বাস্তবায়ন করা যায়?

হ্যাঁ, কার্সার পেজিনেশন GraphQL-এর সাথে আবদ্ধ নয়। এটি যেকোনো REST API-তে কার্সারকে কোয়েরি প্যারামিটার ?after=83&limit=20 হিসেবে পাস করে বাস্তবায়ন করা যেতে পারে। প্রতিক্রিয়ায় endCursor এবং hasNextPage-সহ pageInfo থাকা উচিত — এটি ক্লায়েন্টকে অভ্যন্তরীণ কার্সার কাঠামো না জেনে লোডিং পরিচালনা করতে দেয়।

কোন কার্সার ব্যবহার করবেন — ID, UUID নাকি timestamp?

অটো-ইনক্রিমেন্ট ID সর্বোত্তম পছন্দ: একঘেয়েভাবে বৃদ্ধি পায়, পরিবর্তিত হয় না, কার্যকরভাবে ইনডেক্স করা হয়। UUID v7 (সময়-ক্রমিত)ও কাজ করে। টাইমস্ট্যাম্প একই সময়ে ডুপ্লিকেট তৈরি করতে পারে, তাই এটি ID-র সাথে একত্রিত করুন: (created_at, id) কার্সার অনন্যতার নিশ্চয়তার জন্য।

কার্সার পেজিনেশনের সাথে মোট পৃষ্ঠার সংখ্যা কীভাবে খুঁজে পাবেন?

কার্সার পেজিনেশন মোট পৃষ্ঠার সংখ্যা প্রদান করে না — এটি তার সীমাবদ্ধতা। আপনার যদি মোট তথ্যের প্রয়োজন হয়, তবে একই ফিল্টার সহ একটি পৃথক COUNT কোয়েরি কার্যকর করুন। বড় টেবিলের জন্য, EXPLAIN-এর মাধ্যমে আনুমানিক গণনা বা বিশ্লেষণ থেকে ক্যাশেড মোট ব্যবহার করুন।

সারসংক্ষেপ

  • Cursor Pagination একটি পেজিনেশন পদ্ধতি যেখানে অফসেটের পরিবর্তে অনন্য রেকর্ড শনাক্তকারী দ্বারা নেভিগেশন হয়।
  • কার্সার সন্নিবেশে সেট স্থিতিশীলতার নিশ্চয়তা দেয়: নতুন রেকর্ড ইতিমধ্যে লোড করা পৃষ্ঠাগুলিকে সরিয়ে দেয় না।
  • কর্মক্ষমতা বড় ডেটা ভলিউমে B-tree ইনডেক্স ব্যবহারের কারণে উচ্চ (O(log n)) থাকে।
  • Cursor pagination গতিশীল ডেটার জন্য উপযুক্ত: চ্যাট, নিউজ ফিড, লেনদেন, মন্তব্য।
  • প্রধান সীমাবদ্ধতা হল পৃষ্ঠা নম্বর দ্বারা নেভিগেশনের অভাব এবং নির্বিচার পৃষ্ঠায় ঝাঁপ দিতে অক্ষমতা।
  • বাস্তবায়ন কার্সারের পরে WHERE id, after/before প্যারামিটার এবং প্রতিক্রিয়ায় pageInfo ব্যবহার করে।
  • মান — Relay Connection GraphQL, কিন্তু কার্সার প্যারামিটার সহ REST APIs-ও ব্যাপকভাবে ব্যবহৃত হয়।

আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব

IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।

প্রকল্প নিয়ে আলোচনা করুন

আরও পড়ুন