Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Persistence: ma'lumot qayerda yashaydi
- 2.1 RAM va disk
- 2.2 Bitta jarayon — JSON ishlaydi
- 3. Parallel yozish: o'lchov
- 3.1 Tajriba
- 3.2 Bu tasodifmi?
- 3.3 Nega aynan shunday buziladi
- 3.4 Fayl buzilmasa ham: yo'qolgan yangilanish
- 4. Yaxlitlik: kim qoidani nazorat qiladi
- 4.1 JSON hamma narsani qabul qiladi
- 4.2 Baza qoidani o'zi tekshiradi
- 5. DBMS: baza nimalarni o'z zimmasiga oladi
- 5.1 Ta'rif
- 5.2 JSON fayl va DBMS yonma-yon
- 6. Mijoz-server modeli
- 6.1 Baza — alohida server
- 6.2 Har ulanish — alohida jarayon
- 6.3 Ichki baza: SQLite
- 7. SQL qadami: bu darsda nima qildik
- 8. Ko'p uchraydigan xatolar
- 8.1 "Bizda foydalanuvchi kam, JSON yetadi"
- 8.2 Buzilgan JSON xatosi
- 8.3 "Baza sekin, fayl tez"
- 8.4 Bazani konteynerda volume'siz ishlatish
- 9. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): natijani oldindan ayting
- 3-mashq (qiyin): bitta jarayon ichida navbat
- 4-mashq: SQL qadami — tajribani o'zingiz takrorlang
- 10. Real ishda
- Xulosa
- Manbalar
Ma'lumotlar bazasi nima va nega kerak: JSON fayl bilan o'lchangan farq
Qisqacha: Ma'lumotlar bazasini boshqarish tizimi (DBMS) — ma'lumotni diskda xavfsiz saqlaydigan va unga ko'p foydalanuvchining bir vaqtda to'g'ri kirishini ta'minlaydigan dastur. Oddiy JSON fayl bitta foydalanuvchi uchun ishlaydi, lekin bir nechta jarayon bir vaqtda yozganda yozuvlar yo'qoladi va fayl buziladi. O'lchovimizda 10 ta jarayon 1000 ta vazifa yozdi: JSON faylda oxirida 0 ta qoldi (fayl buzildi), PostgreSQL'da — 1000 tadan 1000 ta.
Bu darsda
- Persistence nima ekanini va RAM bilan disk farqini tushuntira olasiz.
- JSON faylga parallel yozishda nima uchun ma'lumot yo'qolishini haqiqiy o'lchov bilan ko'rsatasiz.
- "Yo'qolgan yangilanish" (lost update) va yaxlitlik muammosini kod bilan ko'rsata olasiz.
- DBMS qanday vazifalarni o'z zimmasiga olishini sanab bera olasiz.
- Mijoz-server modelini va PostgreSQL'da har ulanish alohida jarayon ekanini bilasiz.
Oldin bilishingiz kerak: fs: uch API shakli, Xavfsiz fayl operatsiyalari, child_process, Volume va bind mount.
1. Nega bu kerak?
25-qismda vazifalar-api ni qurdingiz. Ma'lumot repository ichida, oddiy massivda — operativ xotirada turardi. Serverni qayta ishga tushirsangiz, hamma vazifa yo'qoladi. Real ilova uchun bu yaramaydi.
Birinchi xayolga keladigan yechim: massivni JSON faylga yozamiz. fs: uch API shakli darsida readFile va writeFile ni o'rgangansiz — bir necha qatorda bo'ladi. Haqiqatan ham, bitta foydalanuvchi uchun bu ishlaydi.
Muammo boshqa joyda boshlanadi: bir vaqtda ko'p foydalanuvchi. Malika vazifa qo'shayotganda, Jasur ham vazifa qo'shyapti. Server ikki so'rovni bir paytda bajaradi. Keyinroq ilova o'sib, ikkita server bo'ladi. Ikkalasi bitta faylga yozadi. Shunda nima bo'ladi?
Bu darsda bu savolga taxmin bilan emas, o'lchov bilan javob beramiz. Hayotiy o'xshatish: maktabdagi bitta jurnal. O'qituvchi bitta bo'lsa — hammasi tartibda. Endi o'nta o'qituvchi bir vaqtda o'sha sahifaga baho yozayotganini tasavvur qiling. Har biri o'z bahosini yozish uchun eski yozuvni o'chirib, qaytadan ko'chirib yozadi. Jurnal tez orada o'qib bo'lmaydigan holga keladi. Ma'lumotlar bazasi — shu jurnalni boshqaradigan qat'iy kotib: hamma unga aytadi, yozishni faqat u qiladi.
2. Persistence: ma'lumot qayerda yashaydi
2.1 RAM va disk
Persistence (saqlanuvchanlik) — ma'lumot uni yaratgan dastur to'xtagandan keyin ham saqlanib qolishi. Buning uchun ma'lumot doimiy xotiraga — diskka yozilishi kerak.
| Xususiyat | Operativ xotira (RAM) | Disk (SSD, HDD) |
|---|---|---|
| Tezlik | juda tez | ancha sekin |
| Dastur to'xtasa | o'chadi | qoladi |
| Tok o'chsa | o'chadi | qoladi |
| Hajm | gigabaytlar | terabaytlar |
JavaScript o'zgaruvchilari, massivlar, obyektlar — hammasi RAM'da. vazifalar-api dagi massiv ham. Diskka yozish uchun maxsus harakat kerak: writeFile, yoki baza.
2.2 Bitta jarayon — JSON ishlaydi
Avval adolatli bo'laylik: JSON fayl o'z ishini qila oladi. Vazifa qo'shadigan oddiy funksiya — o'qi, qo'sh, yoz. Bu blok Node'da ishlaydi (saytda tugmasi yo'q — o'z terminalingizda .mjs faylda sinang):
import { readFile, writeFile } from "node:fs/promises";
const FAYL = "vazifalar.json";
await writeFile(FAYL, "[]");
async function vazifaQosh(matn) {
const royxat = JSON.parse(await readFile(FAYL, "utf8"));
royxat.push({ id: royxat.length + 1, matn });
await writeFile(FAYL, JSON.stringify(royxat));
}
await vazifaQosh("Non olish");
await vazifaQosh("Ingliz tili darsi");
console.log(await readFile(FAYL, "utf8"));Konsolda:
[{"id":1,"matn":"Non olish"},{"id":2,"matn":"Ingliz tili darsi"}]Ikkala vazifa joyida, id lar to'g'ri. Kalit so'z — await: ikkinchi chaqiruv birinchisi tugagandan keyin boshlandi. Ish ketma-ket bo'lsa, JSON fayl yetarli.
Tekshirib ko'ring:
writeFilemuvaffaqiyatli tugadi. Shu soniyada kompyuterning toki o'chsa, fayl diskda bo'ladimi?
Javob
Kafolat yo'q. Operatsion tizim yozishni avval o'z keshida (RAM'da) saqlaydi va diskka keyinroq tushiradi. writeFile tugashi "tizimga topshirildi" degani, "diskka yozildi" emas. Kafolat uchun maxsus fsync chaqiruvi kerak — Xavfsiz fayl operatsiyalari darsida atomik yozish bilan ko'rgansiz. Ma'lumotlar bazasi buni har tasdiqlangan yozuv uchun o'zi qiladi.
3. Parallel yozish: o'lchov
3.1 Tajriba
Endi real holat: 10 ta mustaqil Node jarayoni bitta faylga bir vaqtda yozadi. Har biri 100 ta vazifa qo'shadi — jami 1000 ta bo'lishi kerak. Jarayonlarni child_process darsidagi fork bilan ishga tushiramiz. Bu ikkita server bitta faylga yozgandagi holatning modeli.
Har jarayonning ishi — o'sha o'qi-qo'sh-yoz, faqat 100 marta. Yozuvchi qismi (to'liq JSON skripti — 4-mashqda):
for (let i = 0; i < 100; i++) {
let royxat;
try {
royxat = JSON.parse(await readFile(FAYL, "utf8"));
} catch {
buzilgan++; // boshqa jarayon yozib ulgurmagan faylni o'qidi
continue;
}
royxat.push({ id: royxat.length + 1, matn: `vazifa ${i}` });
await writeFile(FAYL, JSON.stringify(royxat));
}Xuddi shu 10 × 100 yozuvni PostgreSQL jadvaliga ham yozdik — har jarayon o'z ulanishi bilan INSERT qildi. Postgres kodi bu yerda kerak emas, uni node-postgres asoslari darsida o'rganasiz.
Kurs mashinasidagi natija (Node 24.21.0, Windows 11, i5-12500H, PostgreSQL 18.6 Docker'da):
yakuniy JSON buzilgan: Unexpected non-whitespace character after JSON at position 539 (line 1 column 540)
JSON fayl: kutilgan 1000, faylda 0; takror id: 0; o'qishda buzilgan JSON: 923 marta; 686 ms
Postgres: kutilgan 1000, jadvalda 1000; takror id: 0; 995 msGrafikda:
- Kutilgan1 000 ta
- JSON faylyakuniy fayl buzilgan0 ta
- PostgreSQLtakror id 01 000 ta
Manba: IlmHamroh kanon o'lchovi, 2026-10-24: Node 24.21.0, Windows 11, i5-12500H, PostgreSQL 18.6 (Docker)
Nimaga qarang: JSON faylda 1000 ta emas, hatto 500 ta ham emas — nol. Oxirgi yozuvdan keyin fayl shunchalik buzildiki, uni JSON.parse umuman o'qiy olmadi. Bunday faylni ilova ochib bo'lmaydi — butun ma'lumot yo'qolgan.
3.2 Bu tasodifmi?
Bitta o'lchovga ishonmaslik kerak. Tajribani o'zimiz yana uch marta takrorladik — o'sha kompyuterda, lekin PostgreSQL 17.6 bilan (Windows'ga o'rnatilgan, Docker'siz):
| Urinish | O'qishda buzilgan JSON | Yakuniy JSON fayl | PostgreSQL |
|---|---|---|---|
| 1 | 949 marta | buzilgan | 1000 / 1000 |
| 2 | 952 marta | buzilgan | 1000 / 1000 |
| 3 | 906 marta | buzilgan | 1000 / 1000 |
Raqamlar har safar boshqacha, natija esa bir xil: JSON fayl buziladi, baza hammasini saqlaydi. Postgres versiyasi ham, Docker ham bunda rol o'ynamadi.
3.3 Nega aynan shunday buziladi
Xato xabarini tarjima qilamiz: Unexpected non-whitespace character after JSON at position 539 — "JSON'dan keyin, 539-pozitsiyada kutilmagan (bo'sh joy bo'lmagan) belgi". Ya'ni faylning boshida to'g'ri JSON bor, lekin u tugagandan keyin yana qandaydir belgilar davom etyapti.
Bunday bo'lishi uchun ikki jarayon bir faylga deyarli bir vaqtda yozgan bo'lishi kerak:
writeFilefaylni ochadi va bo'shatadi (eski matnni o'chiradi), keyin yangi matnni boshidan yozadi.- A jarayoni 800 baytlik ro'yxat yozdi.
- B jarayoni faylni ochdi, uni bo'shatdi — lekin A ning yozish operatsiyasi hali tugamagan edi. B 400 baytlik eski ro'yxatini boshidan yozdi.
- Natija: boshida B ning 400 bayti (to'liq JSON), keyin A ning 400-baytdan keyingi qoldig'i.
JSON.parse 400-baytgacha o'qiydi, keyin "JSON tugadi, nega yana belgi bor?" deb xato beradi. O'qish paytidagi 923 ta xato ham shu: jarayon boshqasi hali yozib bo'lmagan yarim faylni o'qigan.
3.4 Fayl buzilmasa ham: yo'qolgan yangilanish
Fayl buzilmasa ham muammo bor. Jarayonlar bir-birini "ko'rmaydi". Sxemaga qarang — ikki jarayon bir vaqtda o'qiydi:
sequenceDiagram
participant A as Jarayon A
participant F as vazifalar.json
participant B as Jarayon B
A->>F: o'qish: []
B->>F: o'qish: []
A->>F: yozish: [Non olish]
B->>F: yozish: [Dars qilish]
Note over F: "Non olish" yo'qoldiIkkalasi bo'sh ro'yxatni o'qidi. Har biri o'z vazifasini qo'shib, butun ro'yxatni qayta yozdi. Oxirgi yozgan g'olib — birinchisining ishi izsiz yo'qoldi. Hech qanday xato chiqmadi. Bu holat yo'qolgan yangilanish (lost update) deyiladi.
Bu muammo uchun ikki jarayon ham shart emas. Bitta Node serveri ichida ham await paytida boshqa so'rov bajarilib ulguradi. Mana jonli misol — fayl o'rniga oddiy satr, disk sekinligi o'rniga setTimeout. Saytda «Ishga tushir» bilan sinang:
const ombor = { matn: "[]" }; // fayl o'rnida oddiy satr
const kut = (ms) => new Promise((tayyor) => setTimeout(tayyor, ms));
async function vazifaQosh(matn, kechikish) {
const royxat = JSON.parse(ombor.matn); // 1. o'qish
await kut(kechikish); // 2. "disk" sekin ishlaydi
royxat.push(matn);
ombor.matn = JSON.stringify(royxat); // 3. yozish
}
await Promise.all([
vazifaQosh("Non olish", 20),
vazifaQosh("Dars qilish", 10),
]);
console.log(ombor.matn);Konsolda:
["Non olish"]Ikki vazifa qo'shildi, faqat bittasi qoldi. Ikkalasi ham [] ni o'qidi. "Dars qilish" 10 ms da yozdi, "Non olish" 20 ms da ustidan yozdi. Promise kombinatorlari darsidagi Promise.all ikkala ishni parallel boshladi. Express server ikki so'rovni xuddi shunday parallel bajaradi.
Tekshirib ko'ring: Kechikishlarni almashtirsangiz (
"Non olish", 10va"Dars qilish", 20), natija nima bo'ladi?
Javob
["Dars qilish"]. Endi "Non olish" birinchi yozadi, "Dars qilish" esa keyin ustidan yozadi. Qaysi vazifa yo'qolishi — kim oxirida yozganiga bog'liq. Real serverda bu tarmoq va disk tezligiga bog'liq, ya'ni oldindan aytib bo'lmaydi. Shuning uchun bunday xatolar sinovda ko'rinmay, foydalanuvchilar ko'payganda chiqadi.
4. Yaxlitlik: kim qoidani nazorat qiladi
4.1 JSON hamma narsani qabul qiladi
Yaxlitlik (integrity) — ma'lumot qoidalarga mos va o'zaro izchil bo'lishi. Masalan: vazifa matni bo'sh bo'lmasin, har vazifaning egasi haqiqatan mavjud bo'lsin, ikki foydalanuvchining emaili bir xil bo'lmasin.
JSON faylda bu qoidalarni hech kim tekshirmaydi:
const royxat = [];
royxat.push({ id: 1, matn: "Non olish" });
royxat.push({ id: 1, matn: "" }); // takror id, bo'sh matn
royxat.push({ id: "uch", matn: null }); // id satr, matn yo'q
console.log(JSON.stringify(royxat));Konsolda:
[{"id":1,"matn":"Non olish"},{"id":1,"matn":""},{"id":"uch","matn":null}]Uchta yozuvdan ikkitasi buzuq, lekin JSON ularni bemalol saqladi. Tekshiruvni ilova kodida qilish mumkin — Zod 4 serverda darsida Zod bilan qilgansiz. Lekin bitta faylga ikkinchi dastur yozsa (admin skripti, boshqa servis), u sizning Zod sxemangizni bilmaydi.
4.2 Baza qoidani o'zi tekshiradi
Ma'lumotlar bazasida qoida ma'lumotning yoniga yoziladi va har yozuvda tekshiriladi — kim yozayotganidan qat'i nazar. Quyidagi SQL'ni hali o'qish shart emas: uni SQL tili va CREATE TABLE va cheklovlar darslarida o'rganasiz. Hozir faqat natijaga qarang.
Jadval yaratamiz: matn bo'sh bo'lmasin (NOT NULL, CHECK), id ni baza o'zi beradi:
CREATE TABLE t04_vazifalar (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
matn text NOT NULL CHECK (matn <> '')
);
INSERT INTO t04_vazifalar (matn) VALUES ('Non olish');Natija:
CREATE TABLE
INSERT 0 1Endi bo'sh matn yozishga urinamiz:
INSERT INTO t04_vazifalar (matn) VALUES ('');Natija:
ERROR: new row for relation "t04_vazifalar" violates check constraint "t04_vazifalar_matn_check"
DETAIL: Failing row contains (2, ).Tarjimasi: "t04_vazifalar jadvalidagi yangi qator t04_vazifalar_matn_check cheklovini buzadi. Rad etilgan qator: (2, bo'sh)". Matnsiz (NULL) yozsak ham:
INSERT INTO t04_vazifalar (matn) VALUES (NULL);Natija:
ERROR: null value in column "matn" of relation "t04_vazifalar" violates not-null constraint
DETAIL: Failing row contains (3, null).Jadvalda faqat to'g'ri yozuv qoldi:
SELECT * FROM t04_vazifalar;Natija:
id | matn
----+-----------
1 | Non olish
(1 row)Bu bloklarni biz o'z PostgreSQL 17.6 bazamizda ishga tushirdik. Kurs tajribasida 18.6 ham aynan shu xabarni berdi (xato kodi 23514 bilan). E'tibor bering: id ni biz bermadik — baza o'zi 1 qo'ydi. Takror id muammosi ham yo'q.
5. DBMS: baza nimalarni o'z zimmasiga oladi
5.1 Ta'rif
Ma'lumotlar bazasi (database) — tartiblangan holda saqlanadigan ma'lumotlar to'plami. Ma'lumotlar bazasini boshqarish tizimi (DBMS, database management system) — shu ma'lumotni saqlaydigan, o'qiydigan va himoya qiladigan dastur. PostgreSQL, MySQL, SQLite, MongoDB — hammasi DBMS. Gapda ko'pincha ikkalasini ham shunchaki "baza" deyishadi.
5.2 JSON fayl va DBMS yonma-yon
| Vazifa | JSON fayl | DBMS (PostgreSQL) |
|---|---|---|
| Parallel yozish | buziladi, yozuv yo'qoladi | navbat va qulflar bilan to'g'ri |
| Yaxlitlik | ilova kodiga bog'liq | cheklovlar bazada |
| Qidiruv | butun faylni o'qib, filter |
indeks bilan bir zumda |
| Tok o'chishi | yarim fayl qolishi mumkin | jurnal bilan tiklanadi |
Ikki qatorni raqam bilan to'ldiramiz — ikkalasi ham kurs o'lchovlaridan:
- Qidiruv. 2 million vazifali jadvalda bitta matnni topish: indekssiz 149.7 ms, indeks bilan 0.084 ms — 1 700 baravardan ko'p tezroq (PostgreSQL 18.6, kurs mashinasi). JSON faylda esa 2 million yozuvni avval RAM'ga yuklash kerak bo'lardi. Indeks — kitob oxiridagi alifbo ko'rsatkichiga o'xshash yordamchi tuzilma. Uni Indeks nima: B-tree ichidan darsida o'lchaymiz.
- Tok o'chishi. Postgres jarayonini
killbilan "o'ldirib" ko'rdik: tasdiqlangan yozuv qayta ishga tushgandan keyin joyida edi, tasdiqlanmagani — yo'q. Bu xususiyat ACID darsida ochiladi.
Bundan tashqari DBMS uch narsa beradi. So'rov tili — SQL: "bu oyda bajarilmagan vazifalarni egasi bo'yicha sanab ber" kabi savol bitta gap bilan beriladi. Foydalanuvchi huquqlari — kim o'qiydi, kim yozadi. Tranzaksiya — bir nechta o'zgarishni "hammasi yoki hech biri" qilib bajarish. Tranzaksiyani Tranzaksiya asoslari darsida o'rganasiz.
6. Mijoz-server modeli
6.1 Baza — alohida server
PostgreSQL — mijoz-server (client-server) modelidagi DBMS. Baza alohida dastur sifatida doim ishlab turadi va tarmoq portida (sukut bo'yicha 5432) ulanishlarni kutadi. Ilovangiz, terminal mijozi psql, grafik dasturlar — hammasi mijoz: ulanadi, so'rov yuboradi, javob oladi.
flowchart LR
N["Node ilova"] -->|"TCP 5432"| S["PostgreSQL server"]
P["psql terminalda"] -->|"TCP 5432"| S
G["Grafik mijoz"] -->|"TCP 5432"| S
S --> D["Disk: ma'lumot fayllari"]Faqat server diskdagi fayllarga tegadi. Mijozlar fayllarni bilmaydi ham. Shuning uchun 10 ta jarayon bir vaqtda yozsa ham, fayl buzilmaydi. Hamma yozuv bitta "kotib" orqali o'tadi. HTTP server darsidagi model bilan bir xil — faqat protokol HTTP emas, PostgreSQL'ning o'z protokoli.
6.2 Har ulanish — alohida jarayon
PostgreSQL har mijoz ulanishi uchun serverda alohida jarayon ochadi. Buni ko'rish mumkin. Bir mijoz (ali_ilovasi nomi bilan) uzoq so'rov bajarayotganda, ikkinchi mijoz — psql dan ulanishlar ro'yxatini so'radik:
SELECT pid, application_name, client_addr, state
FROM pg_stat_activity
WHERE backend_type = 'client backend';Natija:
pid | application_name | client_addr | state
-------+------------------+-------------+--------
20760 | ali_ilovasi | 127.0.0.1 | active
25400 | psql | 127.0.0.1 | active
(2 rows)Ikki mijoz — ikki xil pid, ya'ni ikkita alohida operatsion tizim jarayoni (jarayon nima). Bu chiqishni biz o'z PostgreSQL 17.6 bazamizda oldik; pid raqamlari har safar boshqacha bo'ladi. pg_stat_activity — Postgres'ning hozirgi ulanishlarni ko'rsatadigan tizim jadvali.
Bu dizaynning narxi bor: har ulanish serverda xotira egallaydi. Shuning uchun ilovalar har so'rovga yangi ulanish ochmaydi, ulanishlarni qayta ishlatadi — Connection pooling darsida.
6.3 Ichki baza: SQLite
Hamma DBMS server emas. SQLite — ichki (embedded) baza: alohida server yo'q, kutubxona ilova jarayonining ichida ishlaydi va bitta faylga yozadi. Node.js nima darsida process.versions da sqlite qatorini ko'rgansiz — Node 24 ichida SQLite bor. Telefon ilovalari, brauzerlar, kichik vositalar SQLite ishlatadi.
SQLite JSON fayldan ancha ishonchli: u ham parallel kirishni boshqaradi va qoidalarni tekshiradi. Lekin bitta faylda, bitta kompyuterda yashaydi. Bir nechta server bitta bazaga ulanishi kerak bo'lsa — PostgreSQL kabi mijoz-server baza. Bu kursda PostgreSQL'ni tanlaganimizning sababi shu. SQLite haqida — alohida darsda.
7. SQL qadami: bu darsda nima qildik
Bu darsda vazifalar sxemasiga hech narsa qo'shilmaydi va commit yo'q — faqat tajriba. Biz isbotladik: vazifalar-api ning ma'lumotini JSON faylga yozish ko'p foydalanuvchida buziladi. Keyingi darslarda PostgreSQL'da vazifalar bazasini qadamma-qadam quramiz:
- Relatsion model — jadval, qator, ustun, kalitlar;
- PostgreSQL o'rnatish va ulanish — Docker'da doimiy konteyner, nomli volume bilan;
- CREATE TABLE va cheklovlar — birinchi haqiqiy jadvallar va commit.
8. Ko'p uchraydigan xatolar
8.1 "Bizda foydalanuvchi kam, JSON yetadi"
Yo'qolgan yangilanish uchun ko'p foydalanuvchi kerak emas — ikkita bir vaqtdagi so'rov yetadi. Masalan, telefon sekin internetda bitta so'rovni ikki marta yuborsa. Bunday xato sinovda chiqmaydi va keyin "ba'zan vazifa yo'qolib qoladi" degan shikoyat bo'lib qaytadi.
8.2 Buzilgan JSON xatosi
4-mashqdagi tajribadan (2 ta jarayon) qolgan buzilgan faylni ochdik. Uning to'liq matni — 331 bayt:
[{"id":1,"matn":"vazifa 0"},{"id":2,"matn":"vazifa 1"},{"id":3,"matn":"vazifa 2"},{"id":4,"matn":"vazifa 3"},{"id":5,"matn":"vazifa 0"},{"id":6,"matn":"vazifa 1"},{"id":7,"matn":"vazifa 7"},{"id":8,"matn":"vazifa 8"},{"id":9,"matn":"vazifa 9"},{"id":10,"matn":"vazifa 10"},{"id":11,"matn":"vazifa 11"},{"id":12,"matn":"vazifa 7"}]]Oxiriga qarang: }]] — to'g'ri JSON ] bilan tugagan, undan keyin yana bitta ]. Bu uzunroq eski yozuvning qoldig'i — «Nega aynan shunday buziladi» bo'limidagi mexanizmning o'zi. Yana bir iz: vazifa 0 va vazifa 1 ikki marta bor, vazifa 4–6 esa yo'q — ikki jarayonning yozuvlari aralashib, bir qismi yo'qolgan. Ilova shu faylni JSON.parse bilan o'qisa va xatoni ushlamasa (Node 24.21.0 da ishga tushirdik):
SyntaxError: Unexpected non-whitespace character after JSON at position 330 (line 1 column 331)330-pozitsiya — aynan ortiqcha ]. Ilova ishga tushmaydi. Agar faylga bir nechta jarayon yozsa, bu xato ko'pincha parallel yozishning izi. Yechim — xatoni yashirish emas, saqlash usulini o'zgartirish. Bir jarayon ichida vaqtinchalik yechim bor (3-mashq), lekin to'g'ri yechim — baza.
8.3 "Baza sekin, fayl tez"
Oddiy yozuvda fayl tezroq ko'rinishi mumkin: bizning o'lchovda JSON 686 ms, Postgres 995 ms. Lekin JSON bu vaqtda ma'lumotni yo'qotdi. Noto'g'ri natijani tez berish foydasiz. Qidiruvda esa baza indeks bilan ko'p marta tezroq.
8.4 Bazani konteynerda volume'siz ishlatish
Baza ma'lumotni xavfsiz saqlaydi — lekin faqat o'z ma'lumotlar katalogida. Konteyner volume'siz bo'lsa, docker rm bilan hammasi ketadi. Volume va bind mount darsini eslang.
9. Mashqlar
Mashqlar kurs/mashqlar/26/04-nega-baza/ da.
1-mashq (oson): bo'sh joylarni to'ldiring
- Dastur to'xtagandan keyin ham ma'lumot saqlanishi — .
- Ikki yozuvchi bir xil eski holatni o'qib, biri ikkinchisining ishini o'chirishi — yo'qolgan .
- Ma'lumotni saqlab, unga ko'p mijozning to'g'ri kirishini ta'minlaydigan dastur — .
- PostgreSQL'ning sukut porti — .
- Server'siz, ilova ichida ishlaydigan baza — .
Yechim
Persistence — RAM'dan diskka. Lost update — oxirgi yozgan g'olib, xato chiqmaydi. DBMS — PostgreSQL, MySQL, SQLite, MongoDB. 5432 — Postgres porti; Docker'da -p 127.0.0.1:5498:5432 kabi boshqa host portiga bog'lash mumkin. SQLite — ichki (embedded) baza.
2-mashq (o'rta): natijani oldindan ayting
Darsdagi ombor misolida uchta vazifa qo'shiladi:
await Promise.all([
vazifaQosh("A", 30),
vazifaQosh("B", 10),
vazifaQosh("C", 20),
]);ombor.matn nima bo'ladi? Keyin Promise.all o'rniga uchta ketma-ket await vazifaQosh(...) yozilsa-chi?
Yechim
Parallelda: ["A"]. Uchalasi [] ni o'qidi. B 10 ms da ["B"] yozdi, C 20 ms da ["C"] ni ustidan yozdi, A 30 ms da ["A"] ni. Eng sekini g'olib.
Ketma-ket await bilan: ["A","B","C"]. Har chaqiruv oldingisi yozib bo'lgandan keyin o'qiydi. Kechikishlar endi tartibga ta'sir qilmaydi — faqat umumiy vaqt 60 ms bo'ladi.
Tekshirish uchun darsdagi jonli blokka shu qatorlarni qo'yib ko'ring.
3-mashq (qiyin): bitta jarayon ichida navbat
Bitta Node jarayoni ichida yo'qolgan yangilanishni tuzatish mumkin. Yozuvlarni navbatga qo'yamiz: bir paytda faqat bittasi o'qi-qo'sh-yoz qilsin. navbatda(ish) funksiyasini yozing: u ish funksiyasini oldingi ish tugagandan keyin chaqirsin va uning natijasini Promise sifatida qaytarsin. Oldingi ish xato bilan tugasa ham, navbat to'xtab qolmasin.
Darsdagi ombor va vazifaQosh bilan sinang: Promise.all ichida ikkala chaqiruvni navbatda(() => vazifaQosh(...)) bilan o'rang. Ishora: oxirgi Promise'ni o'zgaruvchida saqlang va har yangi ishni .then bilan unga ulang (Promise zanjiri).
Yechim
const ombor = { matn: "[]" };
const kut = (ms) => new Promise((tayyor) => setTimeout(tayyor, ms));
async function vazifaQosh(matn, kechikish) {
const royxat = JSON.parse(ombor.matn);
await kut(kechikish);
royxat.push(matn);
ombor.matn = JSON.stringify(royxat);
}
let oxirgi = Promise.resolve();
function navbatda(ish) {
const natija = oxirgi.then(ish);
oxirgi = natija.catch(() => {}); // xato navbatni to'xtatmasin
return natija;
}
await Promise.all([
navbatda(() => vazifaQosh("Non olish", 20)),
navbatda(() => vazifaQosh("Dars qilish", 10)),
]);
console.log(ombor.matn);Konsolda:
["Non olish","Dars qilish"]Endi ikkala vazifa joyida. Ikkinchi ish birinchisi tugashini kutdi va yangilangan ro'yxatni o'qidi. oxirgi = natija.catch(...) qatori muhim: usiz bitta xato butun navbatni "rad etilgan" holatda qoldiradi va keyingi hamma ish bajarilmaydi.
Lekin bu yechimning chegarasi bor: navbat faqat shu jarayonning xotirasida. Ikkinchi server yoki darsdagi 10 ta fork jarayoni bu navbatni ko'rmaydi — har birida o'z oxirgi o'zgaruvchisi. Jarayonlar orasida navbat kerak bo'lsa, hamma bitta joyga murojaat qilishi kerak. Ma'lumotlar bazasi aynan shu joy.
4-mashq: SQL qadami — tajribani o'zingiz takrorlang
Quyidagi poyga.mjs — darsdagi tajribaning faqat JSON qismi (Postgres'siz). U o'zini 10 marta fork qiladi: har bola jarayon "yozuvchi" argumenti bilan ishga tushib, bitta vazifalar.json ga 100 tadan vazifa yozadi va buzilgan o'qishlar sonini ota jarayonga process.send bilan yuboradi.
// poyga.mjs — 10 ta jarayon bitta JSON faylga 100 tadan yozadi
import { fork } from "node:child_process";
import { readFile, writeFile } from "node:fs/promises";
const FAYL = "vazifalar.json";
const [, , rol] = process.argv;
if (rol === "yozuvchi") {
let buzilgan = 0;
for (let i = 0; i < 100; i++) {
let royxat;
try {
royxat = JSON.parse(await readFile(FAYL, "utf8"));
} catch {
buzilgan++; // boshqa jarayon yozib ulgurmagan faylni o'qidi
continue;
}
royxat.push({ id: royxat.length + 1, matn: `vazifa ${i}` });
await writeFile(FAYL, JSON.stringify(royxat));
}
process.send(buzilgan);
} else {
await writeFile(FAYL, "[]");
const jarayonlar = Array.from({ length: 10 }, () =>
new Promise((tayyor) => {
fork(process.argv[1], ["yozuvchi"]).on("message", tayyor);
}),
);
const buzilganlar = await Promise.all(jarayonlar);
const jami = buzilganlar.reduce((a, b) => a + b, 0);
console.log(`O'qishda buzilgan JSON: ${jami} marta`);
try {
const royxat = JSON.parse(await readFile(FAYL, "utf8"));
console.log(`Faylda: ${royxat.length} ta (kutilgan 1000)`);
} catch (e) {
console.log(`Yakuniy fayl buzilgan: ${e.message}`);
}
}Bo'sh papkada node poyga.mjs bilan 3 marta ishga tushiring va natijalarni yozib oling. Keyin length: 10 ni length: 2 ga o'zgartirib (kutilgan son 200 bo'ladi), yana 3 marta.
Yechim
Biz Node 24.21.0 da (Windows 11, i5-12500H) 5 marta ishga tushirdik. Birinchi ikki natija:
O'qishda buzilgan JSON: 945 marta
Yakuniy fayl buzilgan: Unexpected non-whitespace character after JSON at position 412 (line 1 column 413)O'qishda buzilgan JSON: 941 marta
Yakuniy fayl buzilgan: Unexpected non-whitespace character after JSON at position 300 (line 1 column 301)Qolgan uchtasida buzilgan o'qishlar 903–930, yakuniy fayl har safar buzilgan. Sizda raqamlar boshqacha bo'ladi — bu poyga holatining (race condition) belgisi: natija jarayonlarning tasodifiy navbatiga bog'liq.
2 ta jarayon bilan ham 5 marta ishga tushirdik. 3 marta yakuniy fayl buzildi. Qolgan 2 marta fayl o'qildi, lekin unda 200 o'rniga 101 va 93 ta vazifa bor edi — qolganlari yo'qolgan yangilanish. Xulosa: jarayonlar kamaysa, xato kamroq bo'lishi mumkin, lekin yo'qolmaydi.
10. Real ishda
- Har backend'da baza bor. Foydalanuvchilar, buyurtmalar, to'lovlar — faylda emas, DBMS'da. Click va Payme kabi to'lov tizimlari aynan tranzaksiya va yaxlitlik kafolatlari uchun relatsion bazalarga tayanadi.
- Fayl qayerda qoladi: sozlamalar (
.env,config.json), loglar, rasm va hujjatlar. Rasmning o'zi fayl yoki obyekt omborida, unga havola esa bazada. - "Bizda ham shunday bo'lgan": JSON yoki CSV fayl bilan boshlangan loyiha foydalanuvchilar ko'payganda "yo'qolgan buyurtma" muammosiga duch keladi. Bazaga ko'chirish keyin qimmatga tushadi.
- Intervyu savollari: "Nega faylga emas, bazaga yozamiz?", "Race condition nima, misol keltiring", "SQLite va PostgreSQL farqi?". Yaxshi javob o'lchov va mexanizmni aytadi: "parallel yozishda lost update; baza yozuvlarni bitta server orqali o'tkazadi va cheklovlarni o'zi tekshiradi".
Xulosa
- Persistence — ma'lumotning dastur to'xtagandan keyin ham saqlanishi; RAM'dagi massiv buni bermaydi.
- JSON fayl bitta, ketma-ket yozuvchi uchun ishlaydi; 10 ta parallel jarayonda 1000 dan 0 qoldi, PostgreSQL'da 1000 dan 1000.
- Parallel yozishda ikki muammo: fayl buzilishi va yo'qolgan yangilanish (xato chiqmaydi).
- DBMS yaxlitlikni (cheklovlar), parallel kirishni, qidiruvni (indeks) va tok o'chishidan tiklanishni o'z zimmasiga oladi.
- PostgreSQL — mijoz-server: hamma mijoz port orqali ulanadi, har ulanish serverda alohida jarayon.
- SQLite — ichki baza, server'siz; bir nechta server uchun — PostgreSQL.
Keyingi dars: Relatsion model — ma'lumot jadvallarda qanday tuziladi va jadvallar kalitlar orqali qanday bog'lanadi.
Manbalar
- PostgreSQL 18 hujjati — Architectural Fundamentals (mijoz-server, har ulanishga jarayon): https://www.postgresql.org/docs/18/tutorial-arch.html
- PostgreSQL 18 hujjati — Constraints: https://www.postgresql.org/docs/18/ddl-constraints.html
- PostgreSQL 18 hujjati — pg_stat_activity: https://www.postgresql.org/docs/18/monitoring-stats.html
- Node.js hujjati — fs.writeFile (bir faylga parallel yozish xavfsiz emasligi haqida eslatma): https://nodejs.org/api/fs.html#fspromiseswritefilefile-data-options
- SQLite — Appropriate Uses For SQLite: https://www.sqlite.org/whentouse.html
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!