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

Mongoose populate va embed vs reference: MongoDB'da bog'lanishni to'g'ri tanlash

Qisqacha: MongoDB'da bog'liq ma'lumot ikki xil saqlanadi: embed — hujjat ichiga joylash (vazifa ichida teglar va izohlar), reference — boshqa hujjatning _id sini saqlash (vazifada egasi). Mongoose populate reference'ni "ochadi", lekin JOIN qilmaydi — har yo'l uchun alohida so'rov yuboradi: kanonda bitta vazifa uchun 3 so'rov. 200 vazifada: embed 6.59 ms, populate 12.46 ms, $lookup 26.82 ms. Qaror qoidasi: birga o'qiladigan, soni chegaralangan narsa — embed; mustaqil yashaydigan yoki cheksiz o'sadigan narsa — reference.

Bu darsda

  • Embed va reference'ni hujjat shaklida farqlaysiz.
  • ref va populate bilan bog'liq hujjatni yuklaysiz, ichma-ich yo'l (izohlar.muallif) bilan ham.
  • populate nechta so'rov yuborishini debug logida ko'rasiz va $lookup bilan solishtirasiz.
  • Bog'lanish sonini (one-to-few, one-to-many, one-to-squillions) va o'qish/yozish nisbatini hisobga olib qaror qilasiz.

Oldin bilishingiz kerak: Mongoose CRUD va update operatorlari, Bog'lanishlar: 1:1, 1:N, N:M, Denormalizatsiya va hisoblangan maydonlar, N+1 muammosi.

1. Nega bu kerak?

Postgres'da bog'lanish haqida o'ylash shart emasdi: har narsa alohida jadvalda, tashqi kalit bilan, va JOIN ularni o'qishda birlashtiradi (Bog'lanishlar: 1:1, 1:N, N:M). Yagona to'g'ri shakl — normalizatsiya (Normalizatsiya).

Mongo'da esa har bog'lanish uchun tanlov qilasiz. Kanon vazifa hujjatida ikkala usul bor edi:

  • teglar va izohlar — vazifaning ichida (embed).
  • egasi — faqat ObjectId, foydalanuvchi alohida kolleksiyada (reference).

Nega bunday? Va qachon teskarisi to'g'ri? Bu Mongo'da modellashning eng muhim savoli. Noto'g'ri tanlov oylar o'tib chiqadi: hujjat 16 MB ga yetadi, yoki har sahifa o'nlab so'rov yuboradi.

O'xshatish: telefoningizdagi kontaktlar. Yaqin oila a'zolarining raqami eslab qolingan — kerak bo'lsa darhol terasiz (embed). Butun shahar telefon kitobi esa alohida — kerak bo'lganda qidirib topasiz (reference). Hamma raqamni yodlash mumkin emas, va oilangiz raqamini har safar kitobdan qidirish — vaqtni behuda sarflash.

Chiqishlar — kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, mongoose 9.11.2, MongoDB 8.3.13. Kolleksiya nomlari — vazifas, foydalanuvchis (Mongoose schema darsidagi tuzoq).

2. Ikki usul: embed va reference

2.1 Hujjat shaklida

Bitta vazifa ikki usulda:

js
// Embed: izohlar vazifaning ichida
{
  _id: ObjectId("…a1"),
  matn: "Non olish #uy",
  izohlar: [
    { muallif: ObjectId("…f1"), matn: "Tez" },
    { muallif: ObjectId("…f2"), matn: "Men ham" },
  ],
}

// Reference: izohlar alohida kolleksiyada
// vazifalar:
{ _id: ObjectId("…a1"), matn: "Non olish #uy" }
// izohlar:
{ _id: ObjectId("…i1"), vazifa: ObjectId("…a1"), matn: "Tez" }
{ _id: ObjectId("…i2"), vazifa: ObjectId("…a1"), matn: "Men ham" }
Embed Reference
O'qish bitta so'rov — hammasi birga ikki so'rov yoki $lookup
Yozish bitta hujjatni atomik o'zgartirish ikki kolleksiyaga
Alohida o'qish qiyin (hujjat ichidan qidirish) oson — o'z kolleksiyasi
O'sish hujjat kattalashadi (16 MB chegara) cheklovsiz
Takrorlanish bir ma'lumot ko'p nusxada bo'lishi mumkin bitta nusxa

Embed — denormalizatsiyaning Mongo'dagi tabiiy shakli. Reference — Postgres'dagi tashqi kalitga o'xshaydi, lekin baza uni tekshirmaydi: o'chirilgan foydalanuvchiga ishora qilib turgan "yetim" ObjectId'ni hech kim to'xtatmaydi (yetim qator muammosi qaytadi).

2.2 Bitta hujjat — atomiklik birligi

MongoDB'da bitta hujjatga yozish doim atomik: ichidagi bir nechta maydon birga o'zgaradi yoki hech biri. Kanon native driver darsida bitta updateOne da $set, $inc va $addToSet ni birga qo'lladi — oraliq holat hech kimga ko'rinmadi.

Bu embed'ning katta yutug'i. Vazifa va uning izohlari bitta hujjatda bo'lsa, "izoh qo'shish + izohlar sonini oshirish" tranzaksiyasiz atomik. Reference'da esa bu ikki kolleksiyaga yozish — atomiklik uchun tranzaksiya kerak (Replica set, tranzaksiyalar va change streams).

Tekshirib ko'ring: Do'kon buyurtmasida yetkazish manzili bor. Mijoz keyinroq profilidagi manzilni o'zgartirdi. Eski buyurtmada qaysi manzil ko'rinishi kerak? Bu embed'ga yoki reference'ga dalilmi?

Javob

Eski buyurtmada buyurtma paytidagi manzil ko'rinishi kerak — tovar o'sha joyga ketgan. Shuning uchun manzilni buyurtma ichiga nusxalash (embed) to'g'ri. Bu yerda takrorlanish xato emas, balki tarixiy fakt. Reference qilsangiz, eski buyurtma yangi manzilni ko'rsatib, hisobotni buzadi.

3. populate: reference'ni ochish

3.1 ref va populate

Mongoose schema darsida egasi va izoh muallif iga ref: "Foydalanuvchi" yozgan edik. Mongo uchun bu hech narsa emas, Mongoose uchun esa ko'rsatma: "bu ObjectId — Foydalanuvchi modelining hujjati".

populate(yo'l, maydonlar) shu ObjectId'larni haqiqiy hujjatlar bilan almashtiradi. Kanon Dilnozani yaratib, vazifaga uning izohini qo'shdi, keyin ikkala reference'ni ochdi:

js
const dilnoza = await Foydalanuvchi.create({
  email: "dilnoza@example.com", ism: "Dilnoza",
});
await Vazifa.updateOne({ _id: v1._id }, {
  $push: { izohlar: { muallif: dilnoza._id, matn: "Men ham" } },
});

const pop = await Vazifa.findById(v1._id)
  .populate("egasi", "ism")
  .populate("izohlar.muallif", "ism")
  .lean();
console.log("populate natijasi:", pop.egasi.ism, "|",
  pop.izohlar.map((i) => `${i.muallif.ism}: ${i.matn}`).join("; "));
text
populate natijasi: Aziz Karimov | Aziz Karimov: Tez; Dilnoza: Men ham

pop.egasi endi ObjectId emas — { _id, ism } obyekti. Ikkinchi argument "ism" — projection: foydalanuvchining faqat ismi kerak (email, parol hash va boshqalar tortilmaydi). "izohlar.muallif" — ichma-ich yo'l: massivdagi har izohning muallifi.

3.2 Necha so'rov ketdi?

Kanon so'rov oldidan Mongoose'ning debug rejimini yoqdi — har bazaga ketgan so'rov logga yoziladi:

js
mongoose.set("debug", (col, metod, sorov) =>
  console.log(`  [debug] ${col}.${metod}(${JSON.stringify(sorov)})`));

Logdagi har qator — bazaga ketgan bitta so'rov. Qatorlarni sanang va $in ichidagi id'larga qarang:

text
  [debug] vazifas.findOne({"_id":"6add9ac27f3d15aa25a380b5"})
  [debug] foydalanuvchis.find({"_id":{"$in":["6add9ac27f3d15aa25a380b1"]}})
  [debug] foydalanuvchis.find({"_id":{"$in":["6add9ac27f3d15aa25a380b1","6add9ac37f3d15aa25a380bf"]}})

Uchta so'rov:

  1. Vazifaning o'zi.
  2. egasi uchun — Azizning id'si bilan.
  3. izohlar.muallif uchun — ikki muallif id'si $in bilan bitta so'rovda.

Demak populate — JOIN emas. U N+1 muammosi darsidagi batch usulining o'zi: avval asosiy hujjatlar, keyin har yo'l uchun hamma id'larni yig'ib { _id: { $in: [...] } } bilan bitta so'rov. 100 ta vazifani populate qilsangiz ham — 3 so'rov, 101 emas. N+1 dan yaxshi, lekin har populate yo'li — qo'shimcha tarmoq aylanishi.

E'tibor bering: Aziz ikki marta so'raldi — 2- va 3-so'rovda. Mongoose har yo'lni alohida yuklaydi va ular orasida keshni bo'lishmaydi.

sequenceDiagram
  participant I as Ilova
  participant M as Mongoose
  participant D as MongoDB
  I->>M: findById().populate(egasi).populate(izohlar.muallif)
  M->>D: vazifas.findOne(_id)
  D-->>M: vazifa (egasi, muallif id'lari)
  M->>D: foydalanuvchis.find(_id $in [egasi])
  M->>D: foydalanuvchis.find(_id $in [mualliflar])
  D-->>M: foydalanuvchilar
  M-->>I: id'lar o'rnida hujjatlar

Diagrammaga qarang: birinchi javob kelmaguncha qolgan so'rovlarni yuborib bo'lmaydi — Mongoose qaysi id'larni so'rashni bilmaydi. Shuning uchun populate vaqti kamida ikki ketma-ket aylanish.

3.3 $lookup — bazadagi birlashtirish

Mongo'ning o'zida ham JOIN'ga o'xshash narsa bor — aggregation bosqichi $lookup. U boshqa kolleksiyadan mos hujjatlarni server ichida biriktiradi. Kanon variant (qatorlar bo'lingan):

js
await Vazifa.aggregate([
  { $match: { matn: /^Ommaviy/ } },
  { $lookup: {
    from: "foydalanuvchis",
    localField: "egasi",
    foreignField: "_id",
    as: "egasi",
    pipeline: [{ $project: { ism: 1 } }],
  } },
]);

from — kolleksiya nomi (model nomi emas — shuning uchun foydalanuvchis!), localField/foreignField — qaysi maydonlar teng bo'lishi kerak, as — natija qaysi maydonga massiv bo'lib tushsin. $lookup natijasi doim massiv: bitta egasi bo'lsa ham [{ … }]. Aggregation'ni keyingi-keyingi darsda to'liq ko'ramiz.

4. O'lchov: embed, populate, $lookup

Kanon 200 ta vazifa yaratdi (egasi — Aziz yoki Dilnoza, har birida bitta izoh) va uch usulda o'qidi — har biri 15 marta, mediana:

js
const tEmbed = await olcha(15, () =>
  Vazifa.find({ matn: /^Ommaviy/ }).lean());
const tPop = await olcha(15, () =>
  Vazifa.find({ matn: /^Ommaviy/ }).populate("egasi", "ism").lean());
// tLookup — yuqoridagi aggregate
text
200 vazifa: embed (faqat o'zi) 6.59 ms, populate egasi 12.46 ms, $lookup 26.82 ms (mediana 15)
200 vazifani o'qish: uch usul
  • Faqat vazifalar (embed)6,59 ms
  • populate egasi12,46 ms
  • $lookup egasi26,82 ms

Manba: IlmHamroh kanon o'lchovi, 2026-10-25: Node 24.21.0, mongoose 9.11.2, MongoDB 8.3.13 (Docker, WSL2), i5-12500H; 15 marta, mediana

Uch usulni yonma-yon qo'yamiz:

Usul Bazaga so'rovlar Birlashtirish qayerda Kanon, 200 vazifa
Embed (faqat hujjat) 1 birlashtirish yo'q 6.59 ms
populate("egasi") 2 (vazifalar + $in) Node'da, Mongoose 12.46 ms
$lookup 1 (aggregate) MongoDB serverida 26.82 ms

Nimani ko'ryapmiz:

  • Embed — eng tez: bitta so'rov, qo'shimcha ish yo'q. (Bu yerda "embed" — egasi ismisiz o'qish; egasi ismi hujjat ichida nusxa bo'lganda ham xuddi shu vaqt bo'lardi — bu extended reference naqshi.)
  • populate — ikki baravar: qo'shimcha so'rov va Mongoose'ning JavaScript'dagi biriktirish ishi.
  • $lookup — bu o'lchovda eng sekin. Kutilmagan natija: "baza ichida birlashtirish tezroq bo'lishi kerak" deb o'ylash tabiiy. Kanon $lookup ni $match bilan, lekin matn indeksisiz o'lchadi; $lookup har hujjat uchun foydalanuvchis dan qidiradi. Bitta o'lchov, bitta mashina — umumiy qonun emas.

Xulosa: "baza ichida — doim tez" degan intuitsiyaga ishonmang, o'lchang. Va eng tez yo'l — umuman birlashtirmaslik: kerakli ma'lumot allaqachon hujjatda bo'lsa.

Postgres bilan solishtirish uchun: N+1 muammosi darsida 50 vazifa izoh va teglari bilan bitta so'rovda 5.0 ms edi. Bu boshqa o'lchov va boshqa hajm — to'g'ridan solishtirib bo'lmaydi. Lekin JOIN Postgres'ning kuchli tomoni ekanini eslatadi.

Tekshirib ko'ring: Sahifada 20 ta vazifa, har biri egasi ismi va izoh mualliflari bilan. populate("egasi").populate("izohlar.muallif") necha so'rov yuboradi?

Javob

3 ta: vazifalar uchun bitta, egasilar uchun bitta ($in bilan 20 tagacha id), mualliflar uchun bitta. Vazifalar soni 20 mi yoki 200 mi — so'rovlar soni o'zgarmaydi; faqat $in ro'yxati uzayadi. Kanon debug logida bitta vazifa uchun ham aynan 3 ta edi.

5. Qaror qoidasi

5.1 Bog'lanish soni: few, many, squillions

MongoDB jamoasi tavsiya qiladigan amaliy bo'linish (MongoDB blogi va hujjatidagi "6 Rules of Thumb" maqolasi — hujjat bo'yicha):

Tur Misol Tavsiya
one-to-few (bir necha, o'nlab) vazifa → teglar, foydalanuvchi → manzillar embed
one-to-many (yuzlab, minglab) mahsulot → ehtiyot qismlar ota hujjatda id'lar massivi yoki bolada ota id'si
one-to-squillions (cheksiz) server → log yozuvlari bolada ota id'si (vazifa: ObjectId)

"Squillions" — inglizcha hazil so'z: "son-sanoqsiz". Vazifa izohlari qaysi turga kiradi? Odatda bir nechta — embed. Lekin mashhur vazifa (yoki ijtimoiy tarmoqdagi post) minglab izoh yig'ishi mumkin. Kanon buni o'lchadi: 20 000 izohli hujjatda findOne 4 ms dan 96 ms ga sekinlashdi — sxema dizayn naqshlari darsida to'liq raqamlar bilan.

5.2 Savollar ketma-ketligi

flowchart TD
  A["Bog'liq ma'lumot"] --> B{"O'zi alohida so'raladimi?"}
  B -->|ha| R["reference"]
  B -->|yo'q| C{"Soni chegaralanganmi?"}
  C -->|"yo'q, cheksiz o'sadi"| R
  C -->|ha| D{"Ko'p joyda takrorlanib, tez-tez o'zgaradimi?"}
  D -->|ha| R
  D -->|yo'q| E["embed"]

Diagrammaga qarang — uchta savol:

  1. Alohida so'raladimi? "Dilnozaning hamma izohlari" sahifasi bo'lsa, izohlar vazifa ichida bo'lishi noqulay: har vazifani ochib qidirish kerak. Reference.
  2. Chegaralanganmi? Teglar — 3–5 ta. Izohlar — bilmaymiz. Cheksiz o'sadigan massiv — reference.
  3. Takrorlanib, o'zgaradimi? Foydalanuvchi ismi 1 000 ta vazifada nusxa bo'lsa va ism tez-tez o'zgarsa — 1 000 ta nusxani yangilash kerak. Kanon buni ham o'lchadi: 1 000 nusxa updateMany 44.9 ms (sxema dizayn naqshlari). Ism kamdan-kam o'zgarsa — nusxa (embed) arzon; tez-tez — reference.

5.3 O'qish va yozish nisbati

Oxirgi omil — ish rejimi. Ko'p ilovalarda o'qish yozishdan o'nlab marta ko'p: vazifalar ro'yxati har sahifa ochilganda o'qiladi, egasi ismi esa yiliga bir marta o'zgaradi. Bunday holatda nusxalash (embed) o'qishni tez qiladi, yozishni biroz qimmat qiladi — va bu yaxshi savdo.

Teskari holat — tez-tez o'zgaradigan narsa (mahsulot qoldig'i, narx) — ko'p joyda nusxalanmasin: har o'zgarishda ko'p hujjat yangilanadi va nusxalar bir-biridan farq qilib qolish xavfi bor.

Mongo'da sxemani so'rovlardan boshlab loyihalaysiz: "ilova qanday savollar beradi?" Postgres'da esa avval ma'lumotni normal shaklga keltirib, savollarni keyin JOIN bilan yozasiz. Bu ikki bazaning falsafiy farqi.

6. vazifalar uchun qaror — kanon shakli

Bog'lanish Tanlov Sabab
vazifa → teglar embed (["ish", "uy"]) 3–5 ta, doim vazifa bilan o'qiladi
vazifa → izohlar embed (kanon) odatda bir necha; lekin cheksizlik xavfi — naqshlar darsida
vazifa → egasi reference foydalanuvchi alohida yashaydi, ko'p vazifada
izoh → muallif reference xuddi shu

Kanon ko'chirishda (native driver) shu shaklni tanladi. Postgres'dagi teglar + vazifa_teglari jadvallari — Mongo'da bitta satrlar massivi. Teglarning o'zi ma'lumot (rang, tavsif) bo'lganda — ular alohida kolleksiyaga chiqardi.

7. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da: u yerda bog'lanish — tashqi kalit va JOIN, populate muammosi yo'q. Kanon Mongo'da sinagani:

  1. populate("egasi", "ism") va ichma-ich populate("izohlar.muallif", "ism").
  2. Debug logida 3 so'rov — populate'ning batch tabiati.
  3. 200 vazifa: embed 6.59 ms, populate 12.46 ms, $lookup 26.82 ms.

8. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
Hamma narsani embed qilish Hujjat cheksiz o'sadi, 16 MB, sekin o'qish Cheksiz bog'lanish — reference
Hamma narsani reference qilish ("Postgres'dagidek") Har sahifa ko'p populate, sekin Birga o'qiladigan kichik ma'lumot — embed
populate siz projection Foydalanuvchining parol hash'i ham javobga tushadi populate("egasi", "ism")
$lookup da model nomi (Foydalanuvchi) Bo'sh massiv, xato yo'q Kolleksiya nomi (foydalanuvchis)
O'chirilgan hujjatga ishora qolishi populate null beradi, pop.egasi.ism → TypeError O'chirishda bog'liqlarni ham tozalash yoki ?.
populate = JOIN deb o'ylash Qo'shimcha so'rovlar e'tiborsiz qoladi Debug log bilan sanang

9. Mashqlar

Mashqlar kurs/mashqlar/27/46-populate/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik — populate'ning kichik modeli, bazasiz).

1-mashq (oson): embed yoki reference?

Har holat uchun yozing: yoki reference.

  • Foydalanuvchining 2–3 ta yetkazish manzili —
  • Foydalanuvchi va uning yillar davomidagi hamma buyurtmalari —
  • Vazifaning teglari —
  • Telegram kanaldagi post va uning ko'rishlar logi —
Yechim
  • embed — bir nechta, doim profil bilan o'qiladi.
  • reference — cheksiz o'sadi, alohida sahifada so'raladi (buyurtmada mijoz: ObjectId).
  • embed — 3–5 ta satr, vazifa bilan birga.
  • reference — one-to-squillions: har ko'rish — alohida hujjat (yoki sanoq — bucket naqshi).

2-mashq (o'rta): qo'lda populate

populate(hujjatlar, maydon, yukla) yozing: hujjatlar dagi maydon id'larini yig'ib, takrorlarsiz, yukla(idlar) ni bir marta chaqirsin va id o'rniga topilgan obyektni qo'ysin (topilmasa null). Asl hujjatlarni o'zgartirmasin. So'rovlar sonini sanang.

Ishora: new Set bilan takrorsiz, Map bilan id → hujjat (N+1 muammosi darsidagi batch).

Yechim
js
const foydalanuvchilar = [
  { _id: "f1", ism: "Aziz Karimov" },
  { _id: "f2", ism: "Dilnoza" },
];
let sorovlar = 0;
async function yukla(idlar) {
  sorovlar++;
  return foydalanuvchilar.filter((f) => idlar.includes(f._id));
}

async function populate(hujjatlar, maydon, yukla) {
  const idlar = [...new Set(hujjatlar.map((h) => h[maydon]))];
  const topildi = new Map(
    (await yukla(idlar)).map((x) => [x._id, x]),
  );
  return hujjatlar.map((h) => ({
    ...h,
    [maydon]: topildi.get(h[maydon]) ?? null,
  }));
}

const vazifalar = [
  { matn: "Non olish", egasi: "f1" },
  { matn: "Hisobot", egasi: "f1" },
  { matn: "Kitob", egasi: "f2" },
  { matn: "Eski", egasi: "f9" },
];
const natija = await populate(vazifalar, "egasi", yukla);
for (const v of natija) {
  console.log(`${v.matn}: ${v.egasi?.ism ?? "(egasi yo'q)"}`);
}
console.log("so'rovlar:", sorovlar, "| asl:", vazifalar[0].egasi);

Konsolda:

text
Non olish: Aziz Karimov
Hisobot: Aziz Karimov
Kitob: Dilnoza
Eski: (egasi yo'q)
so'rovlar: 1 | asl: f1

To'rt vazifa — bitta so'rov. f9 — o'chirilgan foydalanuvchiga "yetim" ishora: populate null qaytaradi va ?. usiz v.egasi.ism TypeError bilan yiqilardi. Mongoose populate ham topilmaganda null qo'yadi.

3-mashq (qiyin): ichma-ich yo'l

populateIchki(hujjatlar, massiv, maydon, yukla) yozing: har hujjatning massiv (masalan izohlar) elementlaridagi maydon (muallif) id'larini hamma hujjatlardan yig'ib, bitta so'rov bilan to'ldirsin. Kanon misolini qayta tuzing: bitta vazifa, egasi Aziz, ikki izoh (Aziz — "Tez", Dilnoza — "Men ham"); egasi va mualliflar uchun ikkita alohida populate. Natijani kanon formatida chiqaring va so'rovlarni sanang.

Ishora: flatMap — flat va flatMap.

Yechim
js
const foydalanuvchilar = [
  { _id: "f1", ism: "Aziz Karimov" },
  { _id: "f2", ism: "Dilnoza" },
];
const log = [];
async function yukla(idlar) {
  log.push(`foydalanuvchis.find(_id $in ${JSON.stringify(idlar)})`);
  return foydalanuvchilar.filter((f) => idlar.includes(f._id));
}

async function xarita(idlar) {
  return new Map((await yukla(idlar)).map((x) => [x._id, x]));
}

async function populate(hujjatlar, maydon) {
  const idlar = hujjatlar.map((h) => h[maydon]);
  const m = await xarita([...new Set(idlar)]);
  return hujjatlar.map((h) => ({
    ...h, [maydon]: m.get(h[maydon]) ?? null,
  }));
}

async function populateIchki(hujjatlar, massiv, maydon) {
  const idlar = hujjatlar
    .flatMap((h) => h[massiv].map((e) => e[maydon]));
  const m = await xarita([...new Set(idlar)]);
  return hujjatlar.map((h) => ({
    ...h,
    [massiv]: h[massiv].map((e) => ({
      ...e, [maydon]: m.get(e[maydon]) ?? null,
    })),
  }));
}

const vazifalar = [{
  matn: "Non olish", egasi: "f1",
  izohlar: [
    { muallif: "f1", matn: "Tez" },
    { muallif: "f2", matn: "Men ham" },
  ],
}];
log.push("vazifas.findOne(...)");
let v = await populate(vazifalar, "egasi");
v = await populateIchki(v, "izohlar", "muallif");
const [pop] = v;
console.log("populate natijasi:", pop.egasi.ism, "|",
  pop.izohlar.map((i) => `${i.muallif.ism}: ${i.matn}`).join("; "));
console.log(log.join("\n"));

Konsolda:

text
populate natijasi: Aziz Karimov | Aziz Karimov: Tez; Dilnoza: Men ham
vazifas.findOne(...)
foydalanuvchis.find(_id $in ["f1"])
foydalanuvchis.find(_id $in ["f1","f2"])

Natija va so'rovlar tartibi kanon debug logi bilan bir xil: 3 so'rov, Aziz ikki marta so'raladi — har populate yo'li o'z so'rovini yuboradi. Yaxshilash g'oyasi: ikkala yo'lning id'larini bitta to'plamga yig'ib, 2 so'rovga tushirish. Mongoose buni o'zi qilmaydi.

10. Real ishda

  • Sxema loyihalash suhbati — Mongo'li jamoada har yangi xususiyat uchun "embed yoki reference?" muhokamasi bo'ladi; qaror so'rovlar ro'yxati va kutilgan hajmdan chiqadi.
  • populate — Express + Mongoose loyihalarida eng ko'p uchraydigan sekinlik manbalaridan biri: ichma-ich bir nechta populate bitta sahifada o'nlab so'rovga aylanadi. Debug log bilan sanash — birinchi qadam.
  • GraphQL serverlari Mongo bilan DataLoader orqali xuddi shu batch g'oyasini ishlatadi.
  • Intervyu savollari: "embed va reference qachon?", "populate JOINmi?", "16 MB chegara modellashga qanday ta'sir qiladi?".

Xulosa

  • Mongo'da bog'lanish — tanlov: embed (hujjat ichida) yoki reference (boshqa hujjatning _id si); baza reference'ni tekshirmaydi.
  • Bitta hujjatga yozish atomik — embed'ning katta yutug'i; reference'da atomiklik uchun tranzaksiya kerak.
  • populate JOIN emas: har yo'l — alohida $in so'rov (kanonda bitta vazifa uchun 3 so'rov); projection bilan faqat kerakli maydonlarni oling.
  • Kanon o'lchovi (200 vazifa): embed 6.59, populate 12.46, $lookup 26.82 ms — intuitsiyaga emas, o'lchovga ishoning.
  • Qaror: alohida so'raladimi? chegaralanganmi? takrorlanib tez o'zgaradimi? — va o'qish/yozish nisbati.

Keyingi dars: Mongoose ilg'or — middleware (Mongoose 9 da next() siz), virtual maydonlar, metodlar, discriminator, lean() va TypeScript turlari.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Mongoose populate va embed vs reference: MongoDB'da bog'lanishni to'g'ri tanlash — IlmHamroh