Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Pipeline qanday ishlaydi
- 2.1 Bosqichlar massivi
- 2.2 Brauzerda modeli
- 3. Asosiy bosqichlar: foydalanuvchi statistikasi
- 3.1 To'liq pipeline
- 3.2 Har bosqichni o'qiymiz
- 4. $unwind + $group: eng ko'p teglar
- 5. $facet: bitta so'rovda bir nechta natija
- 6. $bucket va $size xatosi
- 6.1 Xato
- 6.2 $ifNull bilan tuzatish va $bucket
- 7. $setWindowFields: window hisob-kitob
- 8. Bosqichlar tartibi va indeks
- 8.1 Optimizator nimani ko'chiradi
- 9. Katta to'plam: Mongo va Postgres
- 9.1 Ko'chirish va hajm
- 9.2 Bir xil savol — GROUP BY ustuvorlik
- 9.3 Ikki nozik farq
- 10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): mini pipeline
- 3-mashq (qiyin): $bucket modeli
- 13. Real ishda
- Xulosa
- Manbalar
MongoDB aggregation pipeline: $match, $group, $lookup, $unwind, $facet va bosqichlar tartibi
Qisqacha: Aggregation pipeline — MongoDB'da hisobot va murakkab so'rovlar usuli: hujjatlar bosqichlar quvuridan o'tadi, har bosqich (
$match,$group,$lookup,$unwind,$project,$sort,$facet…) oldingisining natijasini o'zgartiradi. Bu SQL'dagiWHERE → GROUP BY → JOIN → SELECT → ORDER BYning hujjat bazasidagi shakli. Kanon 48 vazifada Postgres bilan bir xil sonlarni oldi. 500 000 vazifada esa bir xilGROUP BYsavoliga Mongo 246.78 ms, Postgres 41.11 ms sarfladi. Mongo diskda esa 6 baravar ixcham: 18.1 MB va 115 MB.
Bu darsda
- Pipeline nima ekanini va SQL bandlariga qanday mos kelishini tushuntira olasiz.
$match,$group,$lookup,$unwind,$project,$sort,$limitbilan foydalanuvchi statistikasini yozasiz.$facet,$bucketva$setWindowFieldsbilan bitta so'rovda bir nechta natija, guruhlar va window hisob-kitob qilasiz.- Maydonsiz hujjatdagi
$sizexatosini o'qiysiz va$ifNullbilan tuzatasiz. - Bosqichlar tartibi, indeks va
explainni bog'laysiz; katta to'plamda Postgres bilan o'lchovni o'qiysiz.
Oldin bilishingiz kerak: Populate va embed vs reference, Agregatlar, GROUP BY va HAVING, Window funksiyalar 1: ranking, View va materialized view.
1. Nega bu kerak?
find bitta savolga javob beradi: "qaysi hujjatlar mos?". Hisobot esa boshqa savollarni beradi:
- Har foydalanuvchida nechta vazifa va qanchasi bajarilgan?
- Eng ko'p ishlatilgan 5 ta teg qaysi?
- Sahifadagi 20 ta vazifa va jami soni — bitta so'rovda.
Postgres'da bularni GROUP BY, JOIN va window funksiyalari bilan yozgandik (Agregatlar, Window funksiyalar). Mongo'da — aggregation pipeline.
O'xshatish: zavoddagi konveyer. Xom ashyo bir uchidan kiradi. Birinchi stanok saralaydi ($match), ikkinchisi bo'laklarga ajratadi ($unwind), uchinchisi qutilarga joylaydi ($group), oxirgisi yorliq yopishtiradi ($project). Har stanok faqat oldingisidan kelganni ko'radi.
Bu darsdagi chiqishlar — kanon o'lchovi (IlmHamroh, 2026-10): native driver darsida ko'chirilgan 48 vazifali vazifalar bazasi va katta to'plam — Postgres'dagi 500 000 vazifa Mongo'ga bir marta ko'chirilgan. Node 24.21.0, mongodb 7.7.0, MongoDB 8.3.13, PostgreSQL 18.6 (ikkalasi Docker'da, WSL2), i5-12500H.
2. Pipeline qanday ishlaydi
2.1 Bosqichlar massivi
await db.collection("vazifalar").aggregate([
{ $match: { … } }, // 1-bosqich
{ $group: { … } }, // 2-bosqich
{ $sort: { … } }, // 3-bosqich
]).toArray();aggregate massiv oladi, har element — bitta bosqich. Natija — kursor (native driver darsidagi kabi), toArray() yoki for await bilan o'qiladi.
| Bosqich | Nima qiladi | SQL o'xshashi |
|---|---|---|
$match |
filtrlaydi | WHERE / HAVING |
$group |
guruhlab hisoblaydi | GROUP BY + agregatlar |
$project, $addFields |
maydonlarni tanlaydi/hisoblaydi | SELECT ifodalari |
$sort, $limit, $skip |
tartib va kesish | ORDER BY, LIMIT, OFFSET |
$lookup |
boshqa kolleksiyadan biriktiradi | LEFT JOIN |
$unwind |
massivni har element — alohida hujjat | unnest |
$count |
sanaydi | count(*) |
$facet |
bir nechta ichki pipeline | bir nechta so'rov |
$bucket |
diapazonlar bo'yicha guruh | CASE + GROUP BY |
$setWindowFields |
window hisob-kitob | OVER (PARTITION BY …) |
$ bilan boshlangan satr ("$egasi") — "shu maydonning qiymati". Bu aggregation'ning asosiy belgisi: { $sum: "$narx" } — narx maydonlarini qo'sh.
2.2 Brauzerda modeli
Pipeline g'oyasi — oddiy massiv metodlari zanjiri (Metod zanjiri va ma'lumot pipeline). Kichik "kolleksiya"da $match → $group → $sort ni JavaScript'da yozamiz:
const vazifalar = [
{ egasi: "Aziz", bajarildi: true },
{ egasi: "Aziz", bajarildi: false },
{ egasi: "Dilnoza", bajarildi: true },
{ egasi: "Aziz", bajarildi: true, ochirilgan: "2026-10-20" },
];
// $match: { ochirilgan: { $exists: false } }
const mos = vazifalar.filter((v) => v.ochirilgan === undefined);
// $group: { _id: "$egasi", jami: {$sum: 1}, bajarilgan: … }
const guruhlar = new Map();
for (const v of mos) {
const g = guruhlar.get(v.egasi) ?? { _id: v.egasi, jami: 0,
bajarilgan: 0 };
g.jami += 1;
g.bajarilgan += v.bajarildi ? 1 : 0;
guruhlar.set(v.egasi, g);
}
// $sort: { _id: 1 }
const natija = [...guruhlar.values()]
.sort((a, b) => a._id.localeCompare(b._id));
console.log(JSON.stringify(natija));Konsolda:
[{"_id":"Aziz","jami":2,"bajarilgan":1},{"_id":"Dilnoza","jami":1,"bajarilgan":1}]Mongo xuddi shu ishni server ichida, C++ da va (iloji bo'lsa) indeks bilan qiladi. Ma'lumot ilovaga ko'chmaydi — faqat natija keladi.
flowchart LR
A["vazifalar (48)"] --> M["$match: Savatda emas"]
M --> G["$group: egasi bo'yicha"]
G --> L["$lookup: foydalanuvchilar"]
L --> U["$unwind: f"]
U --> P["$project: foiz"]
P --> S["$sort: id"]
S --> R["7 qator"]Diagramma — keyingi bo'limdagi kanon pipeline'i: har o'q — oldingi bosqich natijasi keyingisiga oqadi.
3. Asosiy bosqichlar: foydalanuvchi statistikasi
3.1 To'liq pipeline
Kanon har foydalanuvchining vazifalar statistikasini hisobladi — Postgres'dagi statistika materialized view'i (View va materialized view) bilan solishtirish uchun:
const stat = await V.aggregate([
{ $match: { ochirilgan: { $exists: false } } },
{ $group: {
_id: "$egasi",
jami: { $sum: 1 },
bajarilgan: { $sum: { $cond: ["$bajarildi", 1, 0] } },
} },
{ $lookup: {
from: "foydalanuvchilar", localField: "_id",
foreignField: "_id", as: "f",
pipeline: [{ $project: { _id: 0, pgId: 1, ism: 1 } }],
} },
{ $unwind: "$f" },
{ $project: {
_id: 0, id: "$f.pgId", ism: "$f.ism", jami: 1, bajarilgan: 1,
foiz: { $round: [{ $multiply: [
{ $divide: ["$bajarilgan", "$jami"] }, 100] }, 1] },
} },
{ $sort: { id: 1 } },
]).toArray();V — db.collection("vazifalar"). Natija:
{"jami":12,"bajarilgan":5,"id":1,"ism":"Aziz Karimov","foiz":41.7}
{"jami":6,"bajarilgan":3,"id":2,"ism":"Dilnoza Rahimova","foiz":50}
{"jami":6,"bajarilgan":3,"id":3,"ism":"Sardor Tursunov","foiz":50}
{"jami":9,"bajarilgan":4,"id":4,"ism":"Madina Yusupova","foiz":44.4}
{"jami":5,"bajarilgan":2,"id":5,"ism":"Javohir Olimov","foiz":40}
{"jami":4,"bajarilgan":2,"id":6,"ism":"Gulnora Saidova","foiz":50}
{"jami":4,"bajarilgan":2,"id":7,"ism":"Bekzod Nazarov","foiz":50}Kanon qayd qildi: sonlar Postgres'dagi statistika bilan bir xil. Bir xil ma'lumot, ikki baza, ikki til — bir xil javob.
3.2 Har bosqichni o'qiymiz
$match— Savatdagilarni chiqarib tashlaydi. Har doim imkon qadar boshida: keyingi bosqichlarga kamroq hujjat boradi va indeks ishlatilishi mumkin.$group—_id— guruh kaliti ("$egasi"— egasi bo'yicha). Qolgan maydonlar — akkumulyatorlar:$sum,$avg,$min,$max,$first,$push(massivga yig'ish),$addToSet.{ $sum: 1 }— har hujjat uchun 1 — ya'ni sanash.$cond: [shart, ha, yo'q]— SQL'dagiCASE WHEN(Postgres'da bunicount(*) FILTER (WHERE bajarildi)bilan yozgandik).$lookup— oldingi darsda ko'rgan biriktirish;pipelineichida faqat kerakli maydonlar. Natija — massivf: [{ pgId, ism }].$unwind: "$f"— massivni ochadi: bitta elementli massiv → oddiy obyekt. Ko'p elementli bo'lsa — har element uchun alohida hujjat (keyingi misol). E'tibor: egasi topilmagan guruh$unwindda yo'qoladi (bo'sh massiv — hujjat yo'q), SQL'dagiINNER JOINkabi. Saqlab qolish uchun —{ $unwind: { path: "$f", preserveNullAndEmptyArrays: true } }.$project— chiqish shakli:"$f.ism"— ichma-ich maydon,1— o'zgarishsiz qoldir,foiz— ifoda bilan hisoblangan.$sort—idbo'yicha.
4. $unwind + $group: eng ko'p teglar
await V.aggregate([
{ $match: { ochirilgan: { $exists: false } } },
{ $unwind: "$teglar" },
{ $group: { _id: "$teglar", soni: { $sum: 1 } } },
{ $sort: { soni: -1, _id: 1 } },
{ $limit: 5 },
]).toArray();{"_id":"ish","soni":8}
{"_id":"o'qish","soni":8}
{"_id":"to'lov","soni":5}
{"_id":"bozor","soni":3}
{"_id":"oila","soni":3}$unwind: "$teglar" — teglar: ["docker", "o'qish"] li hujjat ikkita hujjatga aylanadi: biri teglar: "docker", ikkinchisi teglar: "o'qish". Keyin teg bo'yicha guruhlash — oddiy sanash. Kanon: natija vazifalar-api ning GET /v1/teglar javobi bilan bir xil.
$sort: { soni: -1, _id: 1 } — avval soni kamayish, teng bo'lsa nomi bo'yicha. Ikkinchi kalit bo'lmasa, ish va o'qish (ikkalasi 8) tartibi kafolatlanmaydi — keyset pagination darsidagi "noyob tartib" qoidasi.
Tegsiz vazifa $unwind dan keyin yo'qoladi — bu yerda to'g'ri, chunki biz teglarni sanayapmiz.
5. $facet: bitta so'rovda bir nechta natija
API ro'yxatiga ko'pincha ikki narsa kerak: sahifa va jami soni. Ikki so'rov yubormaslik uchun — $facet: bir xil kirish ustida bir nechta ichki pipeline:
await V.aggregate([
{ $match: { ochirilgan: { $exists: false } } },
{ $facet: {
sahifa: [
{ $sort: { yaratilgan: -1 } }, { $limit: 2 },
{ $project: { _id: 0, pgId: 1, matn: 1 } },
],
jami: [{ $count: "n" }],
ustuvorlik: [
{ $group: { _id: "$ustuvorlik", n: { $sum: 1 } } },
{ $sort: { _id: 1 } },
],
} },
]).toArray();{"sahifa":[{"pgId":48,"matn":"Bozordan kartoshka va piyoz olish #bozor"},{"pgId":40,"matn":"Sport zalga yozilish #sport"}],"jami":[{"n":47}],"ustuvorlik":[{"_id":null,"n":1},{"_id":"orta","n":29},{"_id":"past","n":7},{"_id":"yuqori","n":10}]}Natija — bitta hujjat, uchta maydon. Ikki g'alati joy bor:
jami: 47. Statistikada jami 46 edi (12+6+6+9+5+4+4). Bitta ortiqcha qayerdan?{"_id":null,"n":1}— ustuvorliginullbo'lgan bitta vazifa. Kanonda bunday vazifa yo'q edi.
Ikkalasining sababi bitta. Native driver darsidagi replaceOne tuzog'ini eslang: 3-vazifa { bajarildi: true } ga aylangan edi — egasi, ustuvorlik, ochirilgan yo'q. U $match dan o'tdi (ochirilgan yo'q!), jamiga kirdi, ustuvorligi null guruhiga tushdi. Statistikada esa $lookup + $unwind uni tashlab yubordi — egasi yo'q.
Bitta noto'g'ri replaceOne ikki darsdan keyin hisobotni jim buzdi. Sxemasizlikning haqiqiy narxi shu.
6. $bucket va $size xatosi
6.1 Xato
Kanon vazifalarni izohlar soni bo'yicha guruhlamoqchi bo'ldi. Avval har hujjatda izohlar sonini $size bilan hisobladi:
await V.aggregate([
{ $project: { n: { $size: "$izohlar" } } },
]).toArray();$size maydonsiz hujjatda: Location17124: The argument to $size must be an array, but was of type: missingTarjima: "$size argumenti massiv bo'lishi kerak, lekin uning turi: yo'q (missing)". Location17124 — server xatosining kod nomi. Yana o'sha 3-vazifa: unda izohlar maydoni umuman yo'q. Postgres'da ustun yo'q bo'lishi mumkin emas edi, Mongo'da esa — bitta yarim hujjat butun hisobotni yiqitdi.
6.2 $ifNull bilan tuzatish va $bucket
await V.aggregate([
{ $project: {
n: { $size: { $ifNull: ["$izohlar", []] } },
} },
{ $bucket: {
groupBy: "$n", boundaries: [0, 1, 2, 5], default: "5+",
output: { vazifalar: { $sum: 1 } },
} },
]).toArray();{"_id":0,"vazifalar":38}
{"_id":1,"vazifalar":5}
{"_id":2,"vazifalar":5}$ifNull: ["$izohlar", []]— maydon yo'q yokinullbo'lsa, bo'sh massiv. SQL'dagicoalesce. Sxemasiz bazada hisobot yozganda bu himoya ko'p joyda kerak.$bucket—boundaries: [0, 1, 2, 5]chegaralari:[0, 1),[1, 2),[2, 5); ulardan tashqarisi —defaultguruhiga. Natijada_id— guruhning pastki chegarasi: 38 ta vazifada izoh yo'q, 5 tasida bitta, 5 tasida 2–4 ta. 5+ guruhi chiqmadi — bunday vazifa yo'q (bo'sh guruh qaytmaydi).
Tekshirib ko'ring: 38 + 5 + 5 = 48. Statistikada 46,
$facetda 47 edi. Bu yerda nega 48?
Javob
Bu pipeline'da $match yo'q — Savatdagi vazifa ham sanaldi. Uchta son uchta savolga javob: statistika — egasi bor va Savatda emas (46), facet — Savatda emas (47, yarim hujjat bilan), bucket — hammasi (48). Hisobotda raqam farq qilsa, avval filtrlarni solishtiring.
7. $setWindowFields: window hisob-kitob
Window funksiyalar darsida row_number() OVER (PARTITION BY … ORDER BY …) va yig'ma summani yozgandik. Mongo'da — $setWindowFields:
await V.aggregate([
{ $match: { egasi: dilnozaId } },
{ $setWindowFields: {
partitionBy: "$egasi",
sortBy: { yaratilgan: 1 },
output: {
n: { $documentNumber: {} },
kumulyativ_bajarilgan: {
$sum: { $cond: ["$bajarildi", 1, 0] },
window: { documents: ["unbounded", "current"] },
},
},
} },
{ $project: { _id: 0, pgId: 1, n: 1, bajarildi: 1,
kumulyativ_bajarilgan: 1 } },
]).toArray();dilnozaId — Dilnozaning ObjectId'si (kanon uni foydalanuvchilar dan pgId: 2 bilan oldi).
{"pgId":5,"bajarildi":true,"n":1,"kumulyativ_bajarilgan":1}
{"pgId":14,"bajarildi":false,"n":2,"kumulyativ_bajarilgan":1}
{"pgId":16,"bajarildi":true,"n":3,"kumulyativ_bajarilgan":2}
{"pgId":26,"bajarildi":false,"n":4,"kumulyativ_bajarilgan":2}
{"pgId":29,"bajarildi":true,"n":5,"kumulyativ_bajarilgan":3}
{"pgId":36,"bajarildi":false,"n":6,"kumulyativ_bajarilgan":3}| Mongo | SQL |
|---|---|
partitionBy: "$egasi" |
PARTITION BY egasi_id |
sortBy: { yaratilgan: 1 } |
ORDER BY yaratilgan_vaqt |
$documentNumber |
row_number() |
window: { documents: ["unbounded", "current"] } |
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
$group dan farqi: hujjatlar kamaymaydi — har biriga yangi maydon qo'shiladi. Natija: Dilnozaning 6 vazifasi tartib raqami va shu paytgacha bajarilganlar soni bilan.
8. Bosqichlar tartibi va indeks
8.1 Optimizator nimani ko'chiradi
Kanon { egasi: 1, bajarildi: 1 } indeksini yaratib, uch pipeline'ning rejasini explain("executionStats") bilan o'qidi:
await V.createIndex({ egasi: 1, bajarildi: 1 });
const p1 = [{ $match: { egasi, bajarildi: false } },
{ $project: { matn: 1 } }];
const p2 = [{ $project: { matn: 1, egasi: 1, bajarildi: 1 } },
{ $match: { egasi, bajarildi: false } }];
const p3 = [{ $group: { _id: "$egasi", n: { $sum: 1 } } },
{ $match: { _id: egasi } }];{"match_boshida":{"ixscan":true,"collscan":false,"docsExamined":7}}
{"match_project_dan_keyin":{"ixscan":true,"collscan":false,"docsExamined":7}}
{"match_group_dan_keyin":{"ixscan":true,"collscan":false,"docsExamined":0}}$matchboshida — kutilganidek: indeks (IXSCAN), faqat Azizning 7 bajarilmagan vazifasi o'qildi.$match$projectdan keyin — xuddi shu reja! Optimizator$matchni o'zi oldinga ko'chirdi:$projectfiltrlangan maydonlarni o'zgartirmaydi, demak tartibni almashtirish xavfsiz.$match$groupdan keyin (_id— guruh kaliti) — baribir IXSCAN va 0 ta hujjat o'qildi. Guruh kalitiegasibo'lgani uchun optimizator filtrni guruhlashdan oldinga o'tkazdi. Sanash uchun faqategasikerak, u indeksda bor — hujjatlarning o'zini o'qishning hojati qolmadi.
Optimizator aqlli, lekin hamma narsani ko'chira olmaydi. Masalan, $group dagi hisoblangan maydon (jami) bo'yicha $match — guruhlashdan keyin (HAVING kabi), uni ko'chirib bo'lmaydi. Qoida: $match va $sort (+$limit) ni o'zingiz boshiga qo'ying — optimizatorga tayanmang. explain — EXPLAIN ANALYZE ning Mongo'dagi o'xshashi; indekslar bilan keyingi darsda chuqur ishlaymiz.
Indeks faqat pipeline boshidagi $match/$sort ga yordam beradi. $group yoki $unwind dan keyin hujjatlar — xotiradagi oraliq natija, indeks ularga tegmaydi.
9. Katta to'plam: Mongo va Postgres
9.1 Ko'chirish va hajm
Kanon Postgres'dagi 500 000 vazifani (26-qismdagi katta to'plamning 1/4 qismi) 50 000 lik bo'laklarda Mongo'ga ko'chirdi — _id sifatida Postgres id'si, teglar massivi bilan:
ko'chirildi: 500000 hujjat, 13.0 s
Mongo katta.vazifalar: size 74.0 MB (siqilmagan), storageSize 18.1 MB (WiredTiger snappy), indekslar 13.3 MB
Postgres katta.vazifalar jadvali (TOAST bilan, indekssiz): 115 MBDiskda Mongo 18.1 MB, Postgres 115 MB. Sabab — WiredTiger (Mongo'ning saqlash dvigateli) ma'lumotni sukut bo'yicha snappy algoritmi bilan siqadi. Siqilmagan holda Mongo hujjatlari 74 MB — Postgres'dan unchalik kichik emas. Postgres jadval sahifalarini siqmaydi (faqat katta qiymatlarni TOAST'da). Diqqat: solishtirish to'liq emas — Postgres tomonda teglar alohida jadvalda (vazifa_teglari), u bu 115 MB ga kirmagan.
9.2 Bir xil savol — GROUP BY ustuvorlik
// Mongo
K.aggregate([
{ $group: { _id: "$ustuvorlik", jami: { $sum: 1 },
bajarilgan: { $sum: { $cond: ["$bajarildi", 1, 0] } } } },
{ $sort: { _id: 1 } },
]);
// Postgres
pg.query(`SELECT ustuvorlik, count(*),
count(*) FILTER (WHERE bajarildi)
FROM vazifalar GROUP BY 1 ORDER BY 1`);Chiqishda avval birinchi va oxirgi qatordagi vaqtlarga, keyin o'rtadagi ikki qatorda sonlar bir xil, lekin tartib va son turi har xil ekaniga qarang:
GROUP BY ustuvorlik (500 000): Mongo 246.78 ms, Postgres 41.11 ms (mediana 5)
[{"_id":"orta","jami":250254,"bajarilgan":149680},{"_id":"past","jami":124368,"bajarilgan":74804},{"_id":"yuqori","jami":125378,"bajarilgan":75202}]
[{"ustuvorlik":"past","jami":"124368","bajarilgan":"74804"},{"ustuvorlik":"orta","jami":"250254","bajarilgan":"149680"},{"ustuvorlik":"yuqori","jami":"125378","bajarilgan":"75202"}]
top-3 teg: Mongo ($unwind) 745.71 ms, Postgres (JOIN) 71.76 ms- GROUP BY ustuvorlik — Mongo246,78 ms
- GROUP BY ustuvorlik — Postgres41,11 ms
- Top-3 teg — Mongo ($unwind)745,71 ms
- Top-3 teg — Postgres (JOIN)71,76 ms
Manba: IlmHamroh kanon o'lchovi, 2026-10-25: MongoDB 8.3.13 va PostgreSQL 18.6 (Docker, WSL2), Node 24.21.0, i5-12500H; 5 marta, mediana
Bu o'lchovda Postgres guruhlashda 6 marta, teglar bo'yicha 10 marta tez. Kanon izohi: Postgres bu so'rovni parallel bajardi (bir nechta ishchi jarayon — Postgres arxitekturasi). Mongo'da bitta aggregation so'rovi bunday parallel ishchilarga bo'linmaydi (hujjat bo'yicha; kanon buni alohida o'lchamagan). Katta to'plamda 400 096 ta teg bog'lanishi bor — $unwind shuncha oraliq hujjat yasaydi, keyin ularni guruhlaydi. Postgres'da teglar allaqachon alohida qatorlarda.
Xulosa "Mongo sekin" emas. Mongo bitta hujjatni olish va yozishda kuchli, katta tahliliy guruhlashda esa relatsion baza odatda oldinda. Bitta mashina, bitta o'lchov — o'z ma'lumotingizda o'lchang.
9.3 Ikki nozik farq
Natija qatorlariga yana qarang:
- Tartib. Mongo:
orta, past, yuqori— satr sifatida alifbo tartibi. Postgres:past, orta, yuqori—ustuvorlikenum turi, tartib e'lon qilingandagidek (Pul, miqdor va enum'larni modellash). Mongo'da "past < orta < yuqori" tartibi kerak bo'lsa — son maydon yoki$switchbilan tartib raqami. - Son turi. Postgres javobida
"jami":"124368"— satr!pgdrayvericount(*)(bigint) ni JavaScript son aniqligini yo'qotmaslik uchun satr qilib qaytaradi (node-postgres asoslari). Mongo'da — oddiy son.
10. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi
vazifalar-api hisobotlari Postgres'da: statistika — materialized view, teglar — GET /v1/teglar. Kanon aggregation'da:
- Statistika va top teglar — Postgres bilan bir xil sonlar.
$facet(sahifa + jami + guruh),$bucket,$setWindowFields.- Yarim hujjat oqibatlari:
jami 47,_id: nullguruhi va$sizexatosi (Location17124) —$ifNullbilan tuzatildi. explain: optimizator$matchni ko'chirdi; 500 000 da Mongo 246.78 vs Postgres 41.11 ms; disk 18.1 vs 115 MB.
11. Ko'p uchraydigan xatolar
| Xato | Oqibat | To'g'risi |
|---|---|---|
$match oxirida |
Hamma hujjat quvurdan o'tadi, indeks yo'q | $match va $sort boshida |
Maydon nomini $ siz ({ $sum: "narx" }) |
Satr — har hujjatda 0 qo'shiladi | "$narx" |
$size maydonsiz hujjatda |
Location17124 — pipeline yiqiladi |
$ifNull: ["$izohlar", []] |
$lookup + $unwind ning yo'qotishi |
Topilmagan guruhlar jim tushib qoladi | preserveNullAndEmptyArrays: true |
Teng qiymatlarda bitta kalit bo'yicha $sort |
Tartib har safar boshqacha | Ikkinchi kalit (_id) |
| Enum'ni satr deb saralash | orta, past, yuqori |
Son maydon yoki $switch |
| "Baza ichida — doim tez" | Katta guruhlashda kutilmagan sekinlik | explain va o'lchov |
Yana bir chegara: har pipeline bosqichi sukut bo'yicha xotira limitiga ega (MongoDB hujjatida — 100 MB); oshsa, server vaqtinchalik fayllarga yozadi (allowDiskUse, yangi versiyalarda sukut yoqiq). Kanon bu chegaraga yetmadi.
12. Mashqlar
Mashqlar kurs/mashqlar/27/48-aggregation/ da. 2- va 3-mashq yechimlari Node 24 da tekshirilgan (biz ishga tushirdik — pipeline'ning kichik modeli, bazasiz).
1-mashq (oson): to'ldiring
- SQL'dagi
WHEREning bosqichi — - Massivni har element uchun alohida hujjatga aylantiradigan bosqich —
{ $sum: 1 }nima qiladi?- Maydon yo'q bo'lsa zaxira qiymat beradigan operator —
row_number()ning Mongo o'xshashi —
Yechim
- $match — boshida bo'lsa indeks ishlatadi.
- $unwind — bo'sh massivli hujjat yo'qoladi.
- sanaydi — har hujjat uchun 1 qo'shadi.
- $ifNull — SQL'dagi
coalesce. - $documentNumber —
$setWindowFieldsichida.
2-mashq (o'rta): mini pipeline
pipeline(hujjatlar, bosqichlar) yozing. Qo'llab-quvvatlasin: $match (faqat tenglik), $unwind ("$maydon"), $group (_id: "$maydon", akkumulyator faqat { $sum: 1 }), $sort (bitta maydon, 1/-1), $limit. Kanon "top teglar" pipeline'ini kichik ma'lumotda takrorlang.
Ishora: har bosqich — massivdan massiv qaytaradigan funksiya; reduce bilan zanjir (reduce).
Yechim
const ol = (h, yol) => h[yol.slice(1)]; // "$teglar" -> h.teglar
const BOSQICHLAR = {
$match: (hh, f) => hh.filter((h) =>
Object.entries(f).every(([k, v]) => h[k] === v)),
$unwind: (hh, yol) => hh.flatMap((h) =>
(ol(h, yol) ?? []).map((e) => ({ ...h, [yol.slice(1)]: e }))),
$group: (hh, { _id, ...akk }) => {
const m = new Map();
for (const h of hh) {
const k = ol(h, _id);
const g = m.get(k) ?? { _id: k };
for (const nom of Object.keys(akk)) g[nom] = (g[nom] ?? 0) + 1;
m.set(k, g);
}
return [...m.values()];
},
$sort: (hh, s) => {
const [[k, yon]] = Object.entries(s);
return [...hh].sort((a, b) =>
(a[k] > b[k] ? 1 : a[k] < b[k] ? -1 : 0) * yon);
},
$limit: (hh, n) => hh.slice(0, n),
};
function pipeline(hujjatlar, bosqichlar) {
return bosqichlar.reduce((hh, b) => {
const [[nom, arg]] = Object.entries(b);
return BOSQICHLAR[nom](hh, arg);
}, hujjatlar);
}
const vazifalar = [
{ egasi: 1, teglar: ["ish", "tolov"] },
{ egasi: 1, teglar: ["ish"] },
{ egasi: 2, teglar: ["uy", "ish"] },
{ egasi: 2, teglar: [] },
{ egasi: 1, teglar: ["uy"] },
];
const top = pipeline(vazifalar, [
{ $unwind: "$teglar" },
{ $group: { _id: "$teglar", soni: { $sum: 1 } } },
{ $sort: { soni: -1 } },
{ $limit: 2 },
]);
console.log(JSON.stringify(top));
const aziz = pipeline(vazifalar, [
{ $match: { egasi: 1 } },
{ $unwind: "$teglar" },
{ $group: { _id: "$teglar", n: { $sum: 1 } } },
{ $sort: { _id: 1 } },
]);
console.log(JSON.stringify(aziz));Konsolda:
[{"_id":"ish","soni":3},{"_id":"uy","soni":2}]
[{"_id":"ish","n":2},{"_id":"tolov","n":1},{"_id":"uy","n":1}]To'rtinchi vazifa (bo'sh teglar) $unwind dan keyin yo'qoldi — Mongo'dagidek. reduce — har bosqich oldingisining natijasini oladi: pipeline'ning aynan ta'rifi. Haqiqiy $sort barqaror tartib kafolatlamaydi — shuning uchun kanon ikkinchi kalit qo'shgan.
3-mashq (qiyin): $bucket modeli
bucket(qiymatlar, chegaralar, sukut) yozing: chegaralar = [0, 1, 2, 5] — [0,1), [1,2), [2,5) guruhlari; tashqaridagilar — sukut guruhiga. Natija — { _id, soni } massivi, faqat bo'sh bo'lmagan guruhlar, chegaralar tartibida (sukut — oxirida). Kanon natijasini qayta hosil qiling (38 ta 0, 5 ta 1, 5 ta 2–4) va yana 3 ta vazifa qo'shing: 7, 9 va 4 izohli.
Ishora: findIndex bilan qaysi oraliqqa tushishini toping.
Yechim
function bucket(qiymatlar, chegaralar, sukut) {
const soni = new Map();
for (const q of qiymatlar) {
const i = chegaralar.findIndex(
(c, j) => j < chegaralar.length - 1
&& q >= c && q < chegaralar[j + 1]);
const kalit = i === -1 ? sukut : chegaralar[i];
soni.set(kalit, (soni.get(kalit) ?? 0) + 1);
}
const tartib = [...chegaralar.slice(0, -1), sukut];
return tartib.filter((k) => soni.has(k))
.map((k) => ({ _id: k, vazifalar: soni.get(k) }));
}
const izohlar = [
...Array(38).fill(0), ...Array(5).fill(1),
2, 2, 3, 3, 4,
];
const chegara = [0, 1, 2, 5];
bucket(izohlar, chegara, "5+").forEach((g) =>
console.log(JSON.stringify(g)));
console.log("---");
bucket([...izohlar, 7, 9, 4], chegara, "5+").forEach((g) =>
console.log(JSON.stringify(g)));Konsolda:
{"_id":0,"vazifalar":38}
{"_id":1,"vazifalar":5}
{"_id":2,"vazifalar":5}
---
{"_id":0,"vazifalar":38}
{"_id":1,"vazifalar":5}
{"_id":2,"vazifalar":6}
{"_id":"5+","vazifalar":2}Birinchi uch qator — kanon chiqishi bilan bir xil. 2–4 izohlilarning aniq taqsimoti kanonda ko'rinmaydi: biz 2, 2, 3, 3, 4 deb oldik, guruh soni baribir 5. 5 chegarasi o'zi [2,5) ga kirmaydi — yuqori chegara ochiq; 7 va 9 — default guruhida. Mongo'da default berilmasa, chegaradan tashqaridagi qiymat xato beradi (hujjat bo'yicha).
13. Real ishda
- Dashboard va hisobotlar — Mongo'li loyihalarda statistika endpoint'larining deyarli hammasi aggregation;
$facet— "sahifa + jami + filtrlar soni" uchun standart. - Katta tahlil — ko'pincha Mongo'dan alohida tahliliy bazaga (ClickHouse, BigQuery, Postgres) ko'chiriladi; kanon o'lchovi sababini ko'rsatdi.
- Materialized natija —
$merge/$outbosqichlari pipeline natijasini kolleksiyaga yozadi (Postgres'dagi materialized view kabi; hujjat bo'yicha, kanon sinamadi). - Intervyu savollari: "
$matchqayerda bo'lishi kerak va nega?", "$lookupqanday ishlaydi?", "$unwindnima qiladi?", "aggregation'da indeks qachon ishlaydi?".
Xulosa
- Aggregation pipeline — bosqichlar massivi; har bosqich oldingisining natijasini oladi;
"$maydon"— maydon qiymati. - Asosiy bosqichlar:
$match,$group(akkumulyatorlar$sum,$cond),$lookup,$unwind,$project,$sort,$limit; qo'shimcha:$facet,$bucket,$setWindowFields. - Sxemasizlik hisobotda chiqadi: yarim hujjat
jami 47,_id: nullva$sizexatosini berdi —$ifNullbilan himoya. $match/$sortboshida — indeks ishlaydi; optimizator ba'zan o'zi ko'chiradi, lekin unga tayanmang.- Kanon 500 000 da: Mongo 246.78 vs Postgres 41.11 ms (guruhlash), 745.71 vs 71.76 ms (teglar); disk 18.1 vs 115 MB.
Keyingi dars: MongoDB indekslari — COLLSCAN va IXSCAN, ESR qoidasi, multikey, text, TTL va partial indekslar 500 000 hujjatda.
Manbalar
- MongoDB 8 hujjati — Aggregation Pipeline: https://www.mongodb.com/docs/manual/core/aggregation-pipeline/
- MongoDB hujjati — Aggregation Pipeline Optimization (bosqichlarni ko'chirish): https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/
- MongoDB hujjati —
$setWindowFields,$bucket,$facet: https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/ - MongoDB hujjati — WiredTiger siqish: https://www.mongodb.com/docs/manual/core/wiredtiger/
- Kanon o'lchovi (IlmHamroh, 2026-10): Node 24.21.0, mongodb 7.7.0, MongoDB 8.3.13, PostgreSQL 18.6 (Docker, WSL2), i5-12500H. Brauzer misoli va mashq yechimlari — biz ishga tushirdik (Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!