المستوى: متوسط
لو الـ Node.js process بتاعك بيستقبل 1,200 طلب/ثانية بسلاسة، لكن أول ما يدخل طلب فيه parse PDF أو image resize، الـ API كله يقف 3 ثواني، انت مش محتاج سيرفر أقوى. انت محتاج تشيل الشغل الـ CPU-bound من Event Loop وتحطه في Worker Thread.
المشكلة باختصار
Node.js شغّال على Event Loop واحد. يعني كل الطلبات الـ HTTP اللي بتدخل بتمر على نفس الـ thread اللي بيشغّل الـ JavaScript. لو طلب واحد قعد يحسب hash لملف 50MB لمدة 2.4 ثانية، الـ 1,199 طلب اللي بعده هيستنوا في الطابور. المستخدم اللي طلب صفحة home page عادية هيلاقي 2.4 ثانية latency بدون أي سبب واضح من ناحيته.
المثال البسيط: المطبخ بطبّاخ واحد
تخيّل مطعم فيه طبّاخ واحد بيعمل كل الأوامر. لو جالك طلب steak محتاج 4 دقائق على النار، الـ sandwiches اللي بعده كلها هتستنى. السندوتشات نفسها بتطلع في 30 ثانية، بس مش لاقيين طبّاخ فاضي. المستخدم اللي طلب سندوتش هيلاقي نفسه مستني 4 دقائق ونص بدون ما يفهم ليه.
لو ضفت طبّاخ تاني في المطبخ مخصوص للأوامر الطويلة، الـ sandwiches هتطلع في وقتها الطبيعي، والـ steak يفضل ياخد 4 دقائق بس على طبّاخ تاني. ده بالظبط اللي بيعمله Worker Thread في Node.js.
التعريف العلمي: ليه setTimeout مش هيحل المشكلة
كتير من الناس بيفتكروا إن لو لفّوا العملية في setImmediate أو setTimeout، الـ Event Loop هيتحرر. ده غلط. الدوال دي بتأجّل الشغل لمرحلة لاحقة في نفس الـ Event Loop، لكن الـ JavaScript نفسها لسه بتشتغل على thread واحد. الـ CPU-bound code هيقفل الـ thread بنفس الطريقة، بس بعد تأخير صغير.
الفرق الحقيقي بييجي لمّا تستخدم Worker Thread: وحدة V8 isolate منفصلة بـ memory heap خاص بيها وـ event loop خاص بيها. الـ OS بيوزّعها على core تاني من المعالج، فالـ main thread يفضل حر يستقبل HTTP requests ويرد عليها فوراً. الكلام ده مدعوم رسمياً من node:worker_threads من Node.js 12 وفوق.
الكود: قبل وبعد
افرض عندك endpoint بيحسب SHA-256 hash لملف 80MB. الكود التقليدي:
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
app.post('/hash', (req, res) => {
const buffer = readFileSync(req.body.path);
const hash = createHash('sha256').update(buffer).digest('hex');
res.json({ hash });
});
الـ createHash هنا CPU-bound. على ملف 80MB، بياخد 2.1 ثانية. خلال الـ 2.1 ثانية دي، الـ Event Loop مقفول. الـ TTFB لباقي الـ requests بيوصل 2,400ms على حسب موقعها في الطابور.
الحل بـ Worker Thread في 12 سطر: