أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Redis Pipeline: انزل 1000 قراءة من 850ms لـ 95ms

📅 ٢٦ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Redis Pipeline: انزل 1000 قراءة من 850ms لـ 95ms

Redis Pipeline: قلل round trips قبل ما تزود السيرفرات

مستوى القارئ: متوسط

لو API عندك بيقرأ 1000 مفتاح من Redis في request واحد، هتكسب أسرع نتيجة لما تقلل عدد الرحلات على الشبكة بدل ما تزود RAM أو CPU.

المشكلة باختصار

Redis سريع جدًا في تنفيذ أوامر بسيطة. اللي بيحصل فعلاً إن التطبيق أحيانًا يخسر الوقت في الطريق بين التطبيق وRedis، مش داخل Redis نفسه. كل أمر GET منفصل يعني request يطلع، response يرجع، وبعدها الأمر اللي بعده يبدأ.

سيناريو واقعي: عندك صفحة منتجات بتعرض 1000 بطاقة. كل بطاقة تحتاج price:{id} وstock:{id} من cache. لو كتبت loop يعمل GET لكل key بالتتابع، فأنت دفعت 1000 round trip. على شبكة داخلية فيها 0.8ms RTT، الرقم النظري وحده يقترب من 800ms قبل حساب JSON parsing أو كود التطبيق.

مخطط يوضح تقليل طلبات Redis من 1000 round trip إلى دفعات Pipeline أو MGET

مثال بسيط قبل التعريف العلمي

ركز في المثال ده. أنت لا تطلب من Redis أن يكون أسرع. أنت تطلب من التطبيق أن يتوقف عن سؤال Redis بنفس الطريقة البطيئة.

JavaScript
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL);
const ids = Array.from({ length: 1000 }, (_, i) => i + 1);

// الطريقة البطيئة: 1000 round trip متتالي
console.time("sequential-get");
const slow = [];
for (const id of ids) {
  slow.push(await redis.get(`product:${id}:price`));
}
console.timeEnd("sequential-get");

// أفضل عندما المفاتيح مستقلة: pipeline يرسل الأوامر كدفعة
console.time("pipeline-get");
const pipeline = redis.pipeline();
for (const id of ids) {
  pipeline.get(`product:${id}:price`);
}
const fast = await pipeline.exec();
console.timeEnd("pipeline-get");

// أفضل لو كل المفاتيح strings وتقدر تجيبها معًا
console.time("mget");
const keys = ids.map((id) => `product:${id}:price`);
const fastest = await redis.mget(keys);
console.timeEnd("mget");

في قياس تقديري على نفس الـ VPC: 1000 GET متتالي وصلوا إلى 850ms. نفس القراءة عبر pipeline نزلت إلى 95ms. وMGET وصلت إلى 75ms لما كانت المفاتيح مناسبة لنفس العملية. الرقم مش وعد عام. هو إطار تفكير: قلل round trips أولًا، ثم قِس.

رسم أعمدة يقارن زمن 1000 GET متتالي مع Redis pipeline وMGET

التعريف الدقيق: Pipeline وMGET بيحلوا إيه

Redis يستخدم نموذج request/response فوق TCP. الطبيعي إن العميل يرسل أمرًا وينتظر الرد. Redis pipelining يكسر النمط ده: العميل يرسل أوامر كثيرة بدون انتظار رد كل أمر، ثم يقرأ الردود مرة واحدة أو على دفعات.

MGET مختلف قليلًا. هو أمر واحد يرجع قيم مفاتيح كثيرة. التعقيد بتاعه O(N) حسب عدد المفاتيح، لكنه يوفر تكلفة إرسال أوامر كثيرة منفصلة. لو المفاتيح كلها strings وتحتاجها معًا، ابدأ بـ MGET. لو الأوامر مختلفة، استخدم pipeline.

الافتراض إن التطبيق وRedis داخل نفس المنطقة أو نفس الشبكة الخاصة، وإن المشكلة تظهر عند قراءة مئات أو آلاف المفاتيح في مسار واحد. لو عندك 5 مفاتيح فقط، الفرق غالبًا مش هيستحق تعديل الكود.

أفضل طريقة للتطبيق بدون كسر الإنتاج

  1. قِس عدد أوامر Redis في request واحد. ابدأ بالمسارات التي تتجاوز 100 أمر.
  2. اجمع المفاتيح المطلوبة قبل الوصول إلى Redis بدل ما تعمل GET داخل loop متداخل.
  3. لو كل العملية قراءة strings، استخدم MGET.
  4. لو عندك أوامر متنوعة مثل GET وHGET وTTL، استخدم pipeline.
  5. قس P50 وP95 قبل وبعد، ولا تعتمد على زمن request واحد.

الـ trade-off هنا واضح: هتكسب latency أقل وعدد syscalls أقل. هتخسر بساطة الكود، وهتحتاج تتعامل مع ترتيب النتائج والأخطاء الجزئية. في pipeline، الرد رقم 17 يخص الأمر رقم 17. لو الكود مش منظم، الأخطاء هتبقى صعبة التتبع.

حجم الدفعة مهم

الطريقة دي بتفشل لما تبعت مليون أمر في pipeline واحد وتفتكر إنك ذكي. Redis يضطر يحتفظ بالردود في الذاكرة حتى يقرأها العميل. الأفضل تقسيم العمليات إلى دفعات. وثائق Redis تقترح دفعات معقولة مثل 10 آلاف أمر عند الأحجام الكبيرة، بدل دفعة مفتوحة بلا سقف.

JavaScript
const BATCH_SIZE = 500;
const results = [];

for (let i = 0; i < keys.length; i += BATCH_SIZE) {
  const batch = keys.slice(i, i + BATCH_SIZE);
  const pipe = redis.pipeline();

  for (const key of batch) pipe.get(key);

  const replies = await pipe.exec();
  for (const [err, value] of replies) {
    if (err) throw err;
    results.push(value);
  }
}

لو الدفعة 500 أمر، أنت تقلل 1000 رحلة إلى رحلتين تقريبًا بدل رحلة واحدة ضخمة. المكسب أقل قليلًا من pipeline كامل، لكن الذاكرة والتحكم في الأخطاء أحسن.

متى لا تستخدم هذه الطريقة

  • لا تستخدمها لو القراءة تعتمد على نتيجة الأمر السابق. هنا التسلسل مقصود.
  • لا تستخدم MGET عشوائيًا في Redis Cluster لو المفاتيح موزعة على slots مختلفة، إلا لو العميل يدعم التعامل مع ذلك بوضوح.
  • لا تستخدم pipeline لإخفاء تصميم cache سيئ. لو بتقرأ 20 ألف key لكل request، راجع شكل البيانات أولًا.
  • لا تستخدم دفعات ضخمة جدًا في مسار user-facing حساس بدون قياس memory وP95.

مصادر

  • Redis Docs: Pipelining
  • Redis Docs: MGET command
  • Redis Docs: Diagnosing latency issues

الخطوة التالية

افتح أبطأ endpoint عندك، وسجل عدد أوامر Redis داخله. لو الرقم أكبر من 100، حوّل أول loop إلى MGET أو pipeline بدفعة 500، ثم قارن P95 قبل وبعد.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة