المستوى: مبتدئ
اعمل CSV Export للمبتدئ — صدّر مليون صف بـ Node.js Streams بدون ما السيرفر يقع
لو ضغطت زرار "Export to CSV" في dashboard شركتك ولقيت المتصفح بيستنّى 90 ثانية ثم بيرجّع 502 Bad Gateway، السيرفر مش بطيء — هو حمّل الـ 1.2 مليون صف في الـ RAM دفعة واحدة فاتاكل 3.8 جيجا ووقع. الحل في 50 سطر Node.js Streams بيخلّي السيرفر يصدّر نفس البيانات في 14 ثانية ويستهلك 42 ميجا RAM فقط، مع متصفح بيبدأ يستقبل الملف من الثانية الأولى.
المشكلة باختصار
السيناريو المتكرر: عميل بيطلب تقرير بكل طلبات السنة الماضية. الـ endpoint بيكتب SELECT * FROM orders، يحط النتيجة في array، يحوّلها لـ CSV string، وأخيراً يرجّعها للمتصفح. مع 5,000 صف الكود ده شغّال. مع 500,000 صف بيقعد دقيقة. مع 1,200,000 صف السيرفر بيموت بـ JavaScript heap out of memory.
المشكلة مش في الـ DB ولا في حجم البيانات. المشكلة إن الكود بيحمّل كل حاجة في الذاكرة قبل ما يرسل أي بايت للعميل، وبيبني CSV string عملاق في heap الـ V8.
مثال يخلّي الفكرة واضحة قبل أي كود
تخيّل إنك بتنقل خزّان مياه سعته 1,000 لتر من بيت لبيت تاني. عندك خياران:
- الطريقة الغبية: تجيب دلو واحد كبير حجمه 1,000 لتر، تملّيه من الخزان الأول، تشيله للبيت التاني، وتفرّغه. المشكلة: مفيش دلو بالحجم ده، وحتى لو فيه، انت مش هتقدر تشيله.
- طريقة الـ Streams: تجيب دلو صغير 5 لتر، تملّيه، تنقله، تفرّغه، ترجع، وتكرّر. اللي في البيت التاني يقدر يبدأ يستخدم المياه بعد أول دلو، مش لازم يستنّى الـ 1,000 لتر يخلصوا.
Node.js Streams بالظبط هي الدلو الصغير. بدل ما الـ DB ترجّع المليون صف دفعة، هي بترجّعهم على دفعات صغيرة (مثلاً 1,000 صف لكل دفعة). كل دفعة تتحوّل لـ CSV وتترسل للمتصفح فوراً، ثم تتنسى من الذاكرة.
تعريف علمي: Streams و Backpressure
حسب توثيق Node.js الرسمي، الـ Stream هو abstract interface بيسمح بمعالجة البيانات على شكل chunks متتالية بدون تحميل المجموع الكلي في الذاكرة. فيه 4 أنواع: Readable، Writable، Duplex، Transform. الـ utility اللي بيربطهم اسمه pipeline() من module node:stream/promises، وبيتولّى الـ error handling والإغلاق الصحيح للموارد لو حصل أي مشكلة في الطريق.
المفهوم الأهم هنا اسمه Backpressure. لو الـ DB بترسل أسرع من ما المتصفح يستقبل (مثلاً المستخدم على شبكة 3G)، الـ pipeline تلقائياً بيوقّف القراءة من المصدر لحد ما الـ internal buffer يفضى. ده اللي بيمنع الذاكرة من إنها تنفجر حتى لو فيه فرق سرعة كبير بين الطرفين.
الكود الكامل — Express + PostgreSQL في 50 سطر
المثال شغّال على Node.js 20+ مع 8.x و 5. هتحتاج تركّب المكتبات دي: