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، ويتجاوز العدد المحدد من الصفوف ويعيد مجموعة نتائج بحجم ثابت.

نشأت هذه الطريقة في قواعد البيانات العلائقية كأبسط طريقة لتنظيم التنقل بين الصفحات وتم نقلها إلى واجهات HTTP API مع تطور بنية REST. لا تتطلب 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). يمسح خادم قاعدة البيانات الجدول، ويتجاوز عدد الصفوف المساوي للإزاحة، ويعيد الصفوف التالية بعدد limit. كلما زادت الإزاحة، زاد وقت تنفيذ الاستعلام.

ترجع مشكلة الأداء إلى أن قاعدة البيانات لا يمكنها الانتقال مباشرة إلى موضع الإزاحة — يجب عليها قراءة وتجاهل جميع الصفوف السابقة. عند offset = 100000 و limit = 20، يقرأ نظام إدارة قواعد البيانات 100,020 صفًا ويعيد 20 فقط.

استعلام SQL تحت الغطاء

SQL — اللغة التي ينفذ بها الخادم الترقيم بالإزاحة. تستخدم PostgreSQL و MySQL LIMIT، بينما تستخدم SQL Server و Oracle OFFSET...FETCH. تحسن أنظمة إدارة قواعد البيانات المختلفة هذا الاستعلام بطرق مختلفة، لكن مشكلة المسح الأساسية تبقى كما هي.

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، يرى المستخدم السجلات 26-45 بدلاً من المتوقعة 21-40 — السجلات 21-25 مفقودة، والسجلات 21-25 من المجموعة السابقة مكررة في الصفحة 1.

Offset vs Cursor-based: مقارنة الأساليب

الترقيم Cursor-based هو بديل لـ Offset Pagination يستخدم مؤشرًا إلى آخر سجل في الصفحة الحالية. بدلاً من الإزاحة الرقمية، يرسل العميل معرف آخر سجل تم استلامه، ويعيد الخادم السجلات N التالية بعده.

يحل أسلوب cursor-based مشكلة الاتساق: موضع المؤشر لا يتغير مع الإدراج أو الحذف لأن المؤشر يشير إلى سجل محدد، وليس إلى موضع. ومع ذلك، فهو أكثر تعقيدًا في التنفيذ — يتطلب حقلًا فريدًا قابلًا للترتيب (عادةً ID أو timestamp).

المعاملOffset PaginationCursor-based Pagination
البساطةعالية — معاملان عدديانمتوسطة — يتطلب ترميز المؤشر
الاتساقمنخفض — تكرارات عند الإدراجعالٍ — المؤشر لا يتأثر بالتغييرات
الأداءيتدهور مع الإزاحة الكبيرةمستقر في أي حجم
القفز إلى صفحةنعم — يمكن التنقل إلى أي صفحةلا — تنقل تسلسلي فقط
مناسب لـجداول <10K سجل، واجهات بأرقام الصفحاتالخلاصات، التمرير اللانهائي، المجموعات الكبيرة

يعتمد الاختيار بين الأساليب على متطلبات واجهة المستخدم. إذا كنت بحاجة إلى تنقل بأرقام الصفحات وقفز مباشر — Offset Pagination أبسط. للتمرير اللانهائي أو خلاصات الأخبار، يُفضل استخدام المؤشرات.

Keyset pagination

Keyset pagination هو تنويع من أسلوب cursor-based حيث يتم التصفية على مفتاح فريد باستخدام WHERE بدلاً من OFFSET. يستخدم استعلام SQL شرطًا مثل WHERE id > lastId، مما يسمح لقاعدة البيانات باستخدام فهرس دون مسح الصفوف المهملة.

وفقًا لويكي PostgreSQL، يعمل keyset pagination أسرع 100-1000 مرة من استعلامات offset عند الإزاحات الكبيرة لأن مسح الفهرس يحل محل المسح الكامل للجدول. العيب هو عدم القدرة على القفز إلى صفحة عشوائية دون عبور تسلسلي.

متى تستخدم Offset Pagination

Offset Pagination مثالية لمجموعات البيانات الصغيرة والمتوسطة (حتى 10,000 سجل) حيث يحتاج المستخدم إلى واجهة بأرقام الصفحات. تتضمن السيناريوهات النموذجية لوحات الإدارة وقوائم الطلبات والكتالوجات المفلترة مع ترقيم الصفحات.

للتطبيقات المحمولة، الترقيم بالإزاحة مناسب عند تحميل البيانات التاريخية حيث الإدراجات الجديدة نادرة أو مستحيلة — مثل سجل طلبات المستخدم، قوائم المهام المكتملة، أو أرشيف المعاملات. في هذه السيناريوهات، لا تنشأ مشكلة الاتساق.

غير موصى به لخلاصات وسائل التواصل الاجتماعي، قوائم التعليقات، الدردشات، والمجموعات الديناميكية الأخرى ذات الإدراجات المتكررة. في هذه الحالات، تؤدي الفجوات والسجلات المكررة إلى تدهور تجربة المستخدم وتتطلب منطق إزالة تكرار إضافي على العميل.

الأسلوب الهجين

الترقيم الهجين يجمع بين offset و cursor: يستخدم الطلب الأول offset لعرض الصفحة الأولية، بينما تستخدم الطلبات اللاحقة cursor لتحميل التمرير اللانهائي. يُستخدم هذا الأسلوب في Instagram و Twitter، حيث تُحمّل الصفحة الأولى عبر cursor، ولكن يُستخدم offset لحساب الموضع عند العودة إلى عرض سابق.

يتطلب تنفيذ الأسلوب الهجين تخزين الموضع الافتراضي للمستخدم على العميل وتنسيق آليتي ترقيم على الخادم. وفقًا لمدونة Instagram Engineering، يستخدم فريقهم الترقيم cursor-based مع حقل إضافي startCursor يحل محل offset للتحميل الأولي.

Offset Pagination في التطبيقات المحمولة

التطبيقات المحمولة تستخدم Offset Pagination مع Retrofit/OkHttp على Android و URLSession/Combine على iOS. النمط النموذجي هو تحميل الصفحة التالية عند التمرير إلى نهاية القائمة عبر RecyclerView.OnScrollListener أو UICollectionView prefetching.

يتضمن تنفيذ الترقيم بالإزاحة على العميل المحمول ثلاثة مكونات: مدير ترقيم (يخزن offset الحالي و hasMore)، ومحول قائمة (يعرض العناصر ومؤشر التحميل)، ومستودع (ينفذ الطلبات ويعالج الأخطاء). يقدم Android Jetpack مكتبة Paging 3 التي تدعم كلاً من الترقيم بالإزاحة و cursor-based بشكل جاهز.

التنفيذ في 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 مستقرًا على حقل فريد. بدونها، قد يعيد نظام إدارة قواعد البيانات السجلات بترتيب عشوائي، مما يؤدي إلى تكرارات وفجوات عشوائية بين الصفحات.

الخطأ الثاني — استخدام offset لحساب رقم الصفحة في الواجهة. تعمل الصيغة page = offset / limit + 1 فقط إذا لم يتم حذف أو إضافة أي سجل بين التحميلات. مع البيانات الديناميكية، يصبح رقم الصفحة غير دقيق ويرى المستخدم معلومات غير صحيحة.

الخطأ الثالث — تجاهل مهلات الاستعلامات ذات offset الكبير. عند offset يتجاوز 100,000، قد يستغرق الاستعلام عشرات الثواني، مما يعطل الواجهة ويستهلك موارد الخادم. يُنصح بتعيين قيمة قصوى لـ offset على مستوى API (مثل 10,000) واستخدام الترقيم cursor-based للكميات الكبيرة.

الخطأ الرابع — عدم إضافة total count في الاستجابة. بدون العدد الإجمالي للسجلات، لا يمكن للعميل عرض عدد الصفحات وتنفيذ الترقيم بالأرقام. ومع ذلك، فإن COUNT(*) على الجداول الكبيرة مكلف — للمجموعات التي تتجاوز 100,000 سجل، استخدم تقديرات تقريبية أو حدد القيمة القصوى لـ total.

الأسئلة الشائعة

كيف يختلف Offset Pagination عن Cursor-based؟

يستخدم Offset إزاحة رقمية لتخطي السجلات، بينما يستخدم cursor مؤشرًا إلى آخر سجل في الصفحة السابقة. Offset أبسط في التنفيذ لكنه يعاني من التكرارات عند الإدراج وفقدان الأداء عند الإزاحات الكبيرة. Cursor مستقر تحت أي تغييرات في البيانات.

متى يكون أداء Offset Pagination سيئًا؟

الترقيم بالإزاحة غير فعال عند offsets أكثر من 10,000 سجل بسبب المسح الكامل للجدول. كما أنه غير مناسب للمجموعات الديناميكية (الخلاصات، الدردشات) حيث تظهر سجلات جديدة بين الطلبات — يرى المستخدم فجوات وسجلات مكررة أثناء التنقل.

ما هو limit الأمثل لـ Offset Pagination؟

يعتمد limit الأمثل على حجم السجل وسرعة الشبكة — من 10 إلى 50 عنصرًا لكل صفحة. للقوائم ذات الصور الكبيرة، استخدم limit = 10-15؛ للبيانات النصية، 20-50. اسمح دائمًا للعميل بتحديد limit الخاص به مع حد أقصى على الخادم (عادةً 100).

كيف نتعامل مع التكرارات في Offset Pagination؟

للتعامل مع التكرارات، استخدم إزالة التكرار على العميل بواسطة ID فريد، أو طبق فرزًا مستقرًا على حقل فريد، أو انتقل إلى الترقيم cursor-based. يدعم Android Paging 3 key لإزالة التكرار التلقائي لعناصر القائمة.

هل يمكن استخدام 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 أكثر من 10,000 بسبب مسح الجدول إلى الموضع المستهدف — تقرأ قاعدة البيانات جميع الصفوف المهملة.
  • مشكلة الاتساق — إدراج وحذف السجلات بين الطلبات يؤدي إلى تكرارات وفجوات في نتائج الصفحات.
  • الترقيم Cursor-based يحل مشكلات Offset Pagination باستخدام مؤشر إلى آخر سجل بدلاً من الإزاحة الرقمية.
  • الأسلوب الهجين يجمع بين offset الصفحة الأولى والتحميل بالمؤشر للتمرير اللانهائي في التطبيقات المحمولة.
  • توصية — استخدم Offset Pagination للمجموعات الثابتة حتى 10,000 سجل وانتقل إلى المؤشرات للكميات الكبيرة والبيانات الديناميكية.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا