إزالة تكرار الطلبات: ما هي، طرقها وآليات العمل

المؤلف: IT Sectr نُشر: 2026-06-13 وقت القراءة: 9 دق

إزالة تكرار الطلبات هي آلية تدمج الطلبات المتوازية المتطابقة في طلب واحد، بحيث يتلقى مصدر البيانات استدعاء واحد فقط بدلاً من العشرات. في تطبيقات الجوال، تعتبر إزالة التكرار مهمة بشكل خاص: قد تطلب عدة شاشات في وقت واحد نفس ملف تعريف المستخدم أو قائمة المنتجات. وفقًا لـ Square Engineering (2024)، أدى تطبيق إزالة التكرار إلى تقليل الحمل على واجهة API الخاصة بهم بنسبة 30% دون تغيير منطق الخادم.

الملخص الرئيسي

  • إزالة تكرار الطلبات — تقنية يتم فيها دمج الطلبات المكررة في طلب واحد، ويتم إرسال النتيجة إلى جميع مقدمي الطلب.
  • Memoization — تخزين نتيجة الطلب مؤقتًا أثناء التنفيذ؛ تستدعاءات لاحقة تحصل على الكائن الجاهز.
  • Request Merging — دمج طلبات بيانات مختلفة متعددة في طلب دفعي واحد إلى الخادم.
  • DataLoader — مكتبة من GraphQL تنفذ إزالة التكرار الدفعي على الخادم.
  • مهلة النافذة — تأخير قصير (10–50 مللي ثانية) لتجميع مجموعة من الطلبات المكررة قبل الإرسال.

ما هي إزالة تكرار الطلبات؟

إزالة تكرار الطلبات هي تقنية تمنع تنفيذ طلبات متطابقة متعددة لمصدر بيانات واحد خلال نفس النافذة الزمنية. بدلاً من إرسال 10 طلبات HTTP متطابقة، يرسل النظام طلبًا واحدًا، بينما تنتظر الطلبات التسعة الأخرى نتيجته.

مشكلة الطلبات المكررة حادة بشكل خاص في تطبيقات الجوال ذات الهندسة المعمارية القائمة على الحالة (MVVM, MVI, Redux). عندما يشترك عدة مراقبين في نفس البيانات خلال فترة قصيرة، يقوم كل واحد بتشغيل طلبه الخاص، مما يخلق حملاً زائدًا. وفقًا لـ Uber Engineering (2024)، ما يصل إلى 18% من جميع الطلبات في عملاء Uber للجوال هي طلبات مكررة، وقد أدت إزالة التكرار من جانب العميل إلى تقليل عددها بمقدار 4 مرات.

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

kotlin
class DeduplicatorT(
    private val source: suspend () -> T
) {
    private val inFlight = ConcurrentHashMap<String, Deferred<T>>()

    suspend fun get(key: String): T = inFlight.getOrPut(key) {
        async {
            source().also { inFlight.remove(key) }
        }
    }.await()
}

يضمن هذا الصنف في Kotlin أنه يتم تنفيذ كوروتين واحد فقط لكل مفتاح. جميع الاستدعاءات المتزامنة مع نفس المفتاح تنتظر Deferred واحد. بعد الاكتمال، يتم إزالة المفتاح، ويتم تنفيذ الطلب التالي بشكل طبيعي.

لماذا نحتاج إزالة التكرار في تطبيقات الجوال

تقليل الحمل على الخادم هو السبب الأول والأكثر وضوحًا. كل طلب مكرر يستهلك موارد الخادم: وحدة المعالجة المركزية، الذاكرة، اتصالات قاعدة البيانات. على نطاق ملايين الأجهزة، حتى 10–15% من الطلبات المكررة تخلق حملاً كبيرًا يتطلب خوادم إضافية.

تقليل استهلاك البطارية والبيانات — كل طلب HTTP على جهاز محمول يستهلك طاقة وحدة الراديو. وفقًا لـ Google I/O (2025)، يمكن لطلب واحد فاشل أو مكرر أن يستهلك ما يصل إلى 15% من طاقة جلسة شبكة واحدة. تقلل إزالة التكرار من عدد مرات تنشيط وحدة الراديو، مما يطيل عمر بطارية الجهاز.

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

تحسين تجربة المستخدم — لا يرى المستخدم مؤشرات تحميل متعددة لنفس البيانات. تتم إدارة حالة واجهة المستخدم (تحميل / نجاح / خطأ) بواسطة مصدر حقيقة واحد بدلاً من طلبات متعددة متنافسة.

Memoization — التخزين المؤقت في الذاكرة

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

التنفيذ النموذجي في تطبيقات الجوال هو HashMap من المفاتيح إلى Deferred أو Promise. المفتاح عادةً ما يكون سلسلة عنوان URL للطلب أو سلسلة متسلسلة من المعلمات. عمر الإدخال هو من الطلب الأول حتى اكتمال الاستجابة. وفقًا لـ Dropbox Engineering (2024)، قللت المذكرات المؤقتة في عميل Dropbox للجوال من الطلبات المكررة لواجهة API بنسبة 40%.

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

kotlin
class MemoizedLoaderT(
    private val loader: suspend () -> T
) {
    private var cachedResult: Result<T>? = null

    suspend fun get(): T = cachedResult ?.getOrThrow() ?: run {
        loader().let {
            Result.success(it)
        }.also { cachedResult = it }
    }.await()
}

يستخدم MemoizedLoader Result<T> لمعالجة الأخطاء بشكل صحيح: عند النجاح — يخزن مؤقتًا، عند الخطأ — يسمح بإعادة المحاولة. يضمن هذا النهج أن فشل الشبكة المؤقت لا يمنع الطلبات اللاحقة.

Request Merging — الدمج الدفعي

Request Merging هي تقنية يتم فيها جمع عدة طلبات مختلفة لنفس المصدر في مجموعة وإرسالها كطلب دفعي واحد. على عكس إزالة التكرار، الطلبات هنا ليست متطابقة — فهي تختلف في المعلمات ولكنها تخاطب نفس المورد.

سيناريو نموذجي: 5 شاشات تطبيق تطلب ملفات تعريف مستخدمين مختلفين. بدلاً من 5 طلبات فردية إلى /api/users/1، /api/users/2، إلخ، ينتظر النظام 20 مللي ثانية، ويجمع جميع المعرفات، ويرسل طلبًا واحدًا /api/users?ids=1,2,3,4,5. مهلة النافذة هي المعلمة الرئيسية: النافذة الطويلة جدًا تضر بتجربة المستخدم، والقصيرة جدًا — لا تجمع طلبات كافية.

وفقًا لـ Netflix Engineering (2023)، في مجمع GraphQL BFF (الواجهة الخلفية للواجهة الأمامية)، قلل دمج الطلبات عدد استدعاءات HTTP بين الطبقات بنسبة 65% ومتوسط وقت الاستجابة بمقدار 120 مللي ثانية من خلال إلغاء RTT الإضافية. النافذة غير المتزامنة (debounce) هي التنفيذ القياسي عبر الكوروتينات أو RxJava.

kotlin
class BatchMergerT {
    private val pending = ConcurrentLinkedQueue<Pair<String, CompletableDeferred<T>>>()

    suspend fun get(id: String): T = suspendCoroutine { cont ->
        pending.add(Pair(id, cont))
        scheduleFlush()
    }
}

يستخدم هذا المزيج suspendCoroutine لتعليق كل طلب ونافذة 30 مللي ثانية لتجميع المجموعة. بعد انتهاء المؤقت، يتم إرسال جميع المعرفات المجمعة في طلب دفعي واحد، ويتلقى كل كوروتين نتائجه.

إزالة التكرار من جانب الخادم عبر DataLoader

DataLoader هي مكتبة (في الأصل لجافا سكريبت/GraphQL) تنفذ المعالجة الدفعية والتخزين المؤقت على جانب الخادم. تقوم بتجميع جميع الطلبات إلى نفس مصدر البيانات خلال دورة حدث واحدة وتنفيذها باستدعاء واحد. يستخدم DataLoader على نطاق واسع مع GraphQL ولكن يمكن تطبيقه في أي تطبيق REST.

كيف يعمل: يتم جمع جميع استدعاءات loader.load(id) خلال مهمة مصغرة واحدة في مصفوفة من المعرفات وتمريرها إلى الدالة الدفعية. بعد تلقي النتائج، يحصل كل معرف على عنصره في المصفوفة. يعمل التخزين المؤقت في DataLoader فقط ضمن طلب HTTP واحد — في الطلب التالي، يتم مسح التخزين المؤقت، مما يضمن نضارة البيانات.

وفقًا لـ Meta Engineering (2024)، أدى تطبيق DataLoader في طبقة GraphQL في Facebook إلى القضاء على مشكلة N+1، مما قلل استعلامات قاعدة البيانات من 200 إلى 10 لكل صفحة نموذجية. الجدولة الدفعية — الابتكار الرئيسي لـ DataLoader — تستخدم process.nextTick (Node.js) أو DispatchQueue.main (iOS) لتحسين التجميع.

أي استراتيجية إزالة تكرار تختار

Memoization مثالية لعملية واحدة (تطبيق جوال، خدمة مصغرة). بسيطة في التنفيذ وفعالة للاستدعاءات المتوازية المتطابقة. العيب أنها لا تعمل عبر عمليات أو أجهزة متعددة.

Request Merging مناسبة لطبقة BFF أو خدمة التجميع. تتطلب دعم نقاط النهاية الدفعية على الخادم. أفضل خيار عندما يقوم الواجهة الأمامية بالعديد من الطلبات الصغيرة لبيانات مختلفة من نفس النوع.

DataLoader هو المعيار لخوادم GraphQL. يحل مشكلة N+1 تلقائيًا ولا يتطلب تكوينًا يدويًا للتخزين المؤقت. موصى به لأي خادم يحتوي على طبقة GraphQL.

التخزين المؤقت HTTP مع إزالة التكرار — على مستوى OkHttp (أندرويد) أو URLSession (iOS)، يمكن تكوين إزالة التكرار عبر المعترض أو المفوض. OkHttp CacheInterceptor هو معترض مخصص يتحقق مما إذا كان الطلب بنفس عنوان URL قيد التنفيذ بالفعل ويدمجها. تعمل هذه الطريقة تحت مستوى منطق الأعمال وتغطي جميع طلبات التطبيق دون تغيير كود الميزات.

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

كيف تختلف إزالة التكرار عن التخزين المؤقت؟

إزالة التكرار تمنع تنفيذ طلب مكرر بينما الأول لا يزال قيد التنفيذ. التخزين المؤقت يحفظ النتيجة بعد التنفيذ. يكملان بعضهما البعض: إزالة التكرار تحمي من الطلبات المتكررة أثناء التحميل، والتخزين المؤقت يحمي من الطلبات المتكررة بعد ذلك.

متى يمكن أن تضر إزالة التكرار؟

إذا تم اختيار مفتاح إزالة التكرار بشكل غير صحيح. على سبيل المثال، إذا استخدم جميع المستخدمين مفتاحًا واحدًا، فإن الطلب الأول سيحظر جميع الطلبات الأخرى. يجب أن يكون المفتاح محددًا: يشمل عنوان URL والمعلمات ومعرف المستخدم. يمكن أن تخفي إزالة التكرار أيضًا مشكلات الخادم عن طريق إخفاء التكرار الحقيقي للطلبات في المقاييس.

كيف تختار مهلة النافذة لـ Request Merging؟

النافذة المثلى هي 20–50 مللي ثانية لسيناريوهات المستخدم. هذا كافٍ لتجميع مجموعة من الطلبات ولكن ليس كافيًا ليلاحظ المستخدم التأخير. للعمليات الخلفية (السجلات، التحليلات)، يمكن زيادة النافذة إلى 200–500 مللي ثانية. القاعدة التجريبية: يجب ألا تتجاوز النافذة 10% من وقت تنفيذ الطلب الواحد.

هل تعمل إزالة التكرار مع WebSocket؟

نعم، نفس المبدأ: إذا اشتركت عدة أجزاء من التطبيق في نفس قناة WebSocket، يفتح مزيل التكرار اتصالاً واحدًا ويوزع الرسائل على جميع المشتركين. RxJava Share أو Kotlin SharedFlow هما أداتان مثاليتان لإزالة تكرار رسائل WebSocket على العميل.

كيف تختبر إزالة التكرار؟

استخدم MockWebServer (OkHttp) لأندرويد أو OHHTTPStubs لنظام iOS. قم بتشغيل 10 طلبات متوازية بمعلمات متطابقة وتحقق من أن الخادم تلقى استدعاءً واحدًا بالضبط. يساعد CountDownLatch أو coroutineScope في مزامنة الاستدعاءات المتوازية في الاختبار.

الخلاصة

  • إزالة تكرار الطلبات — دمج الطلبات المتوازية المتطابقة في طلب واحد مع توزيع النتيجة على جميع مقدمي الطلب.
  • Memoization — تخزين النتيجة مؤقتًا أثناء التنفيذ؛ طريقة بسيطة وفعالة لعملية واحدة.
  • Request Merging — جمع مجموعة من الطلبات المختلفة في دفعة واحدة؛ يتطلب دعم الخادم ومهلة نافذة.
  • DataLoader — معيار إزالة التكرار لـ GraphQL؛ يحل مشكلة N+1 على مستوى الخادم.
  • ما يصل إلى 18% من الطلبات في تطبيقات الجوال مكررة؛ تقلل إزالة التكرار الحمل على الخادم والبطارية.
  • مفتاح إزالة التكرار يجب أن يكون محددًا: يشمل عنوان URL والمعلمات وسياق المستخدم.
  • أفضل ممارسة — مزيج من إزالة التكرار من جانب العميل (OkHttp Interceptor / URLSession) ومن جانب الخادم (DataLoader).

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

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

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

اقرأ أيضًا