Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nima bloklaydi
- 2.1 To'rt asosiy sabab
- 2.2 Katta JSON — o'lchov
- 2.3 ReDoS — bitta regex serverni to'xtatadi
- 3. Kechikishni o'lchash
- 3.1 G'oya: taymer qancha kechikdi
- 3.2 monitorEventLoopDelay — Node'ning o'lchagichi
- 3.3 ELU — loop qancha band
- 4. Sinxron I/O'ni topish: --trace-sync-io
- 5. Oldini olish
- 5.1 Bo'laklash: setImmediate
- 5.2 Worker'ga chiqarish
- 5.3 Qaysi birini tanlash
- 6. CLI qadami: holat yo'q
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): bloklaydimi?
- 2-mashq (o'rta): bo'lakli map
- 3-mashq (qiyin): kechikish soqchisi
- 9. Real ishda
- Xulosa
- Manbalar
Event loop'ni bloklash: aniqlash, o'lchash va oldini olish
Qisqacha: Node'da JS kodi bitta thread'da ishlaydi. Bitta og'ir sinxron hisob (katta sikl, 100 MB
JSON.parse, yomon regex) tugamaguncha boshqa hech bir so'rov, taymer yoki callback ishlamaydi — server "qotadi". Buni event loop kechikishi (lag) bilan o'lchaysiz:perf_hooks.monitorEventLoopDelayvaperformance.eventLoopUtilization. Davolash: og'ir ishni bo'laklarga bo'lib orasida loop'ga nafas berish (setImmediate) yoki uni boshqa thread'ga (worker) chiqarish. Kanon o'lchovi: umumiy vaqt deyarli o'zgarmaydi (1 434 → 1 453 ms), lekin eng katta kechikish 1 390 ms dan 47 ms ga tushadi.
Bu darsda
- Event loop'ni nima bloklashini aniq ayta olasiz: CPU-bound sikl, katta
JSON.parse, ReDoS, sinxron I/O. - Kechikishni o'lchaysiz:
monitorEventLoopDelay(p50, p99, max) va ELU. --trace-sync-iobilan yashiringan sinxron chaqiruvlarni topasiz.- Og'ir ishni
setImmediatebilan bo'laklaysiz va natijani o'lchov bilan isbotlaysiz. - Qachon bo'laklash yetarli, qachon worker kerakligini tanlay olasiz.
Oldin bilishingiz kerak: Microtask, nextTick va setImmediate, libuv thread pool, Asosiy oqimni bo'shatish, Regex amaliyotda va ReDoS.
1. Nega bu kerak?
Tasavvur qiling: valyuta kurslarini beradigan kichik server. Kunda 10 000 so'rov, har biri 2 ms da javob oladi. Bir kuni kimdir "oxirgi 5 yil kurslarini CSV qilib ber" degan tugmani bosdi. Server 1,5 soniya hisobladi. Shu 1,5 soniyada qolgan hamma foydalanuvchilar kutdi — oddiy "bugungi kurs" so'rovi ham.
libuv thread pool darsida pool to'lganda faqat fayl va DNS kutishini ko'rdik. Bu dars boshqa, xavfliroq holat haqida: asosiy thread'ning o'zi band. Bunda hech narsa ishlamaydi — taymerlar, tarmoq javoblari, hatto Ctrl+C ga javob ham kechikadi.
O'xshatish: bitta kassali do'kon. Kassir bitta mijozning 300 ta mahsulotini skanerlayapti. Navbatdagi "faqat non" olgan odam ham 10 daqiqa turadi. Kassir sekin emas — u shunchaki bitta.
Brauzerda buni Asosiy oqimni bo'shatish darsida ko'rgansiz: uzun task tugmalarni "muzlatadi". Node'da printsip xuddi shu, faqat muzlagan narsa — bitta foydalanuvchining ekrani emas, butun server.
2. Nima bloklaydi
2.1 To'rt asosiy sabab
CPU-bound (protsessorga bog'liq) ish — vaqtining asosiy qismi kutish emas, hisoblash bo'lgan ish. Uni await bilan "asinxron" qilib bo'lmaydi: hisobni kimdir bajarishi kerak, va u — asosiy thread.
| Sabab | Misol | Nega bloklaydi |
|---|---|---|
| Og'ir sikl | 400 mln Math.sqrt |
sof hisob |
Katta JSON.parse / stringify |
100 MB javob | bitta sinxron chaqiruv, bo'linmaydi |
| ReDoS | /^(a+)+$/ + yomon matn |
regex dvigateli eksponensial sinaydi |
| Sinxron I/O | readFileSync serverda |
thread disk kutadi |
Diagrammada asosiy thread'ga qarang: callback'lar navbatda turibdi, lekin ularni olib bajaradigan yagona thread band.
flowchart LR
Q["navbat: taymer, HTTP javob,<br/>fayl tayyor"] -.->|kutadi| T
T["asosiy thread<br/>JSON.parse 100 MB<br/>880 ms"]
T -->|tugagach| N["keyingi callback"]2.2 Katta JSON — o'lchov
JSON.parse — bitta funksiya chaqiruvi. U ichida to'xtab, "boshqalarga yo'l berib" bo'lmaydi. Biz CBU kurs yozuvini (6 maydonli obyekt) ko'paytirib, har xil hajmdagi JSON'ni o'qidik. Biz ishga tushirdik (Node 24.21.0, i5-12500H):
JSON.parse 10000 yozuv (1.0 MB): 9 ms
JSON.parse 100000 yozuv (10.3 MB): 72 ms
JSON.parse 1000000 yozuv (103.0 MB): 880 msHajm 10 baravar oshsa, vaqt ham taxminan 10 baravar oshadi. 1 MB — sezilmaydi. 100 MB — deyarli bir soniya butun server qotadi. Katta ma'lumotni bo'laklab o'qish uchun oqimlar (stream) bor — ularni Stream nima darsidan boshlab o'rganasiz.
2.3 ReDoS — bitta regex serverni to'xtatadi
Regex amaliyotda va ReDoS darsida ReDoS (Regular expression Denial of Service — regex orqali xizmatni ishdan chiqarish) bilan tanishgansiz. Brauzerda u bitta tabni qotiradi. Serverda esa hamma foydalanuvchini. Ichma-ich + li naqsh mos kelmaydigan matnda hamma bo'linish variantini sinaydi. Biz ishga tushirdik (har n uchun 3 marta, mediana):
n=20: 9 ms
n=22: 53 ms
n=24: 171 ms
n=26: 640 ms
n=28: 2914 msGrafikda o'sish shaklini ko'ring — har 2 ta harf vaqtni taxminan 4 baravar oshiradi:
| harflar soni (n) | Node 24.21.0 |
|---|---|
| 20 | 9 |
| 22 | 53 |
| 24 | 171 |
| 26 | 640 |
| 28 | 2 914 |
Manba: IlmHamroh yozuvchi o'lchovi: Node 24.21.0, Windows 11, i5-12500H, 3 marta mediana, 2026-10-23
30 harfli matn — bitta formadagi oddiy maydon. Hujumchi uni bir necha marta yuborsa, server daqiqalab band bo'ladi. Shuning uchun foydalanuvchi kiritgan matnga qo'llaniladigan regex'lar ehtiyotkorlik bilan yoziladi va matn uzunligi cheklanadi.
Tekshirib ko'ring:
await JSON.parse(katta)deb yozilsa, loop bloklanmaydimi?
Javob
Bloklanadi. JSON.parse Promise qaytarmaydi — u sinxron. await oddiy qiymatni oladi va faqat keyingi qatorni microtask'ga suradi. Parse'ning o'zi baribir asosiy thread'da, bir bo'lakda bajariladi. await ishni boshqa joyga ko'chirmaydi.
3. Kechikishni o'lchash
3.1 G'oya: taymer qancha kechikdi
Event loop kechikishi (event loop lag / delay) — loop'ga qo'yilgan ish va uning haqiqiy bajarilishi orasidagi ortiqcha vaqt. 10 ms ga qo'yilgan taymer 300 ms da ishlasa, kechikish 290 ms.
Buni brauzerda ham, Node'da ham o'lchash mumkin. Quyidagi blok saytda ham ishlaydi. Har 10 ms da "yurak urishi" sanaladi, keyin loop ikki xil band qilinadi:
function busyWait(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {} // CPU'ni band qiladi
}
const pause = () => new Promise((r) => setTimeout(r, 0));
let beats = 0;
const timer = setInterval(() => beats++, 10);
busyWait(300); // bitta bo'lak
await pause();
console.log(`bloklanganda yurak urishi: ${beats}`);
beats = 0;
for (let i = 0; i < 30; i++) {
busyWait(10); // 30 ta kichik bo'lak
await pause();
}
clearInterval(timer);
console.log(`bo'laklanganda ham urdimi: ${beats > 5}`);Konsolda:
bloklanganda yurak urishi: 1
bo'laklanganda ham urdimi: true300 ms davomida taymer 30 marta urishi kerak edi. U faqat bir marta urdi — loop bo'shagach, kechikkan bitta callback ishladi. Ikkinchi holatda ish hajmi bir xil (30 × 10 ms), lekin har bo'lakdan keyin loop taymerlarni bajarishga ulgurdi.
3.2 monitorEventLoopDelay — Node'ning o'lchagichi
Qo'lda sanash o'rniga Node'da tayyor vosita bor: perf_hooks modulidagi monitorEventLoopDelay. U fonda kechikishni yig'adi va gistogramma (qiymatlar taqsimoti) beradi: o'rtacha, p50, p99, max. p99 — o'lchovlarning 99 foizi shundan kichik bo'lgan qiymat; ya'ni "eng yomon 1 foiz" chegarasi.
Kanon tajribasi ikki usulni solishtirdi: bitta katta sikl (400 mln Math.sqrt) va o'sha ishning 40 bo'lagi (har biri 10 mln), orasida setImmediate:
import { monitorEventLoopDelay } from "node:perf_hooks";
function heavy(n) {
let x = 0;
for (let i = 0; i < n; i++) x += Math.sqrt(i);
return x;
}
const ms = (ns) => (ns / 1e6).toFixed(1);
async function measure(name, work) {
const h = monitorEventLoopDelay({ resolution: 1 });
h.enable();
const start = performance.now();
await work();
h.disable();
const total = (performance.now() - start).toFixed(0);
const p50 = ms(h.percentile(50));
const p99 = ms(h.percentile(99));
console.log(`${name}: jami ${total} ms, p50 ${p50}, ` +
`p99 ${p99}, max ${ms(h.max)} ms`);
}
await measure("bitta sikl 4e8", async () => {
await new Promise((r) => setTimeout(r, 10)); // isitish
heavy(4e8);
await new Promise((r) => setTimeout(r, 10));
});
await measure("40 bo'lak × 1e7", async () => {
for (let i = 0; i < 40; i++) {
heavy(1e7);
await new Promise((r) => setImmediate(r));
}
});Kanonning haqiqiy chiqishi (Node 24.21.0, Windows 11, i5-12500H; qator uzunligi uchun format biroz qisqartirilgan, raqamlar xom fayldan):
bitta sikl 4e8: jami 1434 ms, p50 14.3, p99 1390.4, max 1390.4 ms
40 bo'lak × 1e7: jami 1453 ms, p50 35.4, p99 46.7, max 46.7 msGrafikda eng muhim ustunni solishtiring — eng katta kechikish:
- bitta sikljami 1 434 ms1 390,4 ms
- 40 bo'lak + setImmediatejami 1 453 ms46,7 ms
Manba: O'lchandi: IlmHamroh kanoni, monitorEventLoopDelay, Node 24.21.0, Windows 11, i5-12500H, 2026-10-23
Ikki xulosa:
- Jami vaqt deyarli bir xil (1 434 va 1 453 ms). Bo'laklash ishni tezlashtirmaydi — hatto biroz sekinlashtiradi (har bo'lakda loop aylanadi).
- Eng katta kechikish 30 baravar kamaydi (1 390 → 47 ms). Server endi 1,4 soniya emas, eng ko'pi 47 ms "qotadi". Shu vaqt ichida boshqa so'rovlar ham javob oladi.
Bo'lakli variantda p50 kattaroq (35 ms) — har bo'lak ~35 ms davom etgani uchun. Bo'lakni kichikroq qilsangiz, kechikish ham kamayadi, lekin ortiqcha xarajat oshadi. Bu — murosa.
3.3 ELU — loop qancha band
Ikkinchi o'lchagich — ELU (Event Loop Utilization — loop'ning bandlik ulushi). 0 — loop doim bo'sh kutyapti, 1 — bir lahza ham bo'sh emas. Biz ishga tushirdik:
import { performance } from "node:perf_hooks";
function heavy(n) {
let x = 0;
for (let i = 0; i < n; i++) x += Math.sqrt(i);
return x;
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const a = performance.eventLoopUtilization();
await sleep(200); // hech narsa qilmaymiz
const idle = performance.eventLoopUtilization(a);
console.log(`bo'sh: ELU ${idle.utilization.toFixed(2)}`);
const b = performance.eventLoopUtilization();
heavy(3e8);
await sleep(0);
const busy = performance.eventLoopUtilization(b);
console.log(`band: ELU ${busy.utilization.toFixed(2)}`);bo'sh: ELU 0.04
band: ELU 0.99Kechikish va ELU bir-birini to'ldiradi. Kechikish — "foydalanuvchi qancha kutdi", ELU — "server qanchalik to'lgan". ELU doim 0.9 dan yuqori bo'lsa — server yuklamaga dosh bermayapti, kechikish hali kichik bo'lsa ham.
Tekshirib ko'ring: Server p50 kechikishi 1 ms, p99 — 900 ms. Bu nimani bildiradi?
Javob
Odatda hammasi tez, lekin vaqti-vaqti bilan (1 foiz hollarda) loop deyarli bir soniya bloklanadi. Bu — kamdan-kam ishlaydigan og'ir yo'l belgisi: masalan katta hisobot, katta JSON yoki ReDoS'ga uchragan regex. O'rtacha qiymat buni yashirardi — shuning uchun p99 va max kuzatiladi.
4. Sinxron I/O'ni topish: --trace-sync-io
Sinxron fayl o'qish ham loop'ni bloklaydi. Dastur boshida (sozlamani o'qish) bu joiz — Blocking va non-blocking I/O darsida aytganimizdek, CLI va skriptlarda ham joiz. Lekin serverda, so'rov ishlovchisi ichida — yo'q. Begona kutubxona ichida yashiringan readFileSync ni qanday topasiz?
Node'da maxsus bayroq bor: --trace-sync-io. U dastur birinchi aylanishdan (tick) keyin ishlatilgan har sinxron chaqiruv uchun ogohlantirish va stek izini chiqaradi:
import { readFileSync } from "node:fs";
setTimeout(() => {
readFileSync(import.meta.filename);
console.log("o'qildi");
}, 10);node --trace-sync-io sinxron.mjs — biz ishga tushirdik (yo'l qisqartirilgan):
(node:13348) WARNING: Detected use of sync API
at openSync (node:fs:621:18)
at readFileSync (node:fs:487:35)
at file:///C:/Users/…/sinxron.mjs:4:3
at listOnTimeout (node:internal/timers:685:17)Tarjimasi: "Sinxron API ishlatilgani aniqlandi". Stek izida aybdor qator ko'rinadi. readFileSync ichida bir nechta sinxron chaqiruv bor (openSync, tryStatSync …), shuning uchun ogohlantirish bir necha marta chiqadi. Bu bayroq faqat tashxis uchun — production'da yoqilmaydi.
5. Oldini olish
5.1 Bo'laklash: setImmediate
Og'ir ishni kichik bo'laklarga bo'lamiz va har bo'lakdan keyin loop'ga bir aylanish beramiz. Node'da buning uchun eng mos vosita — setImmediate. U callback'ni check fazasiga qo'yadi — ya'ni loop avval kutayotgan I/O callback'larini bajaradi, keyin bizga qaytadi (Microtask, nextTick va setImmediate).
Quyidagi funksiya katta massivni 10 000 talik bo'laklarda qayta ishlaydi:
const yieldToLoop = () => new Promise((r) => setImmediate(r));
async function sumInChunks(items, chunkSize = 10_000) {
let total = 0;
for (let i = 0; i < items.length; i += chunkSize) {
const end = Math.min(i + chunkSize, items.length);
for (let j = i; j < end; j++) total += items[j];
await yieldToLoop(); // boshqalarga navbat
}
return total;
}
const rates = Array.from({ length: 1_000_000 }, (_, i) => i % 100);
let ticks = 0;
const timer = setInterval(() => ticks++, 0);
console.log(await sumInChunks(rates));
clearInterval(timer);
console.log(`hisob paytida taymer ishladi: ${ticks > 0}`);Konsolda:
49500000
hisob paytida taymer ishladi: trueNega process.nextTick yoki Promise.resolve() bilan emas? Ular microtask — loop fazasiga qaytmasdan bajariladi. Har bo'lakdan keyin await Promise.resolve() qilsangiz, loop baribir bloklanadi: taymer va I/O microtask navbati bo'shamaguncha kutadi (Microtask, nextTick va setImmediate darsidagi "starvation"). Loop'ga haqiqatan yo'l berish uchun makrotask kerak: setImmediate yoki setTimeout.
Maslahat:
setTimeout(r, 0)ham ishlaydi, lekin Node'da u kamida 1 ms kutadi va timers fazasiga qaraydi. Ko'p bo'lakli ishda bu sezilarli qo'shimcha vaqt beradi.setImmediate— Node'da bo'laklash uchun odatiy tanlov. BrauzerdasetImmediateyo'q — u yerdascheduler.yield()(Asosiy oqimni bo'shatish).
5.2 Worker'ga chiqarish
Bo'laklash har doim ham mumkin emas. JSON.parse yoki tayyor kutubxona funksiyasini bo'lib bo'lmaydi. Bunday ishni boshqa thread'ga chiqarish kerak. Node'da buning uchun worker_threads moduli bor. Uni worker_threads darsida to'liq o'rganasiz; hozir g'oyani ko'rish uchun eng kichik misol:
import { Worker, isMainThread, parentPort, workerData }
from "node:worker_threads";
function heavy(n) {
let x = 0;
for (let i = 0; i < n; i++) x += Math.sqrt(i);
return x;
}
if (isMainThread) {
let beats = 0; // hisob paytida loop necha marta "nafas oldi"
const timer = setInterval(() => beats++, 10);
const worker = new Worker(new URL(import.meta.url), {
workerData: 3e8,
});
const result = await new Promise((resolve, reject) => {
worker.once("message", resolve);
worker.once("error", reject);
});
clearInterval(timer);
console.log(`natija tayyor: ${result > 0}`);
console.log(`hisob paytida loop ishladi: ${beats > 5}`);
} else {
parentPort.postMessage(heavy(workerData));
}Konsolda:
natija tayyor: true
hisob paytida loop ishladi: trueBitta fayl ikki rolda ishlaydi: asosiy thread'da u worker'ni yaratadi va kutadi, worker ichida esa hisoblaydi. Hisob ~0,3 soniya davom etdi, lekin asosiy loop'ning taymeri shu vaqtda bemalol urib turdi. Brauzerdagi Web Workers bilan g'oya bir xil: alohida thread, ma'lumot xabar bilan almashinadi.
Worker bepul emas: uni ishga tushirish o'nlab millisekund va alohida xotira oladi. Shuning uchun bir martalik 5 ms li ish uchun worker yaratish ma'nosiz. Ko'p takrorlanadigan og'ir ish uchun esa oldindan yaratilgan worker'lar to'plami ishlatiladi (Worker pool: Piscina).
5.3 Qaysi birini tanlash
| Vaziyat | Yechim |
|---|---|
| Sikl, o'zingiz yozgan, bo'lish mumkin | setImmediate bilan bo'laklash |
Bo'linmaydigan chaqiruv (JSON.parse, kutubxona) |
worker yoki oqim (stream) |
| Katta fayl yoki javob | oqim bilan bo'lak-bo'lak o'qish |
| Foydalanuvchi matni + regex | uzunlikni cheklash, xavfsiz naqsh |
| Sinxron I/O serverda | async variant (fs/promises) |
| Juda ko'p og'ir ish | worker pool yoki alohida xizmat |
6. CLI qadami: holat yo'q
valyuta CLI'ga bu darsdan kod qo'shilmaydi. Kanon buni o'lchab hal qildi:
- CLI'ning eng og'ir ishi — CBU javobini (
74 valyuta, 25.5 KB)JSON.parseqilish. 1 MB 9 ms o'qilsa, 25 KB — millisekunddan ham kam. - CLI'da bitta foydalanuvchi va bitta buyruq bor. Uni "bloklab" kutib qoladigan boshqa so'rov yo'q. Ctrl+C esa signal — u ham faqat uzoq sinxron ishda kechikadi, bizda bunday ish yo'q.
Keyinroq valyuta 1 million qatorli CSV'ni qayta ishlaydi (Amaliyot: katta faylni stream bilan) — u yerda xotira va kechikish muammosini oqimlar hal qiladi. Bo'laklash g'oyasi o'shanda yana uchraydi: oqim ham ma'lumotni bo'laklab, loop'ga navbat berib o'qiydi.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Server vaqti-vaqti bilan bir necha soniya javob bermaydi | og'ir sinxron yo'l (hisobot, katta JSON) | p99/max ni kuzating, ishni bo'laklang yoki worker'ga |
async funksiya ichida sikl — baribir qotadi |
async kodni boshqa thread'ga o'tkazmaydi |
setImmediate bilan bo'laklang |
Bo'laklar orasida await Promise.resolve() — yordam bermadi |
microtask loop fazasiga qaytmaydi | setImmediate (makrotask) |
| Bitta regex CPU'ni 100% qildi | ReDoS: ichma-ich takror + uzun matn | naqshni tuzating, uzunlikni cheklang |
| O'rtacha javob vaqti yaxshi, foydalanuvchilar shikoyat qiladi | o'rtacha qiymat dumni yashiradi | p99 va max ni o'lchang |
8. Mashqlar
Mashqlar kurs/mashqlar/24/06-bloklash/ da.
1-mashq (oson): bloklaydimi?
Har qator uchun javob bering: asosiy thread'ni uzoq bloklaydi — ha yoki yo'q.
await fetch(url)(javob 2 soniyada keladi) —JSON.parse200 MB matn —await fsPromises.readFile("katta.csv")—/^(\d+)+$/.test("1".repeat(40) + "x")—forsikl, 1 milliard qadam,asyncfunksiya ichida —
Yechim
- yo'q — kutish OS'da, loop bo'sh.
- ha — sinxron, bo'linmaydi (~1,7 soniya, «Katta JSON» o'lchoviga ko'ra).
- yo'q — o'qish pool'da (libuv thread pool). Lekin natijani bitta katta satrga aylantirish va keyin uni qayta ishlash — yana asosiy thread.
- ha — ReDoS: ichma-ich
+, oxirida mos kelmaydigan belgi. - ha —
asyncso'zi kodni asinxron qilmaydi; sikl o'rtasidaawaityo'q.
2-mashq (o'rta): bo'lakli map
mapInChunks(items, fn, chunkSize) funksiyasini yozing: massivning har elementiga fn ni qo'llaydi, har chunkSize elementdan keyin loop'ga navbat beradi (setImmediate). Natija — yangi massiv. 250 000 ta so'm summasini (i * 1000) dollarga o'giring (kurs 11 903.15, 2 xonagacha yaxlitlang) va natijaning uzunligini, oxirgi elementini va loop necha marta navbat olganini chiqaring.
Yechim
const yieldToLoop = () => new Promise((r) => setImmediate(r));
async function mapInChunks(items, fn, chunkSize = 10_000) {
const out = new Array(items.length);
let yields = 0;
for (let i = 0; i < items.length; i += chunkSize) {
const end = Math.min(i + chunkSize, items.length);
for (let j = i; j < end; j++) out[j] = fn(items[j]);
await yieldToLoop();
yields++;
}
return { out, yields };
}
const soms = Array.from({ length: 250_000 }, (_, i) => i * 1000);
const toUsd = (som) => Math.round((som / 11903.15) * 100) / 100;
const { out, yields } = await mapInChunks(soms, toUsd);
console.log(out.length, out.at(-1), yields);Konsolda:
250000 21002.76 25250 000 ÷ 10 000 = 25 bo'lak — loop 25 marta navbat oldi. out oldindan to'liq uzunlikda yaratilgan, shuning uchun massiv har qadamda kengaymaydi.
3-mashq (qiyin): kechikish soqchisi
watchLag(limitMs) funksiyasini yozing: monitorEventLoopDelay ni yoqadi va qaytargan stop() chaqirilganda { maxMs, ok } beradi (ok — max chegaradan kichikmi). Uni ikki holatda sinang: (a) 300 ms busyWait, (b) 30 × 10 ms busyWait orasida setImmediate. Chegara — 100 ms. maxMs ni chiqarmang (har safar boshqa), faqat ok ni.
Yechim
import { monitorEventLoopDelay } from "node:perf_hooks";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function watchLag(limitMs) {
const h = monitorEventLoopDelay({ resolution: 5 });
h.enable();
await sleep(20); // o'lchagich "isinib" olsin
return async () => {
await sleep(20); // oxirgi kechikish ham yozilsin
h.disable();
const maxMs = h.max / 1e6;
return { maxMs, ok: maxMs < limitMs };
};
}
function busyWait(ms) {
const end = Date.now() + ms;
while (Date.now() < end) {}
}
let stop = await watchLag(100);
busyWait(300);
console.log(`bitta bo'lak: ok=${(await stop()).ok}`);
stop = await watchLag(100);
for (let i = 0; i < 30; i++) {
busyWait(10);
await sleep(0);
}
console.log(`30 bo'lak: ok=${(await stop()).ok}`);Konsolda:
bitta bo'lak: ok=false
30 bo'lak: ok=trueGistogramma kechikishni o'zining ichki taymeri orqali o'lchaydi: taymer resolution (bu yerda 5 ms) oralig'ida uyg'onishi kerak. 300 ms blokda u uyg'ona olmaydi — max ~300 ms. Bo'laklarda esa eng ko'pi ~10 ms. Ikkita sleep(20) ga e'tibor bering. Biz birinchi variantni ularsiz yozdik va bitta bo'lak ham ok=true chiqdi (max 15 ms). Sabab: o'lchagich taymeri birinchi urishigacha hech narsa yozmaydi, blok esa aynan shu oraliqqa tushgan edi. Saboq: o'lchagichni ishdan oldin yoqing va unga bir-ikki urishga vaqt bering. Production'da xuddi shunday soqchi har daqiqada p99 ni logga yozadi va chegaradan oshsa ogohlantiradi.
9. Real ishda
- Express va boshqa serverlar — Express'ga kirish darslarida ko'rasiz: bitta ishlovchidagi og'ir sinxron kod butun serverni sekinlashtiradi. Ko'p jamoalarda "ishlovchida 50 ms dan ortiq sinxron ish yo'q" degan qoida bor.
- Monitoring — APM vositalari (Datadog, New Relic, OpenTelemetry) event loop kechikishini alohida grafikda ko'rsatadi. Uning ostida aynan shu
monitorEventLoopDelay. - Health check — ba'zi serverlar kechikish 1 soniyadan oshsa o'zini "kasal" deb e'lon qiladi va yuk balanslovchi unga so'rov yubormay turadi.
- Intervyu savoli: "Node'da CPU-og'ir ishni qanday qilasiz?" — javob: bo'laklash (
setImmediate),worker_threads, alohida xizmat; va avval o'lchash (monitorEventLoopDelay, profiling — CPU profiling).
Xulosa
- Node'da JS — bitta thread: og'ir sinxron ish (sikl, katta
JSON.parse, ReDoS, sinxron I/O) butun serverni to'xtatadi. - O'lchash:
monitorEventLoopDelay(p50, p99, max) va ELU; o'rtacha emas, dumga qarang. --trace-sync-ioyashiringan sinxron chaqiruvlarni stek izi bilan ko'rsatadi.- Bo'laklash (
setImmediate) jami vaqtni o'zgartirmaydi, eng katta kechikishni keskin kamaytiradi (1 390 → 47 ms). - Bo'linmaydigan ish — worker'ga; microtask (
Promise.resolve) loop'ga navbat bermaydi.
Keyingi dars: Global obyektlar: Node va brauzer farqi — Node'da window yo'q, lekin globalThis, process, Buffer va ko'plab veb API'lar bor.
Manbalar
- Node.js qo'llanmasi — Don't Block the Event Loop (or the Worker Pool): https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop
- Node.js hujjati —
perf_hooks.monitorEventLoopDelay,performance.eventLoopUtilization: https://nodejs.org/docs/latest-v24.x/api/perf_hooks.html - Node.js hujjati — CLI
--trace-sync-io: https://nodejs.org/docs/latest-v24.x/api/cli.html#--trace-sync-io - Node.js hujjati —
worker_threads: https://nodejs.org/docs/latest-v24.x/api/worker_threads.html - OWASP — Regular expression Denial of Service (ReDoS): https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- Kanon:
valyutatajribasi — bitta sikl va 40 bo'lak (monitorEventLoopDelay, Node 24.21.0, Windows 11, i5-12500H, 2026-10-23); JSON.parse, ReDoS, ELU va--trace-sync-io— yozuvchi o'lchovi, xuddi shu mashina.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!