▶ PHASE 2 · LESSON 2 · NODE.JS PRODUCTION

// node_production_depth.js Node.js Production Mastery ⚙️

Event Loop በትክክል እንዴት እንደሚሰራ እንረዳለን። Memory leaks (የሜሞሪ ብክነት) ምን ያመጣል? Clustering ለምን ያስፈልጋል? ቨርቨራችሁ በ production ጊዜ ለምን ይዘጋል (crash ያደርጋል) — እና እንዴት መከላከል ትችላላችሁ?

ያዘጋጀው: @AppMinds_ET · EthioCode Phase 2
CORE 01Event Loop — ትክክለኛውን ሂደት ይረዱnon-blocking I/O

በጀማሪ ደረጃ "Node.js asynchronous ነው" የሚለውን ብቻ አይታችኋል። አሁን ደግሞ — በአንድ thread ብቻ ሆኖ እንዴት ብዙ ጥያቄዎችን (concurrent requests) በአንድ ጊዜ ያስተናግዳል? Blocking ኮድ ምን ያመጣል? የሚለውን እንመለከታለን።

📥
Call Stack
የ JS ኮዳችን የሚሰራበት (Synchronous)። አንዱን ሳይጨርስ ወደ ቀጣዩ አያልፍም።
Event Queue
የ Async ስራዎች ሲጠናቀቁ እዚህ ይቀመጡና Stack ባዶ እስኪሆን ይጠብቃሉ።
🔄
Event Loop
Stack ባዶ ሲሆን ከ Queue ላይ ወስዶ ወደ Stack የሚያስገባ ሞተር ነው።
event-loop-trap.js — Blocking vs Non-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 አሁንም ነፃ ነው!
🚨
Real Bug — Interview Classic JSON.parse(hugeString)፣ crypto፣ እና image resize — ሁሉም የ CPU ስራዎች ስለሆኑ Event Loopን ብሎክ ያደርጋሉ። 1 request የሌሎቹን ሁሉንም ስራ ሊያስተጓጉል ይችላል! Worker Threads ወይም child_processን ተጠቀሙ።
CORE 02Memory Leaks — Production Crashfind & fix

በ Production ጊዜ አፕሊኬሽኑ ለረጅም ሰዓታት ሲሰራ የሰርቨሩ RAM ይሞላል → ከዚያም Crash ያደርጋል! Memory leak ማለት ጥቅም ላይ የዋሉ ዳታዎች (objects) በ Garbage Collector (GC) መወገድ ሲገባቸው ሳይወገዱ ሲቀሩ የሚፈጠር የሜሞሪ ብክነት ነው።

memory-leaks.js — 3 የተለመዱ ስህተቶች (Patterns)
// ❌ 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
No leaks
50 MB
stable
After 1hr
280 MB
growing
After 6hr
900 MB
danger
Crash!
OOM Killed
💀
CORE 03Clustering — የሰርቨራችሁን ሁሉንም CPU Cores ተጠቀሙhorizontal scaling

Node.js በአንድ thread (single-threaded) ብቻ ነው የሚሰራው — ስለዚህ ሰርቨራችሁ 8-core CPU ቢኖረውም የሚጠቀመው 1 core ብቻ ነው! Clustering ከተጠቀምን ግን ሁሉንም cores ያሳትፋል። ይህ ማለት 8 እጥፍ (8x) ፈጣን ስራ ይሰራላችኋል ማለት ነው!

// Without Cluster — 8 cores አሉት, ነገር ግን የሚሰራው 1 ብቻ ነው: CPU 0: [████████████████] 100% busy CPU 1: [ ] 0% idle CPU 2: [ ] 0% idle CPU 3: [ ] 0% idle CPU 4–7: idle... (በከንቱ ባክኗል!) // With Cluster — ሁሉም cores ስራ ላይ ናቸው: CPU 0: [██████ ] Worker 1 CPU 1: [████████ ] Worker 2 CPU 2: [██████ ] Worker 3 CPU 3: [████████ ] Worker 4 CPU 4–7: [█████ ] Workers 5–8 → 8x እጥፍ የሚበልጥ requests ያስተናግዳል! 🚀
cluster.js — Production Setup
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 ለማየት
CORE 04Database Connection Poolingለእያንዳንዱ request አዲስ connection አትክፈቱ

የጀማሪዎች ስህተት: ለእያንዳንዱ request አዲስ Database connection መፍጠር። ይሄ ሂደት ሁልጊዜ 100ms ሊፈጅ ይችላል! Pool ማለት የተዘጋጁ connectionsን አቆይቶ አዲስ request ሲመጣ ቀጥታ እንዲጠቀምበት ማድረግ ነው።

db-pool.js
// ❌ 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;
LABProduction Scenarios Lab
⚙️ Node.js Production Sim
Event Loop
Memory
Cluster

Blocking ኮድ ሲኖር ምን ይፈጠራል? የ Async እና Sync ልዩነትን እናወዳድር!

← ከላይ ቁልፍ ሲጫኑ → Event Loop ምን እንደሚሆን ያሳያል

Memory እያደገ ሲመጣ ምን እንደሚፈጠር መሞከሪያ — leak vs no leak

← ከላይ ሞክሩት

በ 1 process vs በ 4 workers — requests per second ምን ያህል እንደሚለያይ እናወዳድር!

← ከላይ ቁልፍ ሲጫኑ → throughput (የፍጥነት ለውጥ) ያሳያል
TESTProduction Quiz
❓ የ Node.js ሰርቨር ለሴኮንዶች ከቀዘቀዘ (Responsive ካልሆነ) ምን ችግር ነው? እና ምን ማድረግ አለባችሁ?
GET /api/report → 5 ሰከንድ ይወስዳል። ይህ request በሚሰራበት ጊዜ ሌሎቹ 100 requests ሁሉም ለ 5 ሰከንድ ይጠብቃሉ…
Aየሰርቨሩ RAM አነስተኛ ስለሆነ — ሜሞሪ መጨመር አለበት
Btimeout መጨመር አለበት — 30 ሰከንድ እንፍቀድለት
Cየ CPU ስራ ስለሆነ Worker Thread/child_process መጠቀም፣ ወይንም resultን cache ማድረግ ✅
Dasync/awaitን በ route handler ውስጥ መጨመር አለበት