Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Katta test fayl yasash
- 2.1 Backpressure bilan generatsiya
- 2.2 Xotirani qanday o'lchaymiz
- 3. O'lchov: stream va butun fayl
- 3.1 Ikki usul
- 3.2 Natija
- 3.3 Halol xulosa
- 4. Siqish yo'lda: pipeline + zlib
- 5. Progress: uzoq ishda foydalanuvchiga xabar
- 6. Xatoda qisman natija
- 7. valyuta ning to'liq oqim sxemasi
- 8. Ko'p uchraydigan xatolar
- 8.1 Bitta jarayonda ikki usulni o'lchash
- 8.2 "Stream'da hammasi tez bo'ladi" deb kutish
- 8.3 Stream ichida hammasini massivga yig'ish
- 8.4 Generatsiyada drain ni kutmaslik
- 8.5 Progress'ni stdout'ga yozish
- 9. Mashqlar
- 1-mashq (oson): Kim qancha xotira oladi
- 2-mashq (o'rta): O'tkazib yuborish rejimi
- 3-mashq (qiyin): tahlil ga progress
- 4-mashq: CLI qadami — holat 35 ni yakunlash va commit
- 10. Real ishda
- Xulosa
- Manbalar
Node.js katta fayl: 1 million qatorli CSV'ni stream bilan qayta ishlash
Qisqacha: Katta faylni
readFileSyncbilan o'qisangiz, xotira fayl hajmiga qarab o'sadi. Stream bilan esa xotira deyarli o'zgarmaydi. Kanon o'lchovida 1 million qatorli (41 MB) CSV'ni tahlil qilish stream bilan eng ko'pi 63 MB, butun fayl bilan 194 MB xotira oldi. 16 MB heap chegarasida stream ishladi, butun fayl esa yiqildi. Lekin stream tezroq emas: 796 ms va 567 ms. Stream'ni tezlik uchun emas, barqaror xotira uchun tanlaysiz.
Bu darsda
- Katta test faylni backpressure bilan generatsiya qilasiz.
- Stream va butun faylni o'qishni xotira va vaqt bo'yicha halol solishtirasiz.
process.memoryUsage()varesourceUsage().maxRSSbilan xotirani o'lchaysiz.- Uzoq ishga progress va xatoda qisman natija siyosatini qo'shasiz.
valyutaning holat 35 ini yakunlab, commit qilasiz.
Oldin bilishingiz kerak: pipeline, finished va stream xatolari, Async iteratorlar va Web Streams, zlib: siqish, Node xotira limitlari.
1. Nega bu kerak?
Oxirgi besh darsda oqimlarni qismlarga bo'lib o'rgandik: turlari, backpressure, pipeline, o'z Transform'ingiz, for await. Bugun hammasini bitta real vazifaga yig'amiz va eng muhim savolga raqam bilan javob beramiz: stream rostdan ham kerakmi?
Vaziyat: buxgalter Malika opa sizga bank tizimidan yuklangan CSV beradi. Unda bir yillik barcha valyuta operatsiyalari — million qator. U so'raydi: "Har valyuta bo'yicha eng past, eng yuqori va o'rtacha kurs qancha?" Fayl 40 MB. Ertaga 4 GB bo'lishi mumkin.
valyuta da bu ish uchun ikki buyruq bor: valyuta tarix kunlik kurslarni faylga yozadi, valyuta tahlil esa faylni o'qib statistika chiqaradi. Ikkalasi ham oqim bilan ishlaydi. Bugun ularni katta fayl bilan sinaymiz.
Hayotiy o'xshatish: suv omboridan uyga suv kerak. Birinchi usul — butun omborni bir martada uyingizga to'kish: hovli suv bosadi. Ikkinchi usul — quvur: suv kerakli tezlikda oqadi, uyda bir chelakdan ortiq suv turmaydi. Ombor kichik bo'lsa, ikkinchi usul ortiqcha ovora bo'lib ko'rinadi. Ombor kattalashganda esa birinchi usul umuman ishlamaydi.
2. Katta test fayl yasash
2.1 Backpressure bilan generatsiya
O'lchash uchun avval katta fayl kerak. Kanon 8 valyuta × 125 000 qator = 1 000 000 qatorli CSV yasadi (sana hamma qatorda bir xil — o'lchov uchun bu muhim emas). Uni yozishning o'zi ham oqim bilan bo'lishi kerak. Aks holda million qatorli satrni xotirada yig'ib, keyin yozamiz — xuddi o'zimiz qochmoqchi bo'lgan holat. Kanondagi generator (tajriba/t35-katta.mjs dan, qisqartirilgan):
import { createWriteStream, statSync } from "node:fs";
import { once } from "node:events";
const w = createWriteStream("katta.csv");
w.write("sana,kod,nomi,nominal,kurs,farq\n");
const kodlar = ["USD", "EUR", "RUB", "GBP",
"JPY", "KZT", "CNY", "TRY"];
for (let i = 0; i < 125_000; i++) {
for (const k of kodlar) {
const kurs = (10000 + (i % 997)).toFixed(2);
const qator = `2026-10-23,${k},Valyuta nomi,1,${kurs},0.5\n`;
if (!w.write(qator)) await once(w, "drain");
}
}
w.end();
await once(w, "finish");
const mb = (statSync("katta.csv").size / 2 ** 20).toFixed(1);
console.log(`fayl: ${mb} MB, ${125_000 * kodlar.length} qator`);Kanon natijasi (Node 24.21.0, Windows 11, i5-12500H):
fayl: 41.0 MB, 1000000 qatorAsosiy qator — if (!w.write(qator)) await once(w, "drain"). Bu backpressure darsidagi qoida: write() false qaytarsa, bufer to'lgan — drain hodisasini kutamiz (EventEmitter dagi once). Bu blok saytda emas, o'z kompyuteringizda ishga tushiriladi: zavod tekshiruvchisining 10 soniyalik chegarasiga sig'masligi uchun biz uni nocheck qildik.
(10000 + (i % 997)).toFixed(2) — kurslar 10 000.00 dan 10 996.00 gacha takrorlanadi. Natijani oldindan bilamiz: min 10 000, max 10 996. O'lchov to'g'ri ishlaganini shu bilan tekshiramiz.
2.2 Xotirani qanday o'lchaymiz
Node jarayon xotirasini process.memoryUsage() beradi. Biz ishga tushirdik (bo'sh skript, Node 24.21.0):
const mb = (n) => `${(n / 2 ** 20).toFixed(0)} MB`;
const m = process.memoryUsage();
console.log("rss:", mb(m.rss), "| heapTotal:", mb(m.heapTotal));
console.log("heapUsed:", mb(m.heapUsed),
"| external:", mb(m.external));
console.log("arrayBuffers:", mb(m.arrayBuffers));rss: 45 MB | heapTotal: 6 MB
heapUsed: 4 MB | external: 2 MB
arrayBuffers: 0 MB| Maydon | Nima |
|---|---|
rss |
jarayonning operativ xotiradagi butun hajmi (kod, stek, heap, buferlar) |
heapTotal / heapUsed |
V8 heap: ajratilgan / ishlatilgan (JS obyektlar, satrlar) |
external |
V8'dan tashqaridagi C++ xotira (masalan, Buffer ma'lumoti) |
arrayBuffers |
ArrayBuffer va Buffer lar — external ning bir qismi |
Bu 45 MB ning katta qismi — V8 va Node'ning o'zi (kanon o'lchovida bo'sh Node jarayoni ~38 MB rss) (Node xotira limitlari darsini eslang). Bitta o'lchov lahzani ko'rsatadi, bizga esa eng yuqori nuqta kerak. Buning uchun process.resourceUsage().maxRSS bor — jarayon hayoti davomidagi eng katta rss (kilobaytlarda). Kanon har usulni alohida jarayonda ishga tushirdi va chiqishda shu qiymatni yozdi:
// rss.cjs — har bola jarayonga --require bilan oldindan yuklanadi
process.on("exit", () => {
const mb = (process.resourceUsage().maxRSS / 1024).toFixed(0);
process.stderr.write(`MAXRSS ${mb} MB\n`);
});Nega alohida jarayon? Bitta jarayonda ikki usulni ketma-ket sinasangiz, birinchisining axlati (garbage) ikkinchisining o'lchoviga qo'shiladi. Har usul — toza jarayon, aks holda o'lchov yolg'on bo'ladi.
Tekshirib ko'ring: Nega
heapUsedemas,rss(aniqrog'imaxRSS) ni solishtiramiz?
Javob
Fayldan o'qilgan baytlar Buffer da turadi — u V8 heap'idan tashqarida (external). heapUsed ularni ko'rmaydi. Operatsion tizim esa jarayonga ajratilgan hamma xotirani hisoblaydi — server, konteyner chegarasi ham aynan rss ga qaraydi. Shuning uchun "dastur qancha xotira oldi" degan savolga rss javob beradi.
3. O'lchov: stream va butun fayl
3.1 Ikki usul
Stream usuli — valyuta tahlil katta.csv: createReadStream → readline → for await (oldingi dars). Xotirada bitta bo'lak (64 KB), joriy qator va har valyuta uchun bitta kichik obyekt.
Butun fayl usuli — taqqoslash uchun yozilgan eng oddiy kod:
const fs = require("fs");
const t = fs.readFileSync(process.argv[2], "utf8").split("\n");
const m = new Map();
for (const q of t.slice(1)) {
if (!q) continue;
const p = q.split(",");
m.set(p[1], (m.get(p[1]) ?? 0) + 1);
}
console.log("natija");
console.log([...m].slice(0, 2).join(" "));Bu usulda xotirada bir vaqtda uch narsa turadi: butun fayl satri (41 MB baytdan yasalgan JS satr), million elementli massiv va slice(1) ning nusxasi.
3.2 Natija
Kanon xom chiqishi (t35-katta.txt, Node 24.21.0, Windows 11, i5-12500H). Har qatorda uch narsaga qarang: vaqt, eng katta rss va exit kod:
stream: valyuta tahlil : 796 ms, eng katta rss 63 MB, exit 0
USD 125000 10 000.00 10 996.00 10 497.07 2026-10-23..2026-10-23
butun fayl + split : 567 ms, eng katta rss 194 MB, exit 0
USD,125000 EUR,125000
stream, --max-old-space-size=16 : 835 ms, eng katta rss 61 MB, exit 0
butun fayl, --max-old-space-size=16 : 162 ms, eng katta rss ?, exit 134 — FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryXotirani grafikda ko'ring:
- Streamvalyuta tahlil63 MB
- Butun faylreadFileSync + split194 MB
- Stream, heap 16 MBishladi61 MB
Manba: IlmHamroh kanon o'lchovi, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H (t35-katta.txt)
Endi vaqtni:
- Stream796 ms
- Butun fayltezroq567 ms
- Stream, heap 16 MB835 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H (t35-katta.txt)
3.3 Halol xulosa
Uch narsani o'qiymiz.
Birinchi: xotira. Stream 63 MB, butun fayl 194 MB — uch baravar. Muhimi nisbat emas, o'sish. Stream xotirasi fayl hajmiga bog'liq emas: 41 MB fayl ham, 4 GB fayl ham ~60 MB oladi. Butun fayl usuli esa fayl kattalashgan sari o'sadi. (4 GB ni biz o'lchamadik — bu hujjatlashtirilgan xulq va yuqoridagi grafikdan kelib chiqadigan xulosa. Bundan tashqari V8'da bitta satrning hajmi chegaralangan, juda katta faylni readFileSync(..., "utf8") umuman satrga aylantira olmaydi.)
Ikkinchi: chegara. --max-old-space-size=16 — V8 heap'ini 16 MB bilan cheklash (Node xotira limitlari). Stream bunda ham ishladi. Butun fayl usuli esa 162 ms da FATAL ERROR: Reached heap limit — "heap chegarasiga yetildi, ajratish muvaffaqiyatsiz — JavaScript heap'ida xotira tugadi" bilan yiqildi, chiqish kodi 134. 256 MB li arzon konteynerda, ko'p foydalanuvchili serverda aynan shu bo'ladi.
Uchinchi: tezlik. Stream sekinroq — 796 ms va 567 ms. Bu kutilmagan tuyulishi mumkin, lekin mantiqiy: stream'da har qator uchun alohida readline hodisasi va await, kichik bo'laklar, ko'proq funksiya chaqiruvlari bor. Butun faylni bitta katta split bilan kesish — V8 uchun juda tez ish.
Diqqat: "Stream tez" degan gapni tez-tez eshitasiz. O'lchovimiz buni tasdiqlamadi. Stream'ning foydasi — barqaror, chegaralangan xotira va birinchi natijaning erta kelishi. Fayl kichik (bir necha MB) va xotira yetarli bo'lsa,
readFileoddiyroq va tezroq bo'lishi mumkin.
Qaysi birini tanlash kerak?
| Holat | Tanlov |
|---|---|
| Fayl kichik (≲ 10 MB) va hajmi o'smaydi | readFile — oddiy va tez |
| Hajmi noma'lum yoki o'sadi (log, eksport, foydalanuvchi fayli) | stream |
| Server, ko'p parallel so'rov | stream (har so'rov × fayl hajmi — xotira tugaydi) |
| Natija ham katta (CSV → CSV.gz) | pipeline — kirish ham, chiqish ham oqim |
valyuta tahlil stream'ni tanladi: foydalanuvchi qanday fayl berishini oldindan bilmaymiz.
4. Siqish yo'lda: pipeline + zlib
Katta faylni saqlashda siqish tabiiy qadam. zlib darsida kanon kesh fayllarini (25 KB) siqmaslikka qaror qilgan va "siqish o'rni — katta CSV" degan edi. Mana o'sha joy. Siqish — Transform oqim, shuning uchun u pipeline ga bitta bo'g'in bo'lib qo'shiladi:
await pipeline(
createReadStream("katta.csv"),
createGzip(),
createWriteStream("katta.csv.gz"),
);Biz shu faylda ishga tushirdik (Node 24.21.0, i5-12500H, ikki marta):
asl: 41.0 MB, gzip: 2.39 MB (5.8 %), 1088 ms
asl: 41.0 MB, gzip: 2.39 MB (5.8 %), 867 ms5.8 % — juda yaxshi natija, lekin halol bo'laylik: bizning fayl sun'iy, qatorlar deyarli bir xil. Haqiqiy ma'lumot bunchalik siqilmaydi. Kanondagi haqiqiy EUR tarixi (24 qator, 895 bayt) gzip bilan 317 baytga tushdi — 35 %.
O'qishda teskari yo'l: createReadStream("x.csv.gz") → createGunzip() → readline. Kanondagi faylniOch aynan shunday qiladi va fayl nomi .gz bilan tugasa, ochuvchini o'zi qo'shadi (pipeline darsidagi xato uzatish tuzatishi bilan).
5. Progress: uzoq ishda foydalanuvchiga xabar
4 GB faylni tahlil qilish bir necha daqiqa oladi. Foydalanuvchi qora ekranga qarab, "qotib qoldimi?" deb Ctrl+C bosadi. Yechim — progress (bajarilish foizi). Fayl hajmi statSync dan ma'lum, o'tgan baytlarni esa oqim yo'lida sanaymiz. Buning uchun kichik Transform yetarli — u bo'lakni o'zgartirmaydi, faqat sanaydi:
import { createReadStream, statSync, writeFileSync } from "node:fs";
import { Transform } from "node:stream";
import { pipeline } from "node:stream/promises";
writeFileSync("kurslar.csv", "2026-10-23,USD,12030\n".repeat(50_000));
const jami = statSync("kurslar.csv").size;
let otgan = 0;
let keyingi = 25;
const progress = new Transform({
transform(bolak, _kod, tayyor) {
otgan += bolak.length;
while ((otgan / jami) * 100 >= keyingi) {
console.log(`${keyingi}% (${otgan} bayt)`);
keyingi += 25;
}
tayyor(null, bolak);
},
});
let qatorlar = 0;
const fayl = createReadStream("kurslar.csv");
await pipeline(fayl, progress, async (oqim) => {
for await (const bolak of oqim) {
for (const bayt of bolak) if (bayt === 10) qatorlar++;
}
});
console.log("qatorlar:", qatorlar, "| jami:", jami, "bayt");Konsolda:
25% (327680 bayt)
50% (589824 bayt)
75% (851968 bayt)
100% (1050000 bayt)
qatorlar: 50000 | jami: 1050000 baytRaqamlarga qarang: 327 680 = 5 × 65 536. createReadStream faylni 64 KB lik bo'laklarda o'qiydi, shuning uchun progress bo'lak chegarasida yangilanadi. bayt === 10 — \n belgisining bayt kodi: biz satrga aylantirmasdan, baytlarni to'g'ridan-to'g'ri sanadik.
Real CLI'da ikki qoida bor:
- Progress stderr ga yoziladi (
process.stderr.write), natija esa stdout'ga. Shundavalyuta tahlil katta.csv > natija.txtqilinganda faylga faqat natija tushadi. - Progress faqat terminalda chiqadi:
process.stderr.isTTY(readline va terminal). Skript ichida chaqirilsa, foizlar log'ni ifloslantirmaydi.
Kanondagi valyuta tahlil progress chiqarmaydi — 41 MB fayl 0.8 soniyada tugaydi. Uni qo'shish — 3-mashq.
6. Xatoda qisman natija
Million qatorning 700 000-sida buzuq qator chiqdi. Nima qilish kerak? Ikki siyosat bor va ikkalasi ham to'g'ri bo'lishi mumkin — muhimi, tanlov ongli bo'lsin.
1. To'xtatish (fail fast). Birinchi buzuq qatorda xato beriladi va qator raqami aytiladi. Yarim natija chiqarilmaydi. Kanon shu yo'lni tanladi: noto'g'ri kurs — buzilgan eksport belgisi, uning ustidagi o'rtacha ishonchsiz.
import { createReadStream, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
writeFileSync("tarix.csv", ["sana,kod,kurs", "2026-10-21,USD,12010",
"2026-10-22,USD,12020", "2026-10-23,USD,abc",
"2026-10-24,USD,12040"].join("\n"));
let raqam = 0;
let yigindi = 0;
try {
const qatorlar = createInterface({
input: createReadStream("tarix.csv"),
crlfDelay: Infinity,
});
for await (const qator of qatorlar) {
if (++raqam === 1) continue;
const kurs = Number(qator.split(",")[2]);
if (!Number.isFinite(kurs)) {
throw new Error(`${raqam}-qator: kurs son emas`);
}
yigindi += kurs;
}
} catch (xato) {
console.log("Xato:", xato.message);
console.log("o'qilgan ma'lumot qatorlari:", raqam - 2);
}Konsolda:
Xato: 4-qator: kurs son emas
o'qilgan ma'lumot qatorlari: 2Xabarda qator raqami bor — foydalanuvchi faylni ochib, aynan o'sha joyni tuzatadi. raqam - 2: sarlavha va buzuq qatorning o'zi hisobga olinmaydi. throw for await sikldan chiqaradi, readline yopiladi. (Fayl oqimining o'zini yopish haqida — oldingi darsning 1-mashqi.)
2. O'tkazib yuborish va hisobot (skip and report). Buzuq qator tashlanadi, oxirida "N ta qator o'tkazib yuborildi: 4, 17, 93…" deb aytiladi. Bu loglar uchun yaxshi: bitta buzuq log qatori butun kunlik tahlilni to'xtatmasligi kerak. Muhim qoida — jim o'tkazib yubormaslik. Hisobotsiz tashlangan qatorlar yolg'on statistika beradi. Bu rejim — 2-mashq.
Chiqish fayli bilan ham xuddi shunday: yarim yozilgan natija fayli qolmasligi kerak. pipeline darsidagi vaqtinchalik fayl va rename buni ta'minlaydi — valyuta tarix -o shunday yozadi.
Diqqat — Ctrl+C va
.tmpfayl: holat 35 dagibin.tsCtrl+C da darholprocess.exit(130)qiladi (process obyekti).exitkutayotganpipelineni ham,catchdagirmni ham to'xtatadi. Natijada yondaeur.csv.gz.<pid>.tmpqolishi mumkin. Asosiy fayl buzilmaydi — u faqatrenamedan keyin paydo bo'ladi. Biz buni sinamadik: haqiqiy Ctrl+C terminalda (TTY) sinalmagan. Holat 46 da (Built-in fetch darsi) birinchi Ctrl+Csignalorqalipipelineni bekor qiladi va.tmpo'chiriladi; ikkinchi Ctrl+C esa yana darhol chiqadi. Qolgan.tmpni qo'lda o'chirish xavfsiz.
7. valyuta ning to'liq oqim sxemasi
Holat 35 dagi ikki buyruqni bitta rasmda ko'ring: har bo'g'in — shu qismning bir darsi.
flowchart LR
A["tarixOqimi<br/>async generator"] --> B["matnga<br/>CSV / JSON"]
B --> C["Readable.from"]
C --> D["createGzip<br/>--gzip"]
D --> E["fayl.pid.tmp<br/>keyin rename"]
F["eur.csv.gz"] --> G["createGunzip"]
G --> H["readline<br/>for await"]
H --> I["min / max /<br/>o'rtacha"]Yuqori qator — valyuta tarix: CBU'dan kunma-kun kurslar (o'z oqimingiz), matnga aylanadi, siqiladi va atomik yoziladi (pipeline). Pastki qator — valyuta tahlil: fayl ochiladi va qatorma-qator hisoblanadi (async iterator).
8. Ko'p uchraydigan xatolar
8.1 Bitta jarayonda ikki usulni o'lchash
Avval stream, keyin readFileSync — bitta skriptda. Ikkinchi o'lchovga birinchisining axlati qo'shiladi, maxRSS esa umuman jarayonning eng yuqori nuqtasi. Har usul — alohida jarayon (spawnSync(process.execPath, [...])), bir necha marta, mediana.
8.2 "Stream'da hammasi tez bo'ladi" deb kutish
O'lchovsiz qaror. Kanon o'lchovida stream 40 % sekinroq chiqdi. Xotira muammosi bo'lsa — stream; tezlik muammosi bo'lsa — avval o'lchang (CPU profiling darsida qanday qilishni o'rganamiz).
8.3 Stream ichida hammasini massivga yig'ish
// ❌ stream bilan o'qib, baribir hammasini xotiraga yig'ish
const qatorlar = [];
for await (const q of rl) qatorlar.push(q);Bu readFileSync + split ning sekinroq varianti — xotira yana fayl hajmiga bog'liq. Siklda faqat kerakli natijani yig'ing: yig'indi, son, min, max.
8.4 Generatsiyada drain ni kutmaslik
for siklda w.write() ni natijasiga qaramay chaqirsangiz, million qator xotiradagi buferga yig'iladi. Kanonning backpressure o'lchovida shunday yozish 98 MB bufer va 189 MB rss berdi. if (!w.write(q)) await once(w, "drain").
8.5 Progress'ni stdout'ga yozish
25% qatorlari natija fayliga tushadi va CSV'ni buzadi. process.stderr, va faqat isTTY bo'lsa.
9. Mashqlar
1-mashq (oson): Kim qancha xotira oladi
Javob bering (o'lchovsiz, darsdagi raqamlar asosida):
- 41 MB faylni stream bilan o'qish 63 MB oldi. 410 MB fayl uchun taxminan qancha bo'ladi? Butun fayl usulida-chi?
- Server bir vaqtda 10 ta foydalanuvchi faylini (har biri 41 MB) butun holda o'qisa, eng yomon holatda xotira taxminan qancha kerak?
Yechim
- Stream — taxminan o'sha 60–65 MB: xotira bo'lak hajmiga bog'liq, fayl hajmiga emas. Butun fayl usuli — fayl hajmiga proporsional o'sadi. 41 MB uchun ~194 − 45 (bo'sh jarayon) ≈ 150 MB qo'shimcha bo'lsa, 410 MB uchun taxminan 1.5 GB — yoki undan oldinroq V8 satr chegarasiga urilib yiqiladi. Bu — taxmin, biz 410 MB ni o'lchamadik.
- Taxminan 10 × 150 MB ≈ 1.5 GB qo'shimcha. Stream bilan esa har biri bir necha MB bufer — jami bir necha o'n MB. Server uchun stream'ning asosiy foydasi shu.
2-mashq (o'rta): O'tkazib yuborish rejimi
«Xatoda qisman natija» bo'limidagi kodni o'zgartiring: buzuq qatorda to'xtamasin, uning raqamini otkazildi massiviga qo'shsin. Oxirida o'rtacha kursni va o'tkazilgan qatorlarni chiqaring. Sinov fayli: 6 qator (sarlavha + 5), 3- va 5-qatorlar buzuq (abc, bo'sh kurs). Eslatma: Number("") — 0, shuning uchun bo'sh maydonni alohida tekshiring.
Yechim
import { createReadStream, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
writeFileSync("tarix.csv", ["sana,kod,kurs", "2026-10-20,USD,12000",
"2026-10-21,USD,abc", "2026-10-22,USD,12020", "2026-10-23,USD,",
"2026-10-24,USD,12040"].join("\n"));
let raqam = 0;
let yigindi = 0;
let soni = 0;
const otkazildi = [];
const qatorlar = createInterface({
input: createReadStream("tarix.csv"),
crlfDelay: Infinity,
});
for await (const qator of qatorlar) {
if (++raqam === 1) continue;
const maydon = qator.split(",")[2] ?? "";
const kurs = Number(maydon);
if (maydon.trim() === "" || !Number.isFinite(kurs)) {
otkazildi.push(raqam);
continue;
}
yigindi += kurs;
soni++;
}
console.log("o'rtacha:", yigindi / soni, "|", soni, "ta qatordan");
console.log("o'tkazildi:", otkazildi.join(", "));Konsolda:
o'rtacha: 12020 | 3 ta qatordan
o'tkazildi: 3, 5Bo'sh maydon tekshiruvisiz 5-qator kurs = 0 bilan o'rtachaga kirib, uni 9015 ga tushirgan bo'lardi — jim, noto'g'ri natija. Hisobot qatori esa foydalanuvchiga natija nechta qatordan hisoblanganini halol aytadi.
3-mashq (qiyin): tahlil ga progress
Progress Transform'ini qayta ishlatiladigan funksiya qiling: progressOqimi(jami, xabar) — har 25 % da xabar(foiz) ni chaqirsin. Uni fayl va readline orasiga qo'ying: createInterface({ input: fayl.pipe(progressOqimi(...)) }). Xabarlar console.error ga chiqsin (stderr), natija — console.log ga. Eslatma: pipe xatoni uzatmaydi — fayl xatosini progress oqimiga destroy bilan o'tkazing (pipeline darsi).
Yechim
import { createReadStream, statSync, writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
import { Transform } from "node:stream";
function progressOqimi(jami, xabar) {
let otgan = 0;
let keyingi = 25;
return new Transform({
transform(bolak, _kod, tayyor) {
otgan += bolak.length;
while ((otgan / jami) * 100 >= keyingi) {
xabar(keyingi);
keyingi += 25;
}
tayyor(null, bolak);
},
});
}
writeFileSync("k.csv", "sana,kod,kurs\n"
+ "2026-10-23,USD,12030\n".repeat(40_000));
const fayl = createReadStream("k.csv");
const progress = progressOqimi(statSync("k.csv").size,
(f) => console.error(`${f}%`));
fayl.on("error", (x) => progress.destroy(x));
let soni = 0;
const rl = createInterface({
input: fayl.pipe(progress),
crlfDelay: Infinity,
});
for await (const q of rl) if (q.includes(",USD,")) soni++;
console.log("USD qatorlari:", soni);Konsolda:
25%
50%
75%
100%
USD qatorlari: 40000Bu chiqishda stdout va stderr aralash ko'rinadi. node yechim.mjs > natija.txt qilsangiz, faylga faqat USD qatorlari: 40000 tushadi, foizlar esa terminalda qoladi. Real CLI'da if (process.stderr.isTTY) sharti ham qo'shiladi.
4-mashq: CLI qadami — holat 35 ni yakunlash va commit
Oldingi uch darsda qo'shganlaringizni (oqimniYoz, faylniOch, matnga, tahlilQil) CLI'ga ulang va commit qiling.
src/cli.tsga ikki buyruq:valyuta tarix <KOD> --dan D [--gacha D] [-f csv|json|jadval] [-o fayl] [--gzip]vavalyuta tahlil <fayl.csv[.gz]>.parseArgssozlamasigadan,gacha,chiqish(-o),gzipqo'shiladi.src/tarix.tsdakunlar(dan, gacha)generatori vatarixOqimibo'lsin. Har sana uchun…/json/<KOD>/<sana>/so'raladi va keshda muddatsiz saqlanadi. API'dan olinsa, keyingisidan oldin 100 ms tanaffus — CBU'ga yuk bermaslik uchun. Dam olish kunlari takrori tashlanadi;--dandam olish kuniga tushsa, oldingi juma kursi ham tashlanadi.test/tarix.test.ts— 6 ta test, jami 39.
Yechim
Kanondagi commit xabari:
Tarix va tahlil: kunlik kurslar oqimi (CSV/JSON, gzip, faylga atomik), katta CSV'ni stream bilan tahlilKanon tekshiruvi (tekshiruv-35.txt, oldingi holat 24 bilan): tip va lint — 0 xato; testlar 33 → 39/39; npm pack 47 → 53 fayl, 21.6 → 26.2 kB; sovuq ishga tushish --help 77.9 → 83.8 ms (mediana, 10 marta). Prod bog'liqlik — hamon 0: hamma narsa Node'ning o'zida (node:stream, node:zlib, node:readline).
Jonli sinov — haqiqiy CBU bilan (35-jonli.txt):
valyuta tarix EUR --dan 2026-09-20 --gacha 2026-10-23 \
-f csv -o eur.csv.gz --gzip
valyuta tahlil eur.csv.gzYozildi: C:\Users\…\eur.csv.gz
[exit 0, 36230 ms, 34 so'rov]
kod kunlar min max o'rtacha davr
EUR 23 13 198.93 13 572.53 13 394.39 2026-09-22..2026-10-23Kuzatuvlar:
- 34 kun uchun 34 so'rov 36.2 soniya oldi — 100 ms tanaffusdan ancha ko'p: CBU har so'rovga ~1 soniyada javob berdi. Ikkinchi marta xuddi shu buyruq keshdan 200 ms da tugadi.
- Fayl 24 qator (sarlavha + 23 ish kuni), 895 bayt; gzip — 317 bayt. Birinchi sana —
09-22,09-20emas: 20-sentabr yakshanba, CBU 19-sentabr (juma) kursini qaytardi, u esa oraliqdan tashqarida — tashlandi. Bu kanonda topilgan va test bilan mustahkamlangan ikkinchi xato edi. valyuta tarix EUR --dan 2026-10-20 | head -2— quvur erta yopilgandaEPIPExatosi chiqmadi, exit 0.
10. Real ishda
- Ma'lumot muhandisligi: ETL (extract-transform-load — ma'lumotni olish, o'zgartirish, yuklash) skriptlari, bank va soliq eksportlari, log tahlili — hammasi shu darsdagi naqsh: oqim, siqish, progress, xato siyosati.
- Konteynerlar: serverlar ko'pincha 256–512 MB xotira bilan ishlaydi (Docker'ni 26-qismda o'rganamiz). U yerda "fayl hajmiga bog'liq xotira" — ishlab chiqarishdagi tasodifiy yiqilishlar manbai.
- Fayl yuklash API'lari: foydalanuvchi yuklagan CSV/Excel'ni import qilish — 25–27-qismlarda HTTP va baza bilan birga quramiz. Asosi — bugungi oqim.
- Intervyu savollari: "1 GB faylni qanday qayta ishlaysiz?", "
rssvaheapUsedfarqi?", "Stream har doim tezroqmi?" — oxirgisiga endi raqam bilan javob bera olasiz.
Xulosa
- Stream'ning foydasi — fayl hajmiga bog'liq bo'lmagan xotira: 41 MB faylda 63 MB va 194 MB, 16 MB heap chegarasida ham ishladi.
- Stream tezroq emas: o'lchovda 796 ms va 567 ms. Kichik, o'smaydigan fayl uchun
readFileyetarli. - Xotirani
process.resourceUsage().maxRSSbilan, har usulni alohida jarayonda o'lchang. - Siqish —
pipelinega bittacreateGzip()bo'g'ini; progress — sanovchi Transform va stderr. - Buzuq qator siyosati ongli tanlansin: to'xtatish (qator raqami bilan) yoki o'tkazib yuborish + hisobot. Jim o'tkazish — yo'q.
Keyingi dars: worker_threads — CPU'ni og'ir ishlatadigan hisobni alohida thread'ga chiqarib, event loop'ni bo'shatamiz.
Manbalar
- Node.js hujjati: process.memoryUsage() va process.resourceUsage() (v24).
- Node.js hujjati: Stream — pipeline.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!