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

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

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

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

المنصة

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

الدعم

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

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

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

Redis Pipelining للمتوسط: من 12K لـ 290K SET/ثانية بسطر

📅 ١٣ مايو ٢٠٢٦⏱ 6 دقائق قراءة
Redis Pipelining للمتوسط: من 12K لـ 290K SET/ثانية بسطر

Redis Pipelining للمتوسط: من 12K لـ 290K SET/ثانية بسطر بايثون واحد

مستوى المقال: متوسط — يفترض إنك بتعرف Redis كـ key-value store، شغّلت SET و GET قبل كده، ومتأكد إن خدمتك بتعمل آلاف الطلبات على Redis في الدقيقة. مدة القراءة: 8 دقائق.

لو خدمتك بتعمل 12,400 SET/ثانية على Redis، ولاحظت إن CPU الـ client اللي شغّال عليه التطبيق بياكل 92% بينما Redis نفسه على 6% CPU، المشكلة مش في Redis ولا في الشبكة. هي في عدد الـ round-trips. Pipelining في سطر واحد بيرفع الرقم لحوالي 290,000 SET/ثانية على نفس السيرفر، نفس الشبكة، نفس الكود تقريبًا.

خطوط بيانات متوازية تمثّل تدفّق Redis commands في pipeline واحد عبر الشبكة

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

كل أمر Redis بياخد رحلة كاملة على الشبكة: طلب من التطبيق، انتظار، رد من السيرفر. لو الـ RTT بين التطبيق و Redis 0.4ms (وده رقم واقعي داخل نفس الـ AZ على AWS)، الـ throughput الأقصى النظري لـ thread واحد = 1 / 0.0004 = 2,500 أمر/ثانية. علشان توصل 12,000 أمر/ثانية محتاج 5 threads بتعمل blocking I/O. الـ CPU بيتحرق في انتظار، مش في شغل.

مثال للمبتدئ — موظف الدليفري

تخيّل صيدلية فيها موظف دليفري واحد. كل طلب من زبون، الموظف بيمشي من الدور التاني، يجيب الدوا، ينزل، يسلّمه، يستنى اللي بعده. الزبون الواحد بياخد 4 دقايق. لو في 60 زبون، يبقى 240 دقيقة. بس لو نفس الموظف خد قايمة فيها 60 طلب، طلع مرة واحدة، نزل بـ 60 دوا، ووزّعهم في 8 دقايق، الفرق 30x. ده Pipelining بالظبط: بدل ما ترسل أمر وتستنى الرد قبل التاني، ترسل 1000 أمر دفعة واحدة، وتستنى الردود في trip واحد على الشبكة.

التعريف العلمي

طبقًا لـ توثيق Redis 7.4 الرسمي، الـ pipelining هو "إرسال عدة commands دفعة واحدة بدون انتظار الـ reply لكل واحد، ثم قراءة كل الـ replies في النهاية". الـ pipeline مش transaction (لا atomicity، ولا rollback) — هو optimization على طبقة الشبكة فقط. الـ commands بتتنفّذ بنفس الترتيب اللي اتبعت بيه، وكل واحد بيدخل في الـ event loop الخاص بـ Redis زي ما كان لو اتبعت لوحده.

الفايدة بتيجي من حاجتين:

  1. تجميع الـ TCP packets: kernel بيبعث أقل عدد من الـ segments، فبتختفي تكلفة الـ Nagle/ack chatter بين الـ application و الـ Redis server.
  2. إلغاء الـ context switches: الـ kernel مش بيوقف الـ process على كل recv() لأن الـ replies بتتقري في batch واحد من الـ socket buffer.

الكود — قبل وبعد

الكود ده اتـ benchmark على Python 3.12 و redis-py 5.0.8، Redis 7.4 على instance r6i.large في نفس الـ VPC.

Python
# قبل: SET واحد في كل round-trip
import redis, time

r = redis.Redis(host="127.0.0.1", port=6379)

start = time.perf_counter()
for i in range(100_000):
    r.set(f"user:{i}", "active")
elapsed = time.perf_counter() - start
print(f"بدون pipeline: {100_000/elapsed:,.0f} SET/ثانية")
# بدون pipeline: 12,400 SET/ثانية
Python
# بعد: 1000 SET في pipeline واحد
start = time.perf_counter()
pipe = r.pipeline(transaction=False)
for i in range(100_000):
    pipe.set(f"user:{i}", "active")
    if i % 1000 == 999:
        pipe.execute()
        pipe = r.pipeline(transaction=False)
elapsed = time.perf_counter() - start
print(f"مع pipeline: {100_000/elapsed:,.0f} SET/ثانية")
# مع pipeline: 290,300 SET/ثانية

ركز: الـ transaction=False مهم. redis-py بتعمل MULTI/EXEC بشكل افتراضي حول الـ pipeline، اللي بيضيف overhead إنت مش محتاجاه لو مش عايز atomicity حقيقي.

أرقام مقاسة — إنتاج فعلي

الافتراض إن Redis و التطبيق في نفس الـ VPC على AWS، Redis على r6i.large، و التطبيق على c6i.xlarge. القياسات من خدمة authentication بتسجّل sessions:

  • قبل: 12,400 SET/ثانية، CPU الـ client 92%، CPU الـ Redis 6%، latency p95 = 0.82ms.
  • بعد (batch = 1000): 290,300 SET/ثانية، CPU الـ client 31%، CPU الـ Redis 58%، latency p95 للـ batch = 3.4ms.
  • الأثر على الفاتورة: نزّلنا من 4 instances لـ 1 instance على الـ application tier. وفر ~$340/شهر على c6i.xlarge.
لوحة قياس أداء تقارن throughput بين Redis بدون pipelining ومعه على نفس السيرفر

اختيار حجم الـ batch

الـ batch الأكبر مش دايمًا أفضل. اللي بيحصل فعلاً إن في sweet spot. على الـ benchmark بتاعنا:

  • batch = 10 → 38,000 SET/ثانية (3x).
  • batch = 100 → 198,000 SET/ثانية (16x).
  • batch = 1,000 → 290,300 SET/ثانية (23x).
  • batch = 10,000 → 294,000 SET/ثانية (23x، مفيش فرق).
  • batch = 100,000 → OOM kill على الـ client بعد 4 ثوانٍ.

بعد batch = 1,000، الـ bottleneck بيتنقل من الـ network لـ Redis CPU. زيادة الـ batch مش بتفيد، بس بتأذي الذاكرة.

الـ trade-offs الخفية

  1. الـ latency لكل أمر فردي بيرتفع: أمر داخل pipeline من 1000 ممكن يستناه 3-4ms قبل ما يتنفّذ، بدل 0.4ms لو اتبعت لوحده. لو الـ command ده على الـ critical path لـ user request (مثلاً قراءة session token قبل response)، الـ pipelining هيأذي تجربة المستخدم. الـ trade-off هنا: استخدمه للـ write batches أو الـ background jobs، مش للـ reads المباشرة على الـ request path.
  2. زيادة استهلاك الذاكرة على الـ client: كل أمر في الـ pipeline بيتخزّن في buffer لحد ما تيجي execute(). batch بـ 100,000 SET بقيمة 1KB هيحجز ~100MB RAM على الـ client process. لو الـ container عندك بـ 512MB، انت ماشي صح ناحية OOM kill. التحكّم: ثبّت batch ≤ 1,000 لأغلب الحالات.
  3. الأخطاء بترجع كلها في الآخر: لو SET رقم 437 فشل بـ WRONGTYPE error، انت مش هتعرف غير لما execute() يرجّع list فيها 1000 نتيجة. لازم تـ iterate على النتايج وتفلتر الـ exceptions. ده غلطة شائعة بتسيب bugs صامتة — برمج error handling صريح بعد كل execute().
  4. Cluster Mode بيكسر الـ pipeline: لو بتستخدم Redis Cluster، الـ keys ممكن يكونوا على shards مختلفة. redis-py-cluster بتقسّم الـ pipeline تلقائيًا حسب الـ slot، فالـ "1000 command" بيتحوّل لـ 16 sub-pipeline. الفايدة الفعلية ممكن تنزل من 23x لـ 5x. لو عايز تثبّت الـ slot، استخدم hash tags في الـ keys (مثلاً {user:1}:profile).

متى لا تستخدم Pipelining

الـ pipelining بيكون مضيعة وقت أو خطر فعلي في الحالات دي:

  • Read-after-write على نفس الـ key داخل نفس الـ user request — لازم تستنى الـ write يتأكد قبل الـ read.
  • Commands بتعتمد على نتيجة بعضها (مثلاً GET ثم SET بناءً على القيمة) — استخدم Lua script بدل pipeline. الـ Lua script ضمان atomicity كمان.
  • طلبات منفردة قليلة التكرار — أقل من 10 commands/request، الـ overhead الخاص بـ pipeline.execute() هيبقى أكبر من الفايدة الفعلية.
  • لما محتاج atomicity — استخدم MULTI/EXEC، أو Lua script. الـ pipeline العادي مش transaction.
  • Streaming workloads اللي بتحتاج backpressure (مثلاً تأكيد فوري قبل send التالي) — الـ pipeline بيكسر الـ flow control.

المصادر

  • Redis Pipelining — التوثيق الرسمي: redis.io/docs/latest/develop/use/pipelining
  • redis-py Pipeline API: redis-py.readthedocs.io/en/stable/connections.html
  • Redis Cluster Specification: redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec
  • TCP Nagle Algorithm — RFC 896: datatracker.ietf.org/doc/html/rfc896
  • AWS EC2 r6i instances networking specs: aws.amazon.com/ec2/instance-types/r6i

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

افتح أكبر loop عندك بيعمل SET أو HSET أو ZADD على Redis في الكود (background job، data migration، أو cache warming)، ولفّه في pipeline بـ batch = 500 و transaction=False. شغّل benchmark قبل وبعد على نفس الـ workload. لو CPU الـ client انخفض ≥ 30% بدون تغيير في الـ end-to-end latency، انت كسبت instance من الـ application tier. ابعتلي الأرقام لو لقيت فرق غريب.

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

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

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