Offset Pagination در توسعه موبایل: چیست و چگونه پیاده‌سازی کنیم

نویسنده: IT Sectr منتشر شده: 2026-03-11 زمان مطالعه: 10 دقیقه

Offset Pagination — صفحه‌بندی با افست — روش بارگذاری صفحه‌بندی شده داده‌ها از طریق HTTP API است. کلاینت پارامترهای offset (افست از ابتدا) و limit (اندازه صفحه) را ارسال می‌کند و سرور رکوردها را از موقعیت offset برمی‌گرداند. به گفته REST API Tutorial، این رویکرد به دلیل سادگی پیاده‌سازی به طور گسترده در سرویس‌های RESTful استفاده می‌شود. با این حال، در حجم‌های زیاد داده، صفحه‌بندی با افست به دلیل اسکن کامل جدول تا موقعیت مورد نظر عملکرد خود را از دست می‌دهد.

نکات اصلی

  • Offset Pagination — روش صفحه‌بندی که در آن سرور N رکورد را رد می‌کند و M رکورد بعدی را برمی‌گرداند.
  • سادگی پیاده‌سازی آن را به استانداردی برای REST API و کلاینت‌های موبایل تبدیل کرده است.
  • مشکل پرش — هنگام درج رکوردها بین درخواست‌ها، کاربر تکراری‌ها را می‌بیند.
  • جهش در داده‌ها — حذف رکوردها منجر به جابجایی صفحات و از دست رفتن محتوا می‌شود.
  • صفحه‌بندی cursor-based این مشکلات را از طریق اشاره‌گر به آخرین رکورد به جای افست حل می‌کند.

Offset Pagination چیست؟

Offset Pagination — روشی برای تقسیم صفحه‌بندی شده داده‌ها است که در آن درخواست کلاینت شامل دو پارامتر است: offset (چند رکورد رد شود) و limit (چند رکورد برگردانده شود). سرور کوئری SQL با OFFSET و LIMIT اجرا می‌کند، تعداد مشخص شده ردیف را رد می‌کند و مجموعه نتایج با اندازه ثابت را برمی‌گرداند.

این روش در پایگاه داده‌های رابطه‌ای به عنوان ساده‌ترین راه برای سازماندهی ناوبری بین صفحات ظهور کرد و با توسعه معماری REST به HTTP API منتقل شد. Offset Pagination نیازی به ذخیره وضعیت در سرور ندارد — هر درخواست مستقل است و تمام اطلاعات لازم برای بازیابی را دارد.

بر اساس تحقیق طراحی API توسط Postman (2025)، صفحه‌بندی با افست در 72% از REST API‌های عمومی استفاده می‌شود که آن را با وجود محدودیت‌های شناخته شده عملکرد در حجم‌های زیاد داده به استاندارد غالب تبدیل می‌کند.

ساختار درخواست و پاسخ

یک درخواست REST معمولی با Offset Pagination شامل پارامترهای کوئری offset و limit است. پاسخ شامل لیست رکوردهای صفحه مورد نظر و متاداده برای ساخت رابط ناوبری است.

پارامتر limit تعداد رکوردهای برگشتی را محدود می‌کند و از سرور و کلاینت در برابر بار اضافی محافظت می‌کند. مقادیر معمول limit — از 10 تا 50 رکورد در هر صفحه بسته به پیچیدگی داده‌ها است.

kotlin
data class PageRequest(
    val offset: Int,
    val limit: Int
)

data class PageResponse<T>(
    val items: List<T>,
    val total: Int,
    val hasMore: Boolean
)

fun RetrofitApi.fetchPage(request: PageRequest): Call<PageResponse<Item>>

Offset Pagination چگونه کار می‌کند

Offset Pagination به کوئری SQL با ساختارهای OFFSET و FETCH NEXT (یا LIMIT در MySQL/SQLite) تبدیل می‌شود. سرور پایگاه داده جدول را اسکن می‌کند، تعداد ردیف‌های برابر با offset را رد می‌کند و limit ردیف بعدی را برمی‌گرداند. هرچه offset بزرگ‌تر باشد، کوئری طولانی‌تر اجرا می‌شود.

مشکل عملکرد به این دلیل است که پایگاه داده نمی‌تواند مستقیماً به موقعیت offset برود — باید تمام ردیف‌های قبلی را بخواند و دور بریزد. در offset = 100000 و limit = 20، DBMS 100020 ردیف می‌خواند و فقط 20 را برمی‌گرداند.

کوئری SQL در پشت صحنه

SQL — زبانی است که سرور صفحه‌بندی با افست را در آن اجرا می‌کند. در PostgreSQL و MySQL از LIMIT استفاده می‌شود، در SQL Server و Oracle — OFFSET...FETCH. DBMSهای مختلف این کوئری را به روش خود بهینه می‌کنند، اما مشکل اساسی اسکن کردن همچنان باقی است.

sql
SELECT id, title, created_at
FROM articles
ORDER BY created_at DESC
OFFSET 100000 ROWS
FETCH NEXT 20 ROWS ONLY;

مشکل سازگاری

سازگاری داده‌ها — اصلی‌ترین عیب Offset Pagination هنگام کار با مجموعه‌های پویا. اگر بین دو درخواست کاربر یک رکورد جدید به ابتدای جدول اضافه شود، تمام رکوردهای موجود جابجا می‌شوند. کاربر تکراری‌ها یا پرش‌ها را می‌بیند.

جدولی با 100 رکورد و limit = 20 تصور کنید. در صفحه 1 کاربر رکوردهای 1-20 را می‌بیند. مدیر 5 رکورد جدید اضافه می‌کند. در صفحه 2 کاربر به جای 21-40 مورد انتظار، رکوردهای 26-45 را می‌بیند — رکوردهای 21-25 جا افتاده‌اند و رکوردهای 21-25 از مجموعه قبلی در صفحه 1 تکرار شده‌اند.

Offset vs Cursor-based: مقایسه رویکردها

Cursor-based pagination — جایگزینی برای Offset Pagination که از اشاره‌گر به آخرین رکورد صفحه فعلی استفاده می‌کند. به جای افست عددی، کلاینت شناسه آخرین رکورد دریافتی را ارسال می‌کند و سرور N رکورد بعد از آن را برمی‌گرداند.

رویکرد cursor-based مشکل سازگاری را حل می‌کند: موقعیت نشانگر با درج یا حذف تغییر نمی‌کند، زیرا نشانگر به یک رکورد خاص اشاره دارد نه به یک موقعیت. با این حال پیاده‌سازی آن پیچیده‌تر است — به یک فیلد مرتب‌پذیر یکتا نیاز دارد (معمولاً ID یا timestamp).

پارامترOffset PaginationCursor-based Pagination
سادگیزیاد — دو پارامتر عددیمتوسط — نیاز به کدگذاری نشانگر
سازگاریکم — تکراری‌ها هنگام درجزیاد — نشانگر به تغییرات وابسته نیست
عملکردبا افزایش offset کاهش می‌یابددر هر حجمی پایدار
پرش به صفحهبله — می‌توان به هر صفحه‌ای رفتخیر — فقط ناوبری ترتیبی
مناسب برایجدول‌های <10K رکورد، رابط با شماره صفحاتفیدها، اسکرول بی‌نهایت، مجموعه‌های بزرگ

انتخاب بین رویکردها به نیازهای رابط کاربر بستگی دارد. اگر ناوبری با شماره صفحات و انتقال مستقیم نیاز است — Offset Pagination ساده‌تر است. برای اسکرول بی‌نهایت یا فیدهای خبری، نشانگرها ترجیح داده می‌شوند.

Keyset pagination

Keyset pagination — گونه‌ای از رویکرد cursor-based است که در آن فیلتر کردن با استفاده از یک کلید یکتا با WHERE به جای OFFSET انجام می‌شود. کوئری SQL از شرط WHERE id > lastId استفاده می‌کند که به پایگاه داده امکان می‌دهد بدون اسکن ردیف‌های دور ریخته شده از ایندکس استفاده کند.

به گفته PostgreSQL Wiki، keyset pagination در جابجایی‌های بزرگ 100-1000 برابر سریع‌تر از کوئری offset اجرا می‌شود، زیرا اسکن ایندکس جایگزین پیمایش کامل جدول می‌شود. عیب — عدم امکان پرش به یک صفحه دلخواه بدون عبور ترتیبی.

چه زمانی از Offset Pagination استفاده کنیم

Offset Pagination برای مجموعه‌های داده کوچک و متوسط (تا 10000 رکورد) بهینه است، جایی که کاربر به رابطی با شماره صفحات نیاز دارد. سناریوهای معمول — پنل‌های مدیریت، لیست سفارش‌ها، کاتالوگ‌ها با فیلتر و صفحه‌بندی.

برای برنامه‌های موبایل، صفحه‌بندی با افست برای بارگذاری داده‌های تاریخی مناسب است، جایی که درج رکوردهای جدید نادر یا غیرممکن است — به عنوان مثال، تاریخچه سفارشات کاربر، لیست وظایف تکمیل شده، آرشیو تراکنش‌ها. در این سناریوها مشکل سازگاری رخ نمی‌دهد.

توصیه نمی‌شود از Offset Pagination برای فیدهای شبکه‌های اجتماعی، لیست نظرات، چت‌ها و سایر مجموعه‌های پویا با درج‌های مکرر استفاده شود. در این موارد، پرش و تکراری شدن رکوردها تجربه کاربری را بدتر می‌کند و به منطق اضافی حذف تکراری در کلاینت نیاز دارد.

رویکرد ترکیبی

صفحه‌بندی ترکیبی offset و cursor را ترکیب می‌کند: اولین درخواست از offset برای نمایش صفحه اولیه استفاده می‌کند و درخواست‌های بعدی از cursor برای بارگذاری اسکرول بی‌نهایت. این رویکرد در Instagram و Twitter پیاده‌سازی شده است، جایی که صفحه اول از طریق cursor بارگذاری می‌شود، اما offset برای محاسبه موقعیت هنگام بازگشت به نمای قبلی استفاده می‌شود.

پیاده‌سازی رویکرد ترکیبی نیاز به ذخیره موقعیت مجازی کاربر در کلاینت و هماهنگی دو مکانیزم صفحه‌بندی در سرور دارد. به گفته وبلاگ Instagram Engineering، تیم آنها از صفحه‌بندی cursor-based با فیلد اضافی startCursor استفاده می‌کند که offset را برای بارگذاری اولیه جایگزین می‌کند.

Offset Pagination در برنامه‌های موبایل

برنامه‌های موبایل از Offset Pagination همراه با Retrofit/OkHttp در اندروید و URLSession/Combine در iOS استفاده می‌کنند. الگوی معمول — بارگذاری صفحه بعدی هنگام اسکرول به انتهای لیست از طریق RecyclerView.OnScrollListener یا UICollectionView prefetching.

پیاده‌سازی صفحه‌بندی با افست در کلاینت موبایل شامل سه مؤلفه است: مدیر صفحه‌بندی (offset فعلی و hasMore را ذخیره می‌کند)، آداپتر لیست (عناصر و نشانگر بارگذاری را نمایش می‌دهد) و مخزن (کوئری‌ها را اجرا و خطاها را مدیریت می‌کند). Android Jetpack کتابخانه Paging 3 را ارائه می‌دهد که هم offset و هم cursor-based pagination را به صورت آماده پشتیبانی می‌کند.

پیاده‌سازی با Kotlin و Paging 3

Paging 3 — کتابخانه Android Jetpack برای بارگذاری صفحه‌بندی شده داده‌ها. این کتابخانه منطق صفحه‌بندی از جمله ردیابی offset، مدیریت وضعیت بارگذاری و بارگذاری خودکار هنگام اسکرول را کپسوله می‌کند. PagingSource کلیدهای صفحه بعدی و قبلی را تعریف می‌کند.

kotlin
class OffsetPagingSource(
    private val api: ApiService,
    private val limit: Int = 20
) : PagingSource<Int, Item>() {

    override suspend fun load(
        params: LoadParams<Int>
    ): LoadResult<Int, Item> {
        val offset = params.key ?: 0
        return try {
            val response = api.getItems(offset, limit)
            LoadResult.Page(
                data = response.items,
                prevKey = null,
                nextKey = if (response.hasMore) offset + limit else null
            )
        } catch (e: Exception) {
            LoadResult.Error(e)
        }
    }
}

PagingSource کلیدهای prevKey و nextKey را برای ناوبری صفحه‌بندی تعریف می‌کند. در صفحه‌بندی با افست، prevKey همیشه null است (بدون ذخیره تاریخچه نمی‌توان به صفحه قبلی رفت) و nextKey با هر بارگذاری به اندازه limit افزایش می‌یابد تا زمانی که سرور hasMore = false برگرداند. این یک مدل ساده و قابل پیش‌بینی برای لیست‌های موبایل است.

اشتباهات رایج در Offset Pagination

اولین اشتباه — تکیه بر ترتیب رکوردها بدون مرتب‌سازی. Offset Pagination نیاز به مرتب‌سازی پایدار ORDER BY بر روی یک فیلد یکتا دارد. بدون آن، DBMS ممکن است رکوردها را به ترتیب دلخواه برگرداند که منجر به تکراری‌ها و پرش‌های تصادفی بین صفحات می‌شود.

دومین اشتباه — استفاده از offset برای محاسبه شماره صفحه در رابط کاربری. فرمول page = offset / limit + 1 فقط به شرطی کار می‌کند که هیچ رکوردی بین بارگذاری‌ها حذف یا اضافه نشده باشد. با داده‌های پویا، شماره صفحه نادقیق می‌شود و کاربر اطلاعات نادرست می‌بیند.

سومین اشتباه — نادیده گرفتن مهلت زمانی کوئری‌های با offset بزرگ. در offset بیش از 100000، کوئری ممکن است ده‌ها ثانیه طول بکشد، رابط کاربری را مسدود کرده و منابع سرور را مصرف کند. توصیه می‌شود حداکثر مقدار offset را در سطح API (مثلاً 10000) تنظیم کنید و برای حجم‌های بزرگ از صفحه‌بندی cursor-based استفاده کنید.

چهارمین اشتباه — اضافه نکردن total count به پاسخ. بدون تعداد کل رکوردها، کلاینت نمی‌تواند تعداد صفحات را نمایش داده و صفحه‌بندی با شماره را پیاده‌سازی کند. با این حال، محاسبه COUNT(*) روی جداول بزرگ نیز پرهزینه است — برای مجموعه‌های بیش از 100000 رکورد از تخمین‌های تقریبی استفاده کنید یا حداکثر مقدار total را محدود کنید.

سوالات متداول

تفاوت Offset Pagination با Cursor-based چیست؟

Offset از جابجایی عددی (offset) برای رد کردن رکوردها استفاده می‌کند، در حالی که cursor از اشاره‌گر به آخرین رکورد صفحه قبلی. Offset در پیاده‌سازی ساده‌تر است، اما از تکراری‌ها هنگام درج و کاهش عملکرد در جابجایی‌های بزرگ رنج می‌برد. Cursor در هر تغییری در داده‌ها پایدار است.

چه زمانی Offset Pagination ضعیف عمل می‌کند؟

صفحه‌بندی با offset در offset بیش از 10000 رکورد به دلیل اسکن کامل جدول ناکارآمد است. همچنین برای مجموعه‌های پویا (فیدها، چت‌ها) که رکوردهای جدید بین درخواست‌ها ظاهر می‌شوند نامناسب است — کاربر هنگام ناوبری پرش‌ها و تکراری‌ها را می‌بیند.

چه limitی برای Offset Pagination بهینه است؟

limit بهینه به اندازه رکورد و سرعت شبکه بستگی دارد — از 10 تا 50 عنصر در هر صفحه. برای لیست‌های با تصاویر بزرگ از limit = 10-15 استفاده کنید، برای داده‌های متنی — 20-50. همیشه به کلاینت اجازه دهید limit خود را با محدودیت حداکثر در سرور (معمولاً 100) مشخص کند.

چگونه با تکراری‌ها در Offset Pagination مقابله کنیم؟

برای مقابله با تکراری‌ها از حذف تکراری در کلاینت بر اساس ID یکتا استفاده کنید، مرتب‌سازی پایدار بر روی فیلد یکتا اعمال کنید یا به صفحه‌بندی cursor-based مهاجرت کنید. Android Paging 3 از کلید برای حذف تکراری خودکار عناصر لیست پشتیبانی می‌کند.

آیا می‌توان از Offset Pagination با GraphQL استفاده کرد؟

بله، GraphQL از صفحه‌بندی با افست از طریق آرگومان‌های offset و limit در کوئری پشتیبانی می‌کند، اگرچه مشخصات Relay رویکرد cursor-based را توصیه می‌کند. کتابخانه‌های Apollo GraphQL و Relay پشتیبانی داخلی از صفحه‌بندی با افست با مدیریت خودکار وضعیت صفحات را ارائه می‌دهند.

خلاصه

  • Offset Pagination — روش صفحه‌بندی با پارامترهای offset و limit برای رد کردن و محدود کردن رکوردها هنگام بارگذاری صفحه‌بندی شده داده‌ها از API.
  • سادگی پیاده‌سازی و استقلال درخواست‌ها، Offset Pagination را به رویکرد استاندارد برای 72% REST APIها تبدیل کرده است (داده‌های Postman، 2025).
  • عملکرد در offset بیش از 10000 به دلیل اسکن جدول تا موقعیت مورد نظر کاهش می‌یابد — پایگاه داده تمام ردیف‌های دور ریخته شده را می‌خواند.
  • مشکل سازگاری — درج و حذف رکوردها بین کوئری‌ها منجر به تکراری‌ها و پرش‌ها در نتایج صفحات می‌شود.
  • Cursor-based مشکلات Offset Pagination را از طریق اشاره‌گر به آخرین رکورد به جای جابجایی عددی حل می‌کند.
  • رویکرد ترکیبی offset صفحه اول را با بارگذاری cursor برای اسکرول بی‌نهایت در برنامه‌های موبایل ترکیب می‌کند.
  • توصیه — از Offset Pagination برای مجموعه‌های ایستا تا 10000 رکورد استفاده کنید و برای حجم‌های بزرگ و داده‌های پویا به نشانگرها مهاجرت کنید.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید