المستوى المطلوب: محترف
لو سيرفر Node.js عندك بيقرأ ملف 5GB ويبعته على HTTP response، الذاكرة بتقفز من 92MB لـ 4.18GB في أقل من دقيقة، وبعدها التطبيق بيقع OOM. الـ stream شكله شغّال، لكنك بتتجاهل الـ backpressure. المقال ده بيوريك إزاي تكشف المشكلة بالظبط، تقيسها بأرقام، وتحلها بسطرين كود.
Backpressure في Node.js Streams: المشكلة والحل بالأرقام
المشكلة باختصار
الـ streams في Node.js بتدّيك إحساس إنك بتقرأ ملف ضخم بكفاءة. ده مش صحيح بشكل مطلق. لو الـ producer (مصدر البيانات) أسرع من الـ consumer (المستهلِك)، الـ buffer الداخلي بيتراكم في الـ heap. مفيش حد بيوقف الـ producer تلقائياً. النتيجة: Node بيخزن جيجابايت لحد ما V8 يعمل crash بـ JavaScript heap out of memory.
الحل اسمه backpressure: آلية بتخلي الـ producer يستنى لما الـ consumer يفضى. الكلام ده موجود في Stream API منذ Node 0.10، لكن أغلب الكود اللي شفته في الإنتاج بيتجاهله، وده اللي بيخلي تطبيقات صغيرة تقع على ملفات أو HTTP responses عادية.
مثال للمستوى المتوسط: المطعم والشيف البطيء
تخيل مطعم فيه شيف واحد بيطبخ طبق كل 30 ثانية، وفي صالة استقبال بتقبل طلبات بمعدل 10 طلبات كل دقيقة. خلال ساعة، الصالة استلمت 600 طلب، الشيف نفّذ منهم 120 بس. الباقي بيتراكم على الكاونتر، الكاونتر يمتلي، الأرض تمتلي، المطعم يقفل.
ده بالظبط اللي بيحصل في streams بدون backpressure. الـ readable stream هو الصالة، الـ writable stream هو الشيف. لو الصالة مبتسألش الشيف "هل عندك مكان؟" قبل ما تبعت طلب جديد، الذاكرة هي الكاونتر اللي بيتراكم.
الحل في المطعم: الكاشير بيوقف يقبل طلبات لما الكاونتر يوصل لحد معين. في Node، نفس الفكرة بالظبط. الـ API اسمه write() بيرجّع false لما الـ buffer يمتلي، وأنت بتنتظر drain event قبل ما تكتب تاني.
التعريف العلمي
الـ Backpressure في data streaming هو آلية flow control بتسمح للـ slow consumer إنه يبلّغ الـ producer إنه محتاج وقت. في Node.js، كل writable stream فيه buffer داخلي حجمه الافتراضي 16KB (وللـ object mode 16 object). الـ buffer ده اسمه highWaterMark.
لما تستدعي writable.write(chunk)، الـ method بيرجّع boolean:
true: الـ buffer لسه فيه مساحة، أكمل اكتب.false: الـ buffer امتلى، انتظرdrainevent قبل أيwrite()جديد.
المشكلة إن الكود الشائع بيتجاهل قيمة الإرجاع تماماً. النتيجة: chunks بتتراكم في الـ internal queue، والـ queue ده عمره ما هينضف لأن الـ consumer أبطأ من الـ producer بطبيعته.