الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقع
cache من غير حد و listener جديد مع كل request، فالـ heap بيكبر مع كل request ومش بيرجع. بنفهم rss و heapUsed، والـ young والـ old generation والـ GC pauses، و max-old-space-size في الـ containers، وأشهر أنواع الـ leaks، و heap snapshots وإزاي تقارن اتنين وتدوّر على الحاجة اللي زادت بعدد الـ requests، و LRU و TTL. والتحدي: mem.js قصاد تسعتاشر حالة.
نص شرح المعلم والكود الذي كتبه ونتيجة تشغيله، بنفس ترتيب الدرس.
أهلًا بيك. أصعب bug في production: الـ app شغّال تمام، وبعد يومين يقع بـ out of memory، ويقوم تاني، وبعد يومين يقع تاني. ومحدش عارف ليه، لأن مفيش error، ومفيش request معين بيوقّعه. ده memory leak. server dot js فيه route اسمه report: بيعمل Report ويحطه في cache، بـ key فيه اسم الـ user والوقت. وبيضيف listener على event اسمه flush علشان يطبع آخر url. وفيه اتنين routes للمعمل: debug memory بيعمل GC الأول ويرجّع حجم الـ heap، و debug snapshot بيكتب heap snapshot في ملف. علشان GC بإيدينا، بنشغّل node بـ expose-gc. و leak dot js: خمسين user، ألفين request، وكل خمسمية يطبع الـ memory، وبياخد snapshot في الأول وفي الآخر. نشغّل.
// leak.js: خمسين user، كل واحد بيطلب الـ report بتاعه، وكل مرة الـ t جديد
const B = "http://localhost:3000";
const get = async (p) => (await fetch(B + p)).json();
const name = process.argv[2] ?? "run";
const show = async (i) => { const m = await get("/debug/memory"); console.log(`${String(i).padStart(5)} request ← heap ${m.heapUsedMB}MB | cache ${m.cache} | listeners ${m.listeners}`); };
await get(`/debug/snapshot?name=${name}-before`);
await show(0);
for (let i = 1; i <= 2000; i++) {
await get(`/report?user=u${i % 50}&t=${i}`);
if (i % 500 === 0) await show(i);
}
await get(`/debug/snapshot?name=${name}-after`);
console.log(`snapshots: snaps/${name}-before و snaps/${name}-after`);المخرجات
$ mkdir -p snaps; node --expose-gc server.js & S=$!; sleep 1; node leak.js naive; kill $S
on 3000
0 request ← heap 6.6MB | cache 0 | listeners 0
warning: MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 flush listeners added t
500 request ← heap 10.5MB | cache 500 | listeners 500
1000 request ← heap 13.8MB | cache 1000 | listeners 1000
1500 request ← heap 17.1MB | cache 1500 | listeners 1500
2000 request ← heap 20.3MB | cache 2000 | listeners 2000
snapshots: snaps/naive-before و snaps/naive-afterالـ heap بيكبر مع كل دفعة، بنفس المقدار تقريبًا، ومش بيرجع، رغم إننا بنعمل GC قبل كل قياس. يعني الحاجات دي لسه حد ماسكها. الـ cache فيه ألفين، والـ listeners ألفين. وفوق، الـ server طبع warning اسمه MaxListenersExceededWarning: أكتر من عشرة listeners على نفس الـ event. ده مش error، والـ app كمّل عادي، بس ده من أهم الـ warnings اللي لازم ما تتجاهلهاش. وعلشان كده الـ server بيسمع على event اسمه warning ويبعته للـ logs، بدل ما يضيع في الـ stderr. ودلوقتي اتخيل ده بـ مِيّة request في التانية. كام ساعة والـ process يقع.
الـ memory في Node فيها كام رقم: rss: كل اللي الـ process ماسكه من الـ RAM. ده اللي الـ container بيقيسه وبيقتلك لو عدّى الـ limit. heapUsed: الـ JavaScript objects بتاعتك. و external و arrayBuffers: الـ Buffers والحاجات اللي بره الـ heap. والـ GC في V8 بيقسّم الـ heap لجيلين: young generation: صغير، وكل object جديد بيتعمل فيه. أغلب الـ objects بتموت بسرعة، فـ GC بتاعه اسمه scavenge، سريع وبيحصل كتير. old generation: اللي عاش أكتر من GC، بيتنقل هنا. والـ GC بتاعه mark sweep compact، أتقل، وممكن يعمل pause يبان في الـ event loop delay بتاع الدرس اللي فات. والـ GC بيمسح بس اللي محدش ماسكه. لو فيه Map أو array أو listener لسه بيشاور على object، الـ object ده عايش، حتى لو إنت مش محتاجه تاني. ده هو الـ leak: حاجة reachable ومش محتاجها. وفي containers: الـ old generation ليها حد. اعمل max-old-space-size حوالي خمسة وسبعين في المِيّة من الـ container limit، علشان يبقى فيه مكان للـ Buffers والـ stack. كده V8 يحاول بجد قبل ما يوصل للحد، وتاخد error واضح بدل ما الـ kernel يقتل الـ process من غير ما يقول حاجة.
أشهر أنواع الـ leaks: cache من غير حد: Map أو object بيكبر مع كل key جديد. وخصوصًا لو الـ key فيه حاجة بتتغيّر زي الوقت أو الـ request id. الحل: LRU بحد أقصى و TTL. وفيه مكتبة اسمها lru-cache بتعمل ده. listeners: كل request بيضيف listener ومحدش بيشيله. والـ listener ده closure، يعني ماسك كل اللي حواليه. الحل: listener واحد في الـ startup، أو once، أو off لما الـ request يخلص. timers: setInterval جُوّه request من غير clearInterval. closures: function صغيرة ماسكة req، و req ماسك res، و res ماسك الـ socket، فـ request واحد كامل فضل عايش. global arrays للـ logs أو الـ metrics. و promises عمرها ما بتخلص، ومستنيين عليها حاجات. ولو عايز تربط data بـ object من غير ما تمسكه: WeakMap. لما الـ object يموت، الـ entry بتموت معاه.
طيب في production، تعرف منين؟ وتلاقيه إزاي؟ الـ graph بتاع heapUsed: الطبيعي شكله زي سنان المنشار، يطلع والـ GC ينزّله لنفس المستوى. الـ leak شكله زي السلم: كل مرة الـ GC بينزّله لمستوى أعلى من اللي قبله. الأداة: heap snapshot. صورة لكل object في الـ heap، مين ماسك مين. تاخدها بـ v8 writeHeapSnapshot، أو بـ heapsnapshot-signal وتبعت signal للـ process، أو heapsnapshot-near-heap-limit علشان ياخدها لوحده قبل ما يقع. الطريقة: snapshot، وبعدين load، وبعدين snapshot تاني، وقارن. وفي Chrome DevTools فيه Comparison view بيعمل كده. والـ trick: لو بعت ألفين request، دوّر على الحاجات اللي زادت ألفين بالضبط. دول اللي بيتعملوا مرة لكل request ومش بيموتوا. وبعدها بص على الـ retainers: مين ماسك الـ object ده. وخلي بالك: الـ snapshot بيوقّف الـ process لحد ما يخلص، ومحتاج memory قد الـ heap تقريبًا. فخده من instance واحدة بعد ما تشيلها من الـ load balancer.
server dot js الجديد: الـ cache بقى createLRU بحد خمسمية و TTL دقيقة، والـ listener بقى واحد بس في الـ startup، والـ route بيغيّر متغيّر اسمه lastUrl. و diff dot js بيقرا الـ snapshots ويطبع اللي زاد. التحدي: اكتب mem dot js. test dot js فيه تسعتاشر حالة.
mem.js: createLRU بـ Map وحد أقصى بيشيل الأقدم و TTL وقت الـ get و now ينفع يتغيّر، و heapCounts بيقرا الـ nodes بالـ node_fields ويعدّ بالاسم، و diffHeaps بيطلّع اللي زاد مترتب بالـ size.
الحل. createLRU: الـ Map بيفتكر ترتيب الإضافة، فلما نلمس key نمسحه ونحطه تاني، فيروح الآخر. والأقدم دايمًا أول واحد. والـ TTL بيتشيك وقت الـ get بس، من غير timers. heapCounts: بنمشي على الـ nodes بخطوة قد عدد الـ fields، وكل field بالـ index بتاعه من الـ meta. diffHeaps: الفرق لكل اسم، واللي زاد بس. نقارن الـ snapshots بتاعة الـ server القديم، اللي اتسجّلوا في أول الدرس.
// mem.js: cache بحد، وقراية heap snapshots ومقارنتهم
// LRU: Map بيحفظ ترتيب الإضافة، فأول key فيه هو أقدم واحد اتلمس
export function createLRU({ max = 1000, ttlMs = Infinity, now = Date.now } = {}) {
const m = new Map();
const fresh = (e) => now() - e.at < ttlMs;
return {
get(k) {
const e = m.get(k);
if (!e) return undefined;
if (!fresh(e)) { m.delete(k); return undefined; } // انتهى
m.delete(k); m.set(k, e); // اتلمس ← يروح الآخر
return e.v;
},
set(k, v) {
m.delete(k);
m.set(k, { v, at: now() });
while (m.size > max) m.delete(m.keys().next().value); // شيل الأقدم
return this;
},
has(k) { return this.get(k) !== undefined; },
get size() { return m.size; },
};
}
// heap snapshot: nodes array واحدة طويلة، كل node عدد fields بالترتيب اللي في meta.node_fields
const KINDS = new Set(["object", "closure", "array", "string", "regexp"]);
export function heapCounts(snap) {
const meta = snap.snapshot.meta, F = meta.node_fields, types = meta.node_types[0];
const step = F.length, iType = F.indexOf("type"), iName = F.indexOf("name"), iSize = F.indexOf("self_size");
const out = new Map();
for (let i = 0; i < snap.nodes.length; i += step) {
const type = types[snap.nodes[i + iType]];
if (!KINDS.has(type)) continue;
// الـ object اسمه الـ constructor، والـ closure اسمه اسم الـ function، والباقي بنوعه
const name = type === "object" || type === "closure" ? `${snap.strings[snap.nodes[i + iName]] || "(anonymous)"}${type === "closure" ? "()" : ""}` : `(${type})`;
const r = out.get(name) ?? { count: 0, size: 0 };
r.count++; r.size += snap.nodes[i + iSize];
out.set(name, r);
}
return out;
}
export function diffHeaps(before, after, { n = 5 } = {}) {
const a = heapCounts(before), b = heapCounts(after);
return [...b].map(([name, r]) => {
const old = a.get(name) ?? { count: 0, size: 0 };
return { name, countDelta: r.count - old.count, sizeDelta: r.size - old.size };
}).filter((r) => r.countDelta > 0).sort((x, y) => y.sizeDelta - x.sizeDelta).slice(0, n);
}المخرجات
$ node diff.js naive أكتر حاجات زادت في naive: + 82076 نسخة + 2925KB (string) + 28015 نسخة + 2681KB (array) + 54022 نسخة + 2407KB Object + 26010 نسخة + 813KB Array + 2000 نسخة + 781KB ServerResponse + 8002 نسخة + 641KB system / Context زادوا 2000 بالضبط، يعني واحد لكل request: ServerResponse، IncomingMessage، Url، next()، trimPrefix()، onFlush()، Report
فوق: strings و arrays و objects. دول دايمًا فوق، ومش بيقولوا حاجة كتير. تحت، اللي زادوا ألفين بالضبط: Report، ده الـ cache. و onFlush، ده الـ listener. وكمان ServerResponse و IncomingMessage، يعني الـ req والـ res بتوع كل request لسه عايشين. ليه؟ لأن onFlush closure بيستخدم req dot url، فماسك req، و req ماسك res. فالـ listener الصغير ده خلّى كل request كامل يفضل في الـ memory. نشغّل الـ server الجديد، ونقارن الـ snapshots بتوعه.
المخرجات
$ mkdir -p snaps; node --expose-gc server.js & S=$!; sleep 1; node leak.js fixed; kill $S; node diff.js fixed
on 3000
0 request ← heap 6.6MB | cache 0 | listeners 1
500 request ← heap 8.4MB | cache 500 | listeners 1
1000 request ← heap 8.5MB | cache 500 | listeners 1
1500 request ← heap 8.6MB | cache 500 | listeners 1
2000 request ← heap 8.5MB | cache 500 | listeners 1
snapshots: snaps/fixed-before و snaps/fixed-after
أكتر حاجات زادت في fixed:
+ 10523 نسخة + 489KB Object
+ 11551 نسخة + 342KB (string)
+ 514 نسخة + 114KB (array)
+ 500 نسخة + 23KB Report
+ 509 نسخة + 16KB Array
+ 1 نسخة + 0KB Socket
زادوا 2000 بالضبط، يعني واحد لكل request: ولا حاجةالـ heap طلع شوية في الأول، وبعدين واقف مكانه. ده الـ cache بيتملي لحد خمسمية ويقف. listener واحد، ومفيش warning. والـ diff: خمسمية Report بس، ده حجم الـ cache بالضبط. ومفيش حاجة زادت ألفين. وخلي بالك: الـ key لسه فيه الوقت، يعني الـ cache عمره ما هيلاقي حاجة. ده bug تاني. بس حتى لو الـ key صح، أي cache لازم يبقى ليه حد. ونشغّل test dot js.
المخرجات
$ mkdir -p snaps; node --expose-gc server.js & S=$!; sleep 1; node test.js; kill $S on 3000 ✅ LRU: get و size ✅ LRU: الرابع بيشيل الأقدم (b، لأن a اتلمس) ✅ LRU: set لـ key موجود بيحدّثه ويرجّعه الآخر ✅ LRU: key مش موجود ← undefined ✅ TTL: قبل ما يخلص ← موجود ✅ TTL: بعد ما يخلص ← undefined ومتشال ✅ LRU: عشرة آلاف set ← مية بس ✅ heapCounts: User اتنين بتمانين byte ✅ heapCounts: الـ closure باسم الـ function و () ✅ heapCounts: string ← (string)، و object من غير اسم ← (anonymous) ✅ heapCounts: code و hidden مش محسوبين ✅ diffHeaps: onClick زاد اتنين، User زاد واحد ✅ diffHeaps: مترتب بالـ size، واللي ما زادش مش موجود ✅ diffHeaps: n ✅ snapshot حقيقي: Leaky زادت تلات آلاف 3000 ✅ server: الـ cache مش بيعدّي خمسمية 500 ✅ server: listener واحد بس على flush 1 ✅ server: الـ heap ما زادش بعد ألفين request كمان 9MB ← 8.9MB ✅ server: الـ report لسه شغّال 19/19 نجحوا
كله نجح. الدرس الجاي: مشكلة أداء مش في Node خالص، في الـ database. الـ N plus one: صفحة واحدة بتعمل مِيّة query من غير ما تاخد بالك، وإزاي DataLoader يجمعهم في query واحدة.
// server.js: نفس الـ app، بس الـ cache ليه حد، والـ listener واحد بس
import express from "express";
import { EventEmitter } from "node:events";
import { writeHeapSnapshot } from "node:v8";
import { createLRU } from "./mem.js";
class Report {
constructor(user, t) {
this.user = user;
this.t = t;
this.rows = Array.from({ length: 20 }, (_, i) => ({ i, v: i * 3.5, label: `row-${user}-${i}` }));
}
}
const app = express();
// warnings بتاعة Node بتروح للـ logs زي أي حاجة تانية، علشان ما تضيعش
process.on("warning", (w) => console.log(`warning: ${w.name}: ${w.message.slice(0, 70)}`));
const bus = new EventEmitter();
const cache = createLRU({ max: 500, ttlMs: 60_000 }); // أي cache لازم يبقى ليه حد
let lastUrl = null;
bus.on("flush", () => console.log("flush", lastUrl)); // listener واحد، مرة واحدة في الـ startup
app.get("/report", (req, res) => {
const key = `${req.query.user}:${req.query.t}`;
if (!cache.has(key)) cache.set(key, new Report(req.query.user, req.query.t));
lastUrl = req.url;
res.json({ rows: cache.get(key).rows.length });
});
// للمعمل بس: في production الـ routes دي ورا auth، أو على port داخلي
app.get("/debug/memory", (req, res) => {
globalThis.gc?.(); // محتاج node --expose-gc: نقيس اللي فاضل بعد الـ GC بس
const m = process.memoryUsage();
const mb = (x) => Math.round((x / 1048576) * 10) / 10;
res.json({ heapUsedMB: mb(m.heapUsed), rssMB: mb(m.rss), cache: cache.size, listeners: bus.listenerCount("flush") });
});
app.get("/debug/snapshot", (req, res) => {
globalThis.gc?.();
res.json({ file: writeHeapSnapshot(`snaps/${req.query.name}.heapsnapshot`) });
});
app.listen(3000, () => console.log("on 3000"));// diff.js: إيه اللي زاد بين الـ snapshot الأولاني والأخراني؟
import { readFileSync } from "node:fs";
import { diffHeaps } from "./mem.js";
const name = process.argv[2] ?? "naive";
const load = (f) => JSON.parse(readFileSync(`snaps/${name}-${f}.heapsnapshot`, "utf8"));
console.log(`أكتر حاجات زادت في ${name}:`);
for (const r of diffHeaps(load("before"), load("after"), { n: 6 }))
console.log(` +${String(r.countDelta).padStart(6)} نسخة +${(r.sizeDelta / 1024).toFixed(0).padStart(6)}KB ${r.name}`);
// الـ trick: دوّر على الحاجات اللي زادت بعدد الـ requests بالضبط
const per = Number(process.argv[3] ?? 2000);
const exact = diffHeaps(load("before"), load("after"), { n: 1000 }).filter((r) => r.countDelta === per);
console.log(`\nزادوا ${per} بالضبط، يعني واحد لكل request: ${exact.map((r) => r.name).join("، ") || "ولا حاجة"}`);import { writeHeapSnapshot } from "node:v8";
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { createLRU, heapCounts, diffHeaps } from "./mem.js";
let ok = 0, all = 0;
const check = (name, cond, extra = "") => { all++; if (cond) ok++; console.log(cond ? "✅" : "❌", name, extra); };
const B = "http://localhost:3000";
const c = createLRU({ max: 3 });
c.set("a", 1).set("b", 2).set("c", 3);
check("LRU: get و size", c.get("a") === 1 && c.size === 3);
c.set("d", 4);
check("LRU: الرابع بيشيل الأقدم (b، لأن a اتلمس)", !c.has("b") && c.has("a") && c.has("c") && c.has("d") && c.size === 3);
c.set("c", 30);
c.set("e", 5);
check("LRU: set لـ key موجود بيحدّثه ويرجّعه الآخر", c.get("c") === 30 && !c.has("a"));
check("LRU: key مش موجود ← undefined", c.get("zzz") === undefined);
let t = 0;
const tl = createLRU({ max: 10, ttlMs: 100, now: () => t });
tl.set("x", 1);
t = 99;
check("TTL: قبل ما يخلص ← موجود", tl.get("x") === 1);
t = 100;
check("TTL: بعد ما يخلص ← undefined ومتشال", tl.get("x") === undefined && tl.size === 0);
const big = createLRU({ max: 100 });
for (let i = 0; i < 10000; i++) big.set(i, i);
check("LRU: عشرة آلاف set ← مية بس", big.size === 100 && big.has(9999) && !big.has(0));
const fake = (nodes) => ({
snapshot: { meta: { node_fields: ["type", "name", "id", "self_size", "edge_count"], node_types: [["hidden", "array", "string", "object", "code", "closure"]] } },
strings: ["", "User", "onClick", "x"],
nodes: nodes.flatMap(([type, name, size]) => [type, name, 0, size, 0]),
});
const s1 = fake([[3, 1, 40], [3, 1, 40], [5, 2, 32], [2, 3, 16], [4, 3, 100], [0, 3, 8], [3, 0, 24]]);
const hc = heapCounts(s1);
check("heapCounts: User اتنين بتمانين byte", hc.get("User")?.count === 2 && hc.get("User")?.size === 80);
check("heapCounts: الـ closure باسم الـ function و ()", hc.get("onClick()")?.count === 1);
check("heapCounts: string ← (string)، و object من غير اسم ← (anonymous)", hc.get("(string)")?.count === 1 && hc.get("(anonymous)")?.count === 1);
check("heapCounts: code و hidden مش محسوبين", hc.size === 4);
const s2 = fake([[3, 1, 40], [3, 1, 40], [3, 1, 40], [5, 2, 32], [5, 2, 32], [5, 2, 32], [2, 3, 16]]);
const d = diffHeaps(s1, s2);
check("diffHeaps: onClick زاد اتنين، User زاد واحد", d.find((r) => r.name === "onClick()")?.countDelta === 2 && d.find((r) => r.name === "User")?.countDelta === 1);
check("diffHeaps: مترتب بالـ size، واللي ما زادش مش موجود", d[0].name === "onClick()" && !d.some((r) => r.name === "(string)" || r.name === "(anonymous)"));
check("diffHeaps: n", diffHeaps(s1, s2, { n: 1 }).length === 1);
class Leaky { constructor(i) { this.i = i; } }
const dir = mkdtempSync(`${tmpdir()}/heap-`);
const snap = (n) => JSON.parse(readFileSync(writeHeapSnapshot(`${dir}/${n}.heapsnapshot`), "utf8"));
const before = snap("a");
globalThis.keep = Array.from({ length: 3000 }, (_, i) => new Leaky(i));
const after = snap("b");
rmSync(dir, { recursive: true });
const leaky = diffHeaps(before,
…03 / دروس المسار (122)
- 01أهلًا بيك: يعني إيه backend، والكورس ده ماشي إزاي
- 02الملفات والفولدرات والـ terminal: تتحرك على جهازك زي المبرمجين
- 03Node وأول برنامج: تشغّل وتقرا رسالة الخطأ
- 04القيم والمتغيرات والأنواع: const و let و typeof
- 05القرارات والتكرار: if و === والـ truthy والـ loops
- 06الـ functions: parameters و return والـ arrow functions والـ scope
- 07الـ objects والـ arrays: destructuring و spread و map و filter و reduce
- 08JSON والنصوص والتواريخ: اللغة اللي الـ backend بيتكلم بيها
- 09الـ callbacks والـ closures وأول لمسة async: setTimeout
- 10الـ classes والـ errors: throw و try/catch و stack trace
- 11git: تحفَظ كل خطوة وترجعلها، وما تسرّبش أسرارك
- 12ESM و CommonJS و package exports: Node بيلاقي الـ modules إزاي
- 13الـ Promises و async/await: sequential و parallel والأخطاء والإلغاء
- 14الـ event loop والـ microtasks: Node بيعمل كل حاجة بـ thread واحد إزاي
- 15الـ Streams والـ Backpressure: ملفات أكبر من الـ memory
- 16الـ File System والـ paths: قراية وكتابة صح، ومن غير path traversal
- 17الـ Buffers والبيانات الـ binary: الـ bytes من جُوّه
- 18Crypto APIs والأسرار: random و hash و HMAC و AES-GCM
- 19الـ Worker Threads: شغل CPU تقيل من غير ما توقف الـ server
- 20الـ Errors على مستوى الـ process: operational و programmer و crash-only
- 21الـ Node test runner والـ debugging: tests من غير مكتبات
- 22HTTP من جُوّه: الـ methods والـ status codes والـ headers
- 23Express والـ Routing: الـ params والـ query والترتيب والـ Router
- 24Express 5: إيه اللي اتغيّر، وإزاي تنقل مشروع قديم
- 25الـ Middleware ودورة حياة الـ request جُوّه Express
- 26Validation للمدخلات بـ Zod: ما تصدّقش أي حاجة جاية من برة
- 27تصنيف الأخطاء و Problem Details: شكل واحد لكل الـ errors
- 28الـ CORS والـ Cookies: المتصفح بيحمي المستخدم إزاي
- 29رفع الملفات وحدود الحجم: multer من غير ثغرات
- 30الـ Realtime: Server-Sent Events و WebSockets
- 31OpenAPI والـ API Versioning: عقد مكتوب، وتغيير من غير ما تكسر حد
- 32PostgreSQL: الـ DDL وأنواع البيانات الصح لكل عمود
- 33الـ Constraints: PK و FK و UNIQUE و CHECK — الـ database بتحمي نفسها
- 34الـ Normalization والـ ERD: تقسّم البيانات صح من أول يوم
- 35الـ JOINs والتجميع: INNER و LEFT و GROUP BY و HAVING و window functions
- 36JSONB في PostgreSQL: إمتى تستخدمه، وإزاي تسأله وتعمله index
- 37الـ Indexes من جُوّه: B-tree و GIN و BRIN
- 38Partial و Covering indexes: indexes أصغر وأسرع
- 39الـ Pagination بالـ Keyset: ليه OFFSET بيبطّأ وبيكرر
- 40Full Text Search في Postgres: بحث بالكلمات، بالعربي والإنجليزي
- 41EXPLAIN ANALYZE والـ Planner: اقرا الـ plan وصلّح الـ query
- 42الـ Transactions بـ node-postgres: كله أو ولا حاجة
- 43الـ MVCC ومستويات الـ Isolation: transactions كتير في نفس الوقت
- 44الـ Locks والـ Deadlocks: مين مستني مين
- 45SKIP LOCKED و Job Leasing: Postgres كـ job queue
- 46Row Level Security والـ Multi-tenancy: كل عميل يشوف بياناته بس
- 47Migrations من غير downtime: expand و backfill و contract
- 48الـ Replication والـ Read Replicas: نسخ حية من الـ database
- 49الـ Connection Pooling و PgBouncer: connections أقل، requests أكتر
- 50الـ Partitioning والـ VACUUM والمراقبة: Postgres على المدى الطويل
- 51Redis وأنواع البيانات: strings و hashes و lists و sets و sorted sets
- 52Cache-aside والـ TTL: Redis قدام الـ database
- 53الـ Cache invalidation والـ Stampede: لما الـ key الشهير يموت
- 54Lua والـ Atomicity في Redis: كام أمر كأنهم أمر واحد
- 55الـ Rate Limiting في Redis: fixed window و sliding window و token bucket
- 56الـ Sessions في Redis: تسجيل دخول تقدر تلغيه في أي وقت
- 57Redis Streams والـ Consumer Groups: log بيتوزع على workers
- 58Pub/Sub و BullMQ: إشارات بين الـ instances، وطوابير شغل محترمة
- 59Redis لما يقع: الـ persistence (RDB و AOF) والـ replication
- 60Redis في الإنتاج: الـ ACLs والـ Cluster والمراقبة
- 61تخزين الـ Passwords بـ Argon2id: صح من أول مرة
- 62Session Fixation والـ Rotation: id جديد مع كل صلاحية جديدة
- 63الـ Cookie Flags والـ CSRF: المتصفح بيبعت الـ cookie، فلازم تتأكد مين اللي طلب
- 64الـ XSS والـ Content Security Policy: ما تخليش النص يتحوّل لـ code
- 65الـ OAuth و OpenID Connect و PKCE: login بـ Google من غير ما تشوف الـ password
- 66التأكد من الـ JWT صح: التوقيع والـ claims والـ scopes
- 67الـ BOLA و BFLA: الـ token السليم مش معناه إن الطلب مسموح
- 68الـ RBAC و ABAC والملكية: policy واحدة بدل ifs متفرقة
- 69الـ MFA والـ recovery: التاني factor اللي بجد
- 70الـ Passkeys و WebAuthn: login من غير password وضد الـ phishing
- 71الـ Threat Modeling بـ STRIDE: فكّر زي المهاجم قبل ما تكتب code
- 72الـ SQL Injection والـ Parameterization: الـ data ما تبقاش code
- 73الـ SSRF والـ Egress: ما تخليش الـ server بتاعك يزور الشبكة الداخلية
- 74الـ Security Headers والـ TLS: كل response يقول للـ browser يحمي المستخدم إزاي
- 75توقيع الـ Webhooks والـ Replay: الـ webhook ده جاي فعلًا من Stripe؟
- 76تغيير الأسرار والـ HMAC: إزاي تغيّر مفتاح من غير ما كل حاجة تقع
- 77سلسلة التوريد والـ SBOM: إنت واثق في ألف package ما تعرفهمش؟
- 78الـ Audit والـ Privacy Logs: سجّل مين عمل إيه، من غير ما تسجّل الأسرار
- 79اختبار الأمان و OWASP ASVS: الأمن جُوّه الـ CI، مش audit آخر السنة
- 80الـ Prototype Pollution والـ Resource DoS: request واحد يخلّي الكل admin أو يوقّع الـ server
- 81الـ SLI والـ SLO والـ Error Budget: إمتى تصحّي الـ on-call وإمتى تسيبه نايم
- 82الـ Event Loop Delay والـ ELU: لما Node نفسه يتخنق
- 83الـ Worker Threads والـ CPU Profiling: الشغل التقيل بره الـ event loop، والوقت راح فين
- 84الـ Memory والـ GC والـ Leaks: الـ process اللي بيكبر لحد ما يقعانت هنا
- 85الـ N+1 Queries والـ DataLoader: صفحة واحدة بمِيّتين query
- 86الـ HTTP Caching والـ Compression: أسرع request هو اللي ما بيوصلش
- 87الـ Load Testing والـ Soak Testing: الـ server بيستحمل قد إيه بجد
- 88الـ Concurrency Limits والـ Bulkheads: dependency بطيئة ما توقعش الـ app كله
- 89الـ Timeouts والـ Retries والـ Circuit Breakers: إزاي تتعامل مع dependency بطيئة أو واقعة
- 90OpenTelemetry والـ Distributed Tracing: الـ request راح فين وضاع وقته فين
- 91Docker Layers والـ Build Context: ليه الـ build بياخد دقيقة وفيه الـ .env بتاعك
- 92Multi-stage Builds و Non-root: image صغيرة ومن غير root
- 93Docker Compose والـ Networks: الـ services بتلاقي بعض إزاي
- 94Docker Volumes: الـ data بتتحفَظ فين
- 95Healthcheck و Restart و Resource Limits: running مش معناها شغّال
- 96BuildKit والـ Build Secrets: الـ token ما يدخلش الـ image
- 97Reverse Proxy و TLS: اللي واقف قدام الـ app
- 98Multi-arch Images والـ Native Modules: اشتغلت عندي
- 99CI/CD و Rollback و Canary: الـ deploy يبقى ممل
- 100Kubernetes من الأول: Pods و Deployments و Services و Probes
- 101Modular Monolith و DDD: حدود واضحة من غير network
- 102Transactional Outbox و Inbox: الـ event ما يضيعش وما يتنفذش مرتين
- 103Sagas: عملية واحدة على تلات services من غير transaction واحدة
- 104CQRS و Event Sourcing: الـ events هي الحقيقة
- 105الـ Queues والـ Brokers: BullMQ و RabbitMQ و Kafka و SQS
- 106Realtime على أكتر من instance: Redis pub/sub و presence و resume
- 107الـ Object Storage والـ Media: presigned URLs و uploads مباشرة
- 108التكامل مع الـ Email والـ Search: retries و webhooks و indexes
- 109GraphQL و gRPC في الإنتاج: contracts و DataLoader و limits و deadlines
- 110Kafka على أكتر من zone و region: الـ replication والـ ISR والـ acks والـ MirrorMaker والـ failover
- 111TypeScript و runtime boundaries: الـ types بتختفي، والـ schemas لأ
- 112ORM ولا SQL: query builders و repositories و migrations و transactions
- 113الـ Serverless من جُوّه: cold starts و freeze و timeouts و connections
- 114الـ Testing Pyramid: unit و integration و e2e على Postgres حقيقية
- 115Contract Testing و Property-Based Testing بـ fast-check
- 116الـ Payments: idempotency و webhooks و refunds و reconciliation
- 117الـ Timezones والـ Internationalization: UTC و DST والـ locales والـ plurals والفلوس
- 118الـ Privacy والـ Data Retention: consent و export و erasure و crypto shredding
- 119الـ ADRs والـ Feature Flags: القرارات المكتوبة والـ rollout من غير deploy
- 120Multi-region active-active: حل الـ conflicts بـ HLC و CRDTs
- 121Readiness و Liveness و Startup Probes: الـ pod جاهز ولا عايش؟
- 122Incident Runbooks: من الـ alert للـ postmortem