Event Loop በትክክል እንዴት እንደሚሰራ እንረዳለን። Memory leaks (የሜሞሪ ብክነት) ምን ያመጣል? Clustering ለምን ያስፈልጋል? ቨርቨራችሁ በ production ጊዜ ለምን ይዘጋል (crash ያደርጋል) — እና እንዴት መከላከል ትችላላችሁ?
በጀማሪ ደረጃ "Node.js asynchronous ነው" የሚለውን ብቻ አይታችኋል። አሁን ደግሞ — በአንድ thread ብቻ ሆኖ እንዴት ብዙ ጥያቄዎችን (concurrent requests) በአንድ ጊዜ ያስተናግዳል? Blocking ኮድ ምን ያመጣል? የሚለውን እንመለከታለን።
// ❌ BLOCKING — Event Loopን ያቆመዋል! በ Production ጊዜ በፍፁም እንዳትጠቀሙት! router.get('/users', (req, res) => { // ከባድ የ CPU ስራ (CPU-intensive loop) — ሌሎች requests በሙሉ እንዲጠብቁ ያደርጋል! const result = hugeArray.filter(i => expensiveCalc(i)); res.json(result); // ይህ 3 ሰከንድ ከፈጀ → ሌሎች 1000 requests ለ 3 ሰከንድ ይታገዳሉ! }); // ❌ fs.readFileSync — Synchronous ስለሆነ በፍፁም አትጠቀሙ! const data = fs.readFileSync('bigfile.csv'); // ሁሉም ይጠብቃሉ! // ✅ Non-blocking — Event Loop አሁንም ነፃ ነው! router.get('/users', async (req, res) => { const result = await processInBackground(data); // async አሰራር! res.json(result); }); // ✅ Worker Threads — ለከባድ CPU ስራዎች ተጠቀሙት const { Worker } = require('worker_threads'); const worker = new Worker('./heavy-task.js'); worker.on('message', result => res.json(result)); // Main thread አሁንም ነፃ ነው!
በ Production ጊዜ አፕሊኬሽኑ ለረጅም ሰዓታት ሲሰራ የሰርቨሩ RAM ይሞላል → ከዚያም Crash ያደርጋል! Memory leak ማለት ጥቅም ላይ የዋሉ ዳታዎች (objects) በ Garbage Collector (GC) መወገድ ሲገባቸው ሳይወገዱ ሲቀሩ የሚፈጠር የሜሞሪ ብክነት ነው።
// ❌ LEAK 1: Global array ላይ ዳታን ማከማቸት እና አለመሰረዝ const requestLog = []; // Global! app.use((req) => requestLog.push(req)); // ሁልጊዜ ይሞላል — መቼም አይጠፋም! // ✅ FIX: መጠኑን ገደብ (Limit) አድርጉለት app.use((req) => { requestLog.push(req); if (requestLog.length > 1000) requestLog.shift(); // የድሮውን አጥፉ! }); // ❌ LEAK 2: Event listenerን አለማስወገድ server.on('request', heavyHandler); // Add ሲደረግ... // removeListener ማድረግ ከተረሳ → በሺዎች የሚቆጠሩ listeners ይከማቻሉ! // ✅ FIX: ሁልጊዜ cleanup አድርጉ server.on('request', heavyHandler); server.once('close', () => server.removeListener('request', heavyHandler)); // ❌ LEAK 3: Closure ውስጥ ትልቅ ዳታን ማቆየት function processRequest(bigData) { const fn = () => console.log(bigData.id); // bigData (ለምሳሌ 100MB) ሁልጊዜ ሜሞሪ ላይ ይቀመጣል! setTimeout(fn, 5000); } // ✅ FIX: የምትፈልጉትን ዳታ ብቻ ነጥላችሁ አውጡ function processRequest(bigData) { const id = bigData.id; // የሚያስፈልገውን ብቻ እናወጣለን const fn = () => console.log(id); // አሁን bigData ሜሞሪን አያጨናንቅም (GC ሊያጠፋው ይችላል)! setTimeout(fn, 5000); } // ── Find leaks: node --inspect በመጠቀም → Chrome DevTools የ Memory tabን ማየት እንችላለን node --inspect server.js // chrome://inspect → Memory tab
Node.js በአንድ thread (single-threaded) ብቻ ነው የሚሰራው — ስለዚህ ሰርቨራችሁ 8-core CPU ቢኖረውም የሚጠቀመው 1 core ብቻ ነው! Clustering ከተጠቀምን ግን ሁሉንም cores ያሳትፋል። ይህ ማለት 8 እጥፍ (8x) ፈጣን ስራ ይሰራላችኋል ማለት ነው!
const cluster = require('cluster'); const os = require('os'); const numCPUs = os.cpus().length; // ለምሳሌ 8 ቢሆን 8 workers እንሰራለን if (cluster.isPrimary()) { console.log(`Master: ${numCPUs} workers starting...`); // ① በሁሉም CPUs ላይ Worker fork እናደርጋለን for (let i = 0; i < numCPUs; i++) cluster.fork(); // ② Worker crash ሲያደርግ ራሱን በራሱ ያስነሳል (auto-restart) cluster.on('exit', (worker) => { console.log(`Worker ${worker.id} died → restarting`); cluster.fork(); // Auto-restart! Zero downtime ይኖረናል }); } else { // እያንዳንዱ worker የ Express appያችንን run ያደርጋል require('./server.js'); // መደበኛው Express app console.log(`Worker ${process.pid} started`); } // ── ወይም PM2 መጠቀም እንችላለን — የበለጠ ቀላል ነው! ────────────────────── // pm2 start server.js -i max # Auto cluster (ባለው core ልክ)! // pm2 start server.js -i 4 # 4 workers ያስነሳል // pm2 monit # ሁኔታውን Live ለማየት
የጀማሪዎች ስህተት: ለእያንዳንዱ request አዲስ Database connection መፍጠር። ይሄ ሂደት ሁልጊዜ 100ms ሊፈጅ ይችላል! Pool ማለት የተዘጋጁ connectionsን አቆይቶ አዲስ request ሲመጣ ቀጥታ እንዲጠቀምበት ማድረግ ነው።
// ❌ Bad: ለሁሉም request አዲስ connection መክፈት እና መዝጋት app.get('/users', async (req, res) => { const conn = await mysql.createConnection(config); // 100ms ይወስዳል! const [rows] = await conn.query('SELECT * FROM users'); await conn.end(); // ይዘጋል! ← ሁልጊዜ ይሄን ማድረግ ስህተት ነው! res.json(rows); }); // ✅ Good: Connection Pool — አንድ ጊዜ ይከፈታል፣ ለዘላለም ጥቅም ላይ ይውላል const pool = mysql.createPool({ ...config, connectionLimit: 10, // 10 connections ተዘጋጅተው ይቆያሉ waitForConnections: true, queueLimit: 0, }); app.get('/users', async (req, res) => { const [rows] = await pool.query('SELECT * FROM users'); // Pool ስለተጠቀመ connection ሁሌ ዝግጁ ነው! የሚፈጀው 1ms ብቻ ነው! res.json(rows); }); // Prismaን ስትጠቀሙ — auto pool ይሰራላችኋል! ── ነገር ግን PrismaClientን አንድ ጊዜ ብቻ ጥሩ! // ❌ new PrismaClient()ን ሁልጊዜ መጥራት አደገኛ ነው → connection exhaustion ይፈጥራል! // ✅ Singleton patternን ተጠቀሙ: const prisma = global.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== 'production') global.prisma = prisma;
Blocking ኮድ ሲኖር ምን ይፈጠራል? የ Async እና Sync ልዩነትን እናወዳድር!
Memory እያደገ ሲመጣ ምን እንደሚፈጠር መሞከሪያ — leak vs no leak
በ 1 process vs በ 4 workers — requests per second ምን ያህል እንደሚለያይ እናወዳድር!