IlmHamroh
JavaScript Full-stack/27-qism. Ma'lumotlar bazasi chuqur: Postgres, ORM, Mongo, Redis50/65-dars16 daqiqa
Mundarija (25)

MongoDB sxema dizayn naqshlari: subset, computed, bucket, extended reference va anti-naqshlar

Qisqacha: Hujjat bazasida modellashning sinalgan yechimlari bor — naqshlar (patterns). Eng xavfli anti-naqsh — cheksiz massiv: kanonda 20 000 izohli vazifa 1.85 MB ga yetdi, findOne 4 ms dan 96 ms ga sekinlashdi. Uni subset (hujjatda faqat oxirgi 5 izoh, hammasi alohida kolleksiyada — 2.4 ms) va computed (izohlarSoni ni $inc bilan saqlash) hal qiladi. Bucket 1 440 ta daqiqalik hodisani 24 ta soatlik hujjatga yig'adi (102 KB → 49 KB). Extended reference o'qishni tezlatadi, lekin nusxalarni yangilash kerak (1 000 nusxa — 44.9 ms). Schema versioning eski va yangi shaklni bir kolleksiyada yashatadi.

Bu darsda

  • 16 MB chegarani va cheksiz massiv anti-naqshini o'lchov bilan tushunasiz.
  • Subset va computed naqshlarini $push + $slice va $inc bilan qo'llaysiz.
  • Bucket naqshi bilan vaqt qatorlarini ixcham saqlaysiz.
  • Extended reference, outlier va attribute naqshlarining narxini bilasiz.
  • Schema versioning bilan sxemani to'xtovsiz o'zgartirasiz.

Oldin bilishingiz kerak: MongoDB indekslari, Populate va embed vs reference, Denormalizatsiya va hisoblangan maydonlar, Modellash naqshlari.

1. Nega bu kerak?

Populate va embed vs reference darsida qaror qoidasini o'rgandik: chegaralangan — embed, cheksiz — reference. Lekin hayot oq-qora emas. Vazifaning izohlari odatda 2–3 ta. Lekin bitta vazifa "Kurs tugadi, fikringizni yozing!" bo'lsa, unga 20 000 izoh tushishi mumkin. Hammasini reference qilsak — oddiy vazifalar uchun ortiqcha so'rov. Hammasini embed qilsak — mashhur vazifa hujjati shishib ketadi.

Postgres'da bunday savollar kam edi: normalizatsiya, keyin kerak bo'lsa denormalizatsiya (Denormalizatsiya va hisoblangan maydonlar). Mongo'da esa shu oraliq yechimlarning nomli to'plami bor — sxema dizayn naqshlari. Ular MongoDB jamoasining "Building with Patterns" maqolalar turkumidan keng tarqalgan.

O'xshatish: oshxonadagi muzlatgich. Hamma mahsulotni muzlatgichga tiqsangiz (cheksiz embed) — eshik yopilmaydi, kerakli narsani topish qiyin. Hammasini omborda saqlasangiz (reference) — har ovqatga omborga borasiz. Yechim: kundalik kerakli narsalar muzlatgichda, qolgani omborda (subset). Va "nechta tuxum qoldi" deb har safar sanamaslik uchun eshikka yozib qo'yasiz (computed).

Hamma o'lchovlar — kanon o'lchovi (IlmHamroh, 2026-10, alohida naqsh bazasi): MongoDB 8.3.13 (Docker, WSL2), mongodb 7.7.0, Node 24.21.0, i5-12500H. Bitta o'lchov — tendensiyaga qarang, aniq millisekundga emas.

2. 16 MB chegara

MongoDB tuzilishi darsida aytdik: bitta BSON hujjat — ko'pi bilan 16 MB. Kanon chegarani buzib ko'rdi — bitta hujjatga 17 MB matn:

js
await db.collection("katta_hujjat").insertOne({
  matn: "x".repeat(17 * 1024 * 1024),
});
text
RangeError: The value of "offset" is out of range. It must be >= 0 && <= 17825792. Received 17825794

Kutilmagan joy: xatoni server emas, drayver berdi. RangeError — Node/drayverning o'z xatosi: hujjatni BSON'ga aylantirish (serializatsiya) paytida bufer chegarasidan chiqib ketdi. Tarjima: "offset qiymati ruxsat etilgan oraliqdan tashqarida: 0 dan 17 825 792 gacha bo'lishi kerak, 17 825 794 berildi". 17 825 792 bayt = 17 MB — satrning o'zi; { matn: … } ning qo'shimcha baytlari sig'madi. Ya'ni so'rov serverga yetib bormadi ham.

Xabar 16 MB haqida gapirmaydi va chalg'itadi. Shuning uchun real ilovada kattaligi oldindan bilinmaydigan maydonni (yuklangan fayl, uzun log) hujjat ichida saqlamang: fayllar — obyekt omboriga (S3 kabi) yoki GridFS'ga (hujjat bo'yicha), hujjatda faqat havola.

3. Anti-naqsh: cheksiz massiv

3.1 O'lchov

Kanon bitta vazifaga izohlarni ichiga $push bilan qo'shdi — 20 000 ta, va oraliq nuqtalarda hujjat hajmini, bitta $push vaqtini va findOne vaqtini (5 marta, mediana) o'lchadi:

js
await V.updateOne({ _id: 1 }, { $push: { izohlar: {
  muallif: i % 50,
  matn: `Izoh raqami ${i} — biroz uzunroq matn bilan`,
  vaqt: new Date(1.7e12 + i * 60000),
} } });
// hajm: { $bsonSize: "$$ROOT" } — hujjatning BSON hajmi

Har qator — bitta oraliq nuqta. izohlar o'sgan sari hajm_kb va ayniqsa findOne_ms qanday o'zgarishini kuzating:

text
{"izohlar":100,"hajm_kb":9,"push_ms":9.86,"findOne_ms":4.13}
{"izohlar":1000,"hajm_kb":90,"push_ms":8.08,"findOne_ms":6.96}
{"izohlar":5000,"hajm_kb":457,"push_ms":8.69,"findOne_ms":14.48}
{"izohlar":10000,"hajm_kb":916,"push_ms":15.54,"findOne_ms":72.72}
{"izohlar":20000,"hajm_kb":1853,"push_ms":22.98,"findOne_ms":95.71}
16 MB ga taxminan: 176838 izohda yetadi
Izohlar vazifa ichida: o'qish vaqti o'sadi
findOne, ms
95,714,1310020 000izohlar soni, tafindOne (mediana 5): 100 ta → 4,13 msfindOne (mediana 5): 1 000 ta → 6,96 msfindOne (mediana 5): 5 000 ta → 14,48 msfindOne (mediana 5): 10 000 ta → 72,72 msfindOne (mediana 5): 20 000 ta → 95,71 msbitta $push: 100 ta → 9,86 msbitta $push: 1 000 ta → 8,08 msbitta $push: 5 000 ta → 8,69 msbitta $push: 10 000 ta → 15,54 msbitta $push: 20 000 ta → 22,98 ms
  • findOne (mediana 5)
  • bitta $push
Izohlar vazifa ichida: o'qish vaqti o'sadi
izohlar sonifindOne (mediana 5)bitta $push
1004,13
1 0006,96
5 00014,48
10 00072,72
20 00095,71
1009,86
1 0008,08
5 0008,69
10 00015,54
20 00022,98

Manba: IlmHamroh kanon o'lchovi, 2026-10-25: MongoDB 8.3.13 (Docker, WSL2), mongodb 7.7.0, Node 24.21.0, i5-12500H; bitta tajriba

Grafikdan uch xulosa:

  1. O'qish chiziqli emas, keskin o'sdi. 100 izohda 4 ms, 20 000 da 96 ms. Har findOne butun hujjatni — 1.85 MB ni — tarmoqdan o'tkazib, JavaScript obyektiga aylantiradi. Vazifani ro'yxatda ko'rsatish uchun 20 000 izoh kerak emas edi, lekin ular baribir keldi.
  2. Yozish ham sekinlashdi (10 → 23 ms): katta hujjatni yangilash qimmatroq.
  3. 16 MB hali uzoq (~177 000 izoh) — lekin muammo chegaraga yetmasdan ancha oldin boshlanadi.

Bu anti-naqsh hech qanday xato bermaydi. U asta-sekin, oylar davomida ilovani sekinlashtiradi. Mashhur vazifani ochganlar shikoyat qiladi, oddiy vazifalar esa tez ishlaydi — muammoni topish qiyin.

4. Subset naqshi: faqat kerakli qism

4.1 G'oya

Subset — hujjatda massivning faqat tez-tez kerak bo'ladigan qismi (oxirgi 5 izoh), hammasi esa alohida kolleksiyada. Ro'yxat sahifasi — bitta findOne. "Hamma izohlar" sahifasi — alohida kolleksiyadan, sahifalab.

Kanon har izohni ikki joyga yozdi — izohlar kolleksiyasiga va vazifaning oxirgiIzohlar iga:

js
await I.insertOne(izoh);   // to'liq ro'yxat — alohida kolleksiya
await S.updateOne({ _id: 1 }, {
  $push: { oxirgiIzohlar: {
    $each: [{ matn: izoh.matn, vaqt: izoh.vaqt }],
    $sort: { vaqt: -1 },
    $slice: 5,
  } },
  $inc: { izohlarSoni: 1 },
});

$push ning modifikatorlari:

  • $each — qo'shiladigan elementlar massivi (modifikator ishlatish uchun shart).
  • $sort: { vaqt: -1 } — qo'shgandan keyin massivni vaqt bo'yicha kamayish tartibida saralash.
  • $slice: 5 — faqat birinchi 5 tasini qoldirish.

Natija: massiv hech qachon 5 tadan oshmaydi. Hujjat hajmi doim kichik.

4.2 Natija

2 000 izohdan keyin vazifa hujjati:

text
{"izohlarSoni":2000,"oxirgi":["Izoh raqami 2000","Izoh raqami 1999","Izoh raqami 1998","Izoh raqami 1997","Izoh raqami 1996"]}
subset findOne: 2.40 ms | 2-sahifa izohlar (keyset vaqt bo'yicha): 2.58 ms
  • findOne — 2.40 ms: 20 000 izohli embed variantdagi 96 ms o'rniga.
  • 2-sahifa (keyingi 5 izoh) — izohlar kolleksiyasidan { vazifa: 1, vaqt: { $lt: … } } + sort + limit — 2.58 ms. Bu Keyset pagination ning o'zi; { vazifa: 1, vaqt: -1 } indeksi bilan (ESR: tenglik, keyin saralash).

Tekshirib ko'ring: Subset naqshida foydalanuvchi bitta izohni o'chirdi. Qaysi joylarni yangilash kerak?

Javob

Uchta joyni: izohlar kolleksiyasidagi hujjatni, izohlarSoni ni ($inc: -1) va — agar izoh oxirgi 5 tasi orasida bo'lsa — oxirgiIzohlar massivini ($pull, keyin bo'shagan joyga izohlar dan navbatdagi izohni qo'shish). Subset'ning narxi aynan shu: ma'lumot ikki joyda.

Narxi: har izoh — ikki yozuv. Va ikki yozuv orasida jarayon yiqilsa, oxirgiIzohlar va izohlar farq qilib qolishi mumkin — kerak bo'lsa tranzaksiya (Replica set, tranzaksiyalar va change streams).

5. Computed naqshi: hisobni oldindan saqlash

Yuqoridagi $inc: { izohlarSoni: 1 } — computed naqshi. "Bu vazifada nechta izoh bor?" savoliga har o'qishda countDocuments qilish o'rniga, sonni har yozishda yangilab borish. Postgres'dagi counter cache (Denormalizatsiya va hisoblangan maydonlar).

Mongoose ilg'or darsidagi virtual izohlarSoni dan farqi: virtual har safar hisoblanadi va bazada yo'q; computed maydon bazada bor — u bo'yicha filtr, saralash va indeks mumkin ("eng ko'p muhokama qilingan vazifalar").

$inc atomik — ikki foydalanuvchi bir vaqtda izoh qo'shsa ham son to'g'ri. Xavf — izoh o'chirilganda $inc: -1 ni unutish. Vaqti-vaqti bilan haqiqiy son bilan solishtiradigan tekshiruv skripti foydali.

6. Bucket naqshi: vaqt qatorlari

6.1 Muammo

Har foydalanuvchi harakati (vazifani ko'rdi, bajardi) — hodisa. Har daqiqada bitta bo'lsa, bir kunda 1 440 ta hujjat. Har hujjatda _id, foydalanuvchi, vaqt takrorlanadi va har biri indeksga kiradi.

Bucket — hodisalarni vaqt oralig'i bo'yicha bitta hujjatga yig'ish: bir soat — bitta hujjat, ichida o'sha soatning hodisalari massivi.

6.2 O'lchov

js
const soat = new Date(h.vaqt);
soat.setUTCMinutes(0, 0, 0);          // soat boshi — bucket kaliti
await B.updateOne(
  { foydalanuvchi: 7, soat },
  {
    $push: { hodisalar: { d: h.vaqt.getUTCMinutes(), amal: h.amal } },
    $inc: { soni: 1, bajarish: h.amal === "bajarish" ? 1 : 0 },
  },
  { upsert: true },
);

Har hodisa: shu soatning hujjati bo'lmasa — upsert yaratadi, bo'lsa — massivga qo'shadi. Hodisa ichida to'liq vaqt emas, faqat daqiqa (d) — soat hujjat kalitida bor. $inc bilan bucket ichidagi computed sonlar.

text
{"alohida":{"hujjat":1440,"size_kb":102,"indeks_kb":4},"bucket":{"hujjat":24,"size_kb":49,"indeks_kb":4}}
kunlik 'bajarish' soni: alohida 480 | bucket (computed $inc) 480
  • 1 440 hujjat → 24 hujjat; 102 KB → 49 KB (takrorlanadigan maydonlar va _id lar yo'qoldi).
  • Natija bir xil: kunlik "bajarish" — 480. Bucket'da bu son tayyor — 24 ta bajarish maydonini qo'shish yetadi, 1 440 hujjatni sanash shart emas.
  • Indeks hajmi ikkalasida 4 KB — bu kichik to'plamda farq ko'rinmadi; millionlab hodisada hujjatlar soni 60 baravar kam bo'lgani uchun indeks ham kichrayadi (hisob, sinalmagan).

MongoDB 5.0 dan beri bu naqsh bazaning o'zida bor — time series kolleksiyalar (bucket'lashni server o'zi qiladi; hujjat bo'yicha, kanon sinamadi).

7. Extended reference: nusxa bilan ishora

Extended reference — reference yoniga eng ko'p kerak bo'ladigan maydonlarning nusxasi. Vazifada egasi: ObjectId o'rniga egasi: { _id, ism }. Vazifalar ro'yxati egasi ismi bilan — populate siz, $lookup siz.

Narxi — ism o'zgarsa, hamma nusxa yangilanishi kerak. Kanon 1 000 vazifali foydalanuvchining ismini o'zgartirdi:

js
await E.updateMany(
  { "egasi._id": 1 },
  { $set: { "egasi.ism": "Aziz K." } },
);
text
ism o'zgardi: 1000 nusxa yangilandi, 44.9 ms (indekssiz)

1 000 nusxa — 44.9 ms. "egasi._id" — ichma-ich maydon yo'li (nuqta bilan); indeks qo'yilsa tezroq bo'lardi. Ism kamdan-kam o'zgaradi, ro'yxat esa har daqiqada o'qiladi — savdo foydali. Populate darsidagi qaror: tez o'zgaradigan maydonni (balans, qoldiq) nusxalamang.

Nusxa yana bir savol tug'diradi: updateMany yarim yo'lda to'xtasa? Ba'zi vazifalarda eski ism qoladi. Ko'pincha bu qabul qilinadi (ism — muhim emas). Muhim ma'lumot bo'lsa — tranzaksiya yoki change streams orqali sinxronlash (keyingi dars).

8. Outlier va attribute naqshlari

Bu ikkisini kanon o'lchamadi — hujjat bo'yicha, qisqa.

Outlier — "istisno" hujjatlar uchun alohida yo'l. 99% vazifada 50 tadan kam izoh, 1% da minglab. Hammasi uchun subset murakkablik keltirmaslik uchun: oddiy hujjatda izohlar ichida, chegara oshganda hujjatga ortiqcha: true bayrog'i qo'yilib, qolganlari alohida kolleksiyaga yoziladi. Ilova bayroqni ko'rganda ikkinchi so'rov yuboradi.

js
{ _id: 1, matn: "Kurs tugadi!", izohlar: [/* birinchi 50 */],
  ortiqcha: true }        // qolganlari: izohlar_ortiqcha kolleksiyasi

Attribute — turli hujjatlarda turli nomli, lekin bir xil ma'noli maydonlar. Masalan, mahsulot xususiyatlari: telefonda xotira, ekran, ko'ylakda olcham, rang. Har biriga indeks qo'yish mumkin emas. Yechim — kalit-qiymat juftlari massivi va bitta indeks:

js
{ nom: "Telefon", xususiyatlar: [
  { k: "xotira", v: "128 GB" },
  { k: "ekran", v: "6.1" },
] }
// bitta multikey indeks: { "xususiyatlar.k": 1, "xususiyatlar.v": 1 }

So'rov: { xususiyatlar: { $elemMatch: { k: "xotira", v: "128 GB" } } } (native driver darsidagi $elemMatch). Postgres'da shu muammo uchun JSONB va GIN indeks ishlatgandik (JSONB).

9. Schema versioning

Sxema o'zgaradi. Postgres'da bu migratsiya: ALTER TABLE, hamma qatorlar yangi shaklga (Migratsiya tushunchasi). Mongo'da millionlab hujjatni bir vaqtda o'zgartirish shart emas — eski va yangi shakl birga yashashi mumkin. Schema versioning — har hujjatga versiya raqami (v) va o'qishda moslashtirish.

Kanon: foydalanuvchi sozlamalari eski shaklda (mavzu yuqori darajada) va yangi shaklda (v: 2, sozlama.mavzu ichida):

js
await SV.insertMany([
  { foydalanuvchi: 1, mavzu: "qorongi" },
  { foydalanuvchi: 2, v: 2,
    sozlama: { mavzu: "yorug", til: "uz-Latn" } },
]);
await SV.aggregate([{ $project: {
  _id: 0, foydalanuvchi: 1,
  v: { $ifNull: ["$v", 1] },
  mavzu: { $ifNull: ["$sozlama.mavzu", "$mavzu"] },
} }]).toArray();
text
[{"foydalanuvchi":1,"v":1,"mavzu":"qorongi"},{"foydalanuvchi":2,"v":2,"mavzu":"yorug"}]
  • v yo'q hujjat — 1-versiya ($ifNull: ["$v", 1]).
  • mavzu — yangi joydan, bo'lmasa eski joydan.

Tipik oqim: (1) ilova ikkala shaklni o'qiy oladigan qilinadi, (2) yangi yozuvlar v: 2 bilan, (3) fon skripti eski hujjatlarni asta-sekin yangilaydi, (4) hamma hujjat v: 2 bo'lgach, eski shaklni o'qish kodi olib tashlanadi. Bu Zero-downtime: expand-contract g'oyasining hujjat bazasidagi shakli.

Tekshirib ko'ring: 3-versiyada mavzu yana boshqa joyga — korinish.mavzu ga ko'chdi. O'qishdagi $ifNull qanday o'zgaradi?

Javob

$ifNull bir nechta argument oladi va birinchi null bo'lmaganini qaytaradi: { $ifNull: ["$korinish.mavzu", "$sozlama.mavzu", "$mavzu"] } — eng yangi joydan eskisiga qarab. Versiya ifodasi { $ifNull: ["$v", 1] } o'zgarmaydi: 3-versiyali hujjatlar v: 3 bilan yoziladi.

10. Naqshlar jadvali

Naqsh Muammo Yechim Narxi
Subset Katta massiv o'qishni sekinlatadi Hujjatda oxirgi N ta, hammasi alohida Ikki yozuv
Computed Har o'qishda sanash Sonni $inc bilan saqlash O'chirishda kamaytirishni unutmaslik
Bucket Juda ko'p mayda hujjat Vaqt oralig'i — bitta hujjat So'rovlar murakkablashadi
Extended reference Har o'qishda $lookup Kerakli maydonlar nusxasi Nusxalarni yangilash
Outlier Kam hujjat hammani murakkablashtiradi Istisnolarga alohida yo'l Ilovada ikki yo'l
Attribute Ko'p turli maydon, ko'p indeks { k, v } massivi So'rov $elemMatch bilan
Schema versioning Sxema o'zgarishi v maydoni, o'qishda moslash Kodda bir necha shakl

11. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da: izohlar alohida jadvalda (sahifalash bilan), sonlar — so'rovda yoki materialized view'da, sxema o'zgarishi — migratsiya bilan. Kanon Mongo'da:

  1. 17 MB hujjat — drayverning RangeError i (server emas).
  2. Cheksiz massiv: 20 000 izoh — 1.85 MB, findOne 4 → 96 ms.
  3. Subset + computed: findOne 2.40 ms, keyingi sahifa 2.58 ms.
  4. Bucket: 1 440 → 24 hujjat, 102 → 49 KB, son bir xil (480).
  5. Extended reference: 1 000 nusxa — 44.9 ms; schema versioning — $ifNull.

12. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
Cheksiz o'sadigan massivni embed qilish O'qish va yozish asta-sekin sekinlashadi Subset yoki reference
Fayl yoki uzun logni hujjat ichida 16 MB, chalg'ituvchi drayver xatosi Obyekt ombori/GridFS + havola
Computed sonni faqat qo'shishda yangilash O'chirishdan keyin son noto'g'ri $inc: -1 ham, davriy tekshiruv
Tez o'zgaradigan maydonni nusxalash Nusxalar bir-biridan farq qiladi Reference
Versiyasiz sxema o'zgarishi Eski hujjatlar o'qishda yiqitadi v maydoni va $ifNull
$push + $slice da $each yo'q Modifikatorlar ishlamaydi $each: [...] majburiy

13. Mashqlar

Mashqlar kurs/mashqlar/27/50-naqshlar/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik, bazasiz).

1-mashq (oson): qaysi naqsh?

  • Sahifada faqat oxirgi 3 ta sharh kerak, qolganlari "Hammasi" tugmasida —
  • Postda layklar sonini har safar sanamaslik —
  • Har soniyadagi harorat o'lchovini ixcham saqlash —
  • Buyurtmada mahsulot nomi va narxi nusxasi —
  • Ba'zi hujjatlarda telefon, ba'zilarida aloqa.telefon —
Yechim
  • subset — hujjatda N ta, hammasi alohida.
  • computed — $inc bilan saqlangan son.
  • bucket — masalan, daqiqa yoki soat — bitta hujjat.
  • extended reference — buyurtma paytidagi nom va narx; narx keyin o'zgarsa ham buyurtmada eskisi qolishi to'g'ri.
  • schema versioning — v maydoni va o'qishda $ifNull.

2-mashq (o'rta): soatlik bucket

bucketla(hodisalar) yozing: daqiqalik hodisalar ({ vaqt: Date, amal }) massividan soatlik bucket'lar massivini qaytarsin: { soat: "HH", soni, bajarish, hodisalar: [{ d, amal }] }. Kanon ma'lumotini qayta tuzing: 2026-10-24 kuni 1 440 daqiqa, har uchinchi daqiqa (i % 3 === 0) — "bajarish", qolgani — "korish". Bucket'lar sonini, birinchi bucket'ning soni va bajarish ini va kunlik bajarish yig'indisini chiqaring.

Ishora: soat kaliti — getUTCHours(); Map bilan guruhlash.

Yechim
js
function bucketla(hodisalar) {
  const m = new Map();
  for (const h of hodisalar) {
    const soat = String(h.vaqt.getUTCHours()).padStart(2, "0");
    const b = m.get(soat)
      ?? { soat, soni: 0, bajarish: 0, hodisalar: [] };
    b.hodisalar.push({ d: h.vaqt.getUTCMinutes(), amal: h.amal });
    b.soni += 1;
    b.bajarish += h.amal === "bajarish" ? 1 : 0;
    m.set(soat, b);
  }
  return [...m.values()];
}

const kun = Array.from({ length: 1440 }, (_, i) => ({
  vaqt: new Date(Date.UTC(2026, 9, 24, 0, i)),
  amal: i % 3 ? "korish" : "bajarish",
}));
const b = bucketla(kun);
console.log("bucket'lar:", b.length);
console.log(b[0].soat, b[0].soni, b[0].bajarish,
  JSON.stringify(b[0].hodisalar.slice(0, 2)));
console.log("kunlik bajarish:",
  b.reduce((s, x) => s + x.bajarish, 0));
const alohida = JSON.stringify(kun).length;
const yigilgan = JSON.stringify(b).length;
const foiz = Math.round(yigilgan / alohida * 100);
console.log("JSON hajmi:", foiz + "%");

Konsolda:

text
bucket'lar: 24
00 60 20 [{"d":0,"amal":"bajarish"},{"d":1,"amal":"korish"}]
kunlik bajarish: 480
JSON hajmi: 50%

24 bucket va kunlik 480 — kanon bilan bir xil. Har soatda 60 hodisa, 20 tasi "bajarish". JSON hajmi 50% — kanondagi BSON nisbatiga (102 → 49 KB, ya'ni 48%) juda yaqin. Sabab bir xil: har hodisada to'liq sana va takrorlanadigan maydonlar yo'qoldi.

3-mashq (qiyin): $push + $each + $sort + $slice

subsetPush(massiv, yangilar, saralash, n) yozing — MongoDB'dagi $push: { $each, $sort, $slice } ning modeli: yangilarni qo'shib, saralash ({ maydon: 1 | -1 }) bo'yicha tartiblab, birinchi n tasini qaytarsin (asl massivga tegmasin). Kanon tajribasini takrorlang: 2 000 izoh bittadan qo'shiladi, har birida izohlarSoni ham oshadi. Oxirida izohlarSoni va oxirgi matnlarni chiqaring.

Ishora: toSorted — nusxa bilan o'zgartirish.

Yechim
js
function subsetPush(massiv, yangilar, saralash, n) {
  const [[k, yon]] = Object.entries(saralash);
  return [...massiv, ...yangilar]
    .toSorted((a, b) => (a[k] - b[k]) * yon)
    .slice(0, n);
}

let vazifa = { oxirgiIzohlar: [], izohlarSoni: 0 };
for (let i = 1; i <= 2000; i++) {
  const izoh = {
    matn: `Izoh raqami ${i}`,
    vaqt: new Date(1.7e12 + i * 60000),
  };
  vazifa = {
    oxirgiIzohlar: subsetPush(vazifa.oxirgiIzohlar, [izoh],
      { vaqt: -1 }, 5),
    izohlarSoni: vazifa.izohlarSoni + 1,
  };
}
console.log(JSON.stringify({
  izohlarSoni: vazifa.izohlarSoni,
  oxirgi: vazifa.oxirgiIzohlar.map((x) => x.matn),
}));

Konsolda:

text
{"izohlarSoni":2000,"oxirgi":["Izoh raqami 2000","Izoh raqami 1999","Izoh raqami 1998","Izoh raqami 1997","Izoh raqami 1996"]}

Kanon chiqishi bilan aynan bir xil. Massiv 2 000 qadam davomida hech qachon 5 tadan oshmadi. a[k] - b[k] — ikki Date ayirmasi millisekundlarda; * yon — -1 da teskari tartib. MongoDB'da bu bitta atomik updateOne ichida bajariladi.

14. Real ishda

  • Ijtimoiy tarmoq va chat — subset (oxirgi xabarlar hujjatda), computed (layklar, javoblar soni), outlier (mashhur postlar).
  • IoT va monitoring — bucket yoki time series kolleksiyalar; sensor har soniyada yozadi.
  • E-commerce — extended reference (buyurtmada mahsulot nomi va narxi), attribute (turli xususiyatli mahsulotlar).
  • Intervyu savollari: "Mongo'da bitta postga million izoh — qanday saqlaysiz?", "16 MB chegaraga qanday yetmaslik kerak?", "sxemani to'xtovsiz qanday o'zgartirasiz?".

Xulosa

  • 16 MB — qattiq chegara; 17 MB hujjatni drayverning o'zi RangeError bilan to'xtatdi.
  • Cheksiz massiv — eng xavfli anti-naqsh: 20 000 izoh — 1.85 MB, findOne 4 → 96 ms, xatosiz.
  • Subset ($push + $each/$sort/$slice) + computed ($inc) — findOne 2.40 ms; to'liq ro'yxat alohida, keyset bilan.
  • Bucket: 1 440 → 24 hujjat, 102 → 49 KB; extended reference: o'qish tez, 1 000 nusxa yangilash 44.9 ms.
  • Outlier va attribute — istisnolar va turli maydonlar uchun; schema versioning — v va $ifNull bilan sxema to'xtovsiz o'zgaradi.

Keyingi dars: Replica set, tranzaksiyalar va change streams — write concern narxi, bir nechta hujjatli tranzaksiya va o'zgarishlarni real vaqtda tinglash.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
MongoDB sxema dizayn naqshlari: subset, computed, bucket, extended reference va anti-naqshlar — IlmHamroh