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

MongoDB replica set, tranzaksiyalar va change streams: write concern, session va real vaqt

Qisqacha: Replica set — bir xil ma'lumotni saqlaydigan bir nechta MongoDB serveri: bittasi primary (yozadi), qolganlari secondary (nusxa oladi). Write concern yozuv qachon "tasdiqlandi" deb hisoblanishini belgilaydi: kanonda 300 ta yozuv w: 1 bilan 561 ms, w: "majority" bilan 2 577 ms, w: 0 bilan 61 ms. Bir nechta hujjatni "hammasi yoki hech biri" qilib o'zgartirish — session va withTransaction: 20 parallel o'tkazmada pul yo'qolmadi (jami 150), tranzaksiyasiz uzilishda esa 5 so'm yo'qoldi. Change streams o'zgarishlarni real vaqtda beradi va resume token bilan uzilgan joydan davom etadi.

Bu darsda

  • Replica set, primary, secondary va oplog nima ekanini tushuntira olasiz.
  • Write concern (w, j) va read concern/read preference ni tanlaysiz, narxini o'lchov bilan bilasiz.
  • Session va withTransaction bilan pul o'tkazmasini yozasiz va WriteConflict'da nima bo'lishini ko'rasiz.
  • Change streams bilan o'zgarishlarni tinglaysiz va resume token bilan yo'qotmasdan davom etasiz.
  • Bitta tugunli sinov bazasining cheklovlarini halol baholaysiz.

Oldin bilishingiz kerak: MongoDB sxema dizayn naqshlari, Tranzaksiya asoslari, Read phenomena va izolyatsiya darajalari, LISTEN/NOTIFY.

1. Nega bu kerak?

Hozirgacha MongoDB'ni bitta server deb ko'rdik. Prodakshnda bunday qilinmaydi: server o'chsa yoki disk buzilsa — ma'lumot va ilova yo'qoladi. Shuning uchun Mongo deyarli doim replica set ko'rinishida ishlaydi: kamida uchta server bir xil ma'lumotni saqlaydi.

Bu uchta yangi savol tug'diradi:

  1. Yozuv nechta serverga yetganda "saqlandi" deymiz? (write concern)
  2. Ikki hujjatni birga o'zgartirish kerak bo'lsa-chi? Masalan, Azizdan Dilnozaga pul o'tkazish. (tranzaksiya)
  3. Boshqa servis o'zgarishlarni qanday bilib oladi? (change streams)

O'xshatish: bank filiallari. Pul qo'yganingizda kassir "qabul qilindi" deydi. Lekin bu faqat shu filial daftarigami, yoki markaziy ofisga ham yetib bordimi? Filialda yong'in chiqsa — farq juda muhim.

Kanon bazasi — bitta tugunli replica set rs0 (Docker, MongoDB tuzilishi darsidagi rs.initiate). Tranzaksiya va change streams uchun replica set rejimi shart — shuning uchun bitta server bo'lsa ham shu rejimda. Haqiqiy uch tugunli set va failover (primary almashishi) sinalmagan — bu darsda ular hujjat bo'yicha, ochiq aytib. Chiqishlar: kanon o'lchovi (IlmHamroh, 2026-10), MongoDB 8.3.13, mongodb 7.7.0, Node 24.21.0, i5-12500H.

2. Replica set qanday tuzilgan

2.1 Primary, secondary va oplog

flowchart LR
  I["Ilova (drayver)"] -->|yozish| P["PRIMARY"]
  P -->|oplog| S1["SECONDARY 1"]
  P -->|oplog| S2["SECONDARY 2"]
  I -.->|"o'qish (ixtiyoriy)"| S1
  S1 <-->|heartbeat| S2
  S1 <-->|heartbeat| P

Bu diagramma — hujjat bo'yicha umumiy rasm (kanonda faqat PRIMARY bor):

  • Primary — yagona yozadigan server. Har o'zgarishni oplog ga yozadi (operations log — maxsus kolleksiya, Postgres'dagi WAL'ning o'xshashi).
  • Secondary — primary'ning oplog'ini o'qib, o'zida takrorlaydi. Nusxa bir oz kechikishi mumkin (replication lag).
  • Heartbeat — a'zolar bir-birini doimiy tekshiradi. Primary javob bermasa, qolganlar saylov o'tkazib, yangi primary tanlaydi (odatda soniyalar ichida — hujjat bo'yicha, kanonda sinalmagan). Drayver yangi primary'ni topologiya orqali topadi (native driver darsidagi directConnection muhokamasi).

Saylov uchun ko'pchilik (majority) ovozi kerak: 3 a'zodan 2, 5 dan 3. Shuning uchun a'zolar soni toq bo'ladi: 3 tugun bittasini yo'qotsa ham ishlaydi, 4 tugun ham faqat bittasini — ortiqcha xarajat.

2.2 Kanondagi holat

js
const st = await c.db("admin").command({ replSetGetStatus: 1 });
const h = await c.db("admin").command({ hello: 1 });
text
{"set":"rs0","azolar":[{"nom":"127.0.0.1:27017","holat":"PRIMARY"}],"writeMajorityCount":1}
{"isWritablePrimary":true,"hosts":["127.0.0.1:27017"],"setName":"rs0"}

Bitta a'zo, u PRIMARY. writeMajorityCount: 1 — ko'pchilik bitta tugundan iborat. hosts — konteyner ichidagi manzil (127.0.0.1:27017) — shuning uchun tashqaridan directConnection=true kerak bo'lgan.

3. Write concern: "saqlandi" nimani anglatadi

3.1 Variantlar va narxi

Write concern — yozuv tasdiqlanishi uchun shart:

Write concern Ma'nosi
w: 0 javob kutilmaydi — "yubordim, xolos"
w: 1 primary qabul qildi (xotirada)
w: "majority" ko'pchilik a'zolar qabul qildi
j: true jurnalga (diskka) yozildi

Kanon har variantda 300 ta insertOne ni ketma-ket yubordi:

js
for (const wc of [{ w: 1 }, { w: "majority" },
  { w: 1, j: true }, { w: 0 }]) {
  const col = db.collection("wc_sinov", { writeConcern: wc });
  const t = performance.now();
  for (let i = 0; i < 300; i++) await col.insertOne({ i });
  console.log(`${JSON.stringify(wc)}: ${(performance.now() - t)
    .toFixed(0)} ms`);
}
text
{"w":1}: 561 ms
{"w":"majority"}: 2577 ms
{"w":1,"j":true}: 2513 ms
{"w":0}: 61 ms
300 ta insertOne: write concern narxi
  • w: 061 ms
  • w: 1561 ms
  • w: 1, j: true2 513 ms
  • w: majority2 577 ms

Manba: IlmHamroh kanon o'lchovi, 2026-10-25: MongoDB 8.3.13, bitta tugunli rs0 (Docker, WSL2), mongodb 7.7.0, Node 24.21.0; bitta o'lchov

3.2 Raqamlarni o'qish

  • w: 0 — 61 ms. Javob kutilmaydi. Tez, lekin xato bo'lsa (masalan, takror kalit) — bilmaysiz ham. Faqat yo'qolsa ham bo'ladigan ma'lumot uchun (statistika hisoblagichi).
  • w: 1 — 561 ms. Primary xotirasida — lekin hali diskda emas. Server shu soniyada o'chsa, yozuv yo'qolishi mumkin.
  • j: true — 2 513 ms. Jurnal (journal) diskka yozilguncha kutiladi: har yozuv fsync. Postgres'dagi synchronous_commit = on g'oyasi.
  • w: "majority" — 2 577 ms. Bitta tugunda "ko'pchilik" — o'zi; sekinlik j: true ga deyarli teng. Sabab: MongoDB'da majority yozuv sukut bo'yicha jurnalga yozilishini ham kutadi (hujjat bo'yicha: writeConcernMajorityJournalDefault). Haqiqiy uch tugunli setda bunga secondary'gacha tarmoq aylanishi qo'shiladi.

MongoDB 5.0 dan beri sukut write concern — "majority" (hujjat bo'yicha). Ya'ni Mongo sukut bo'yicha xavfsiz tomonni tanlaydi. Bu mashinada bitta yozuv majority bilan ~8.6 ms, w: 1 bilan ~1.9 ms (2 577 / 300 va 561 / 300) — farq "ishonchlilik narxi".

3.3 A'zolar yetmasa

js
await db.collection("wc_sinov", {
  writeConcern: { w: 3, wtimeoutMS: 1000 },
}).insertOne({ x: 1 });
text
w: 3 (a'zolar yetmaydi) wtimeout bilan: MongoWriteConcernError: Not enough data-bearing nodes

w: 3 — "uchta a'zo qabul qilsin", bizda esa bitta. Xato: "ma'lumot saqlaydigan tugunlar yetarli emas". wtimeoutMS — kutish chegarasi; usiz w bajarilmaydigan holatda so'rov osilib qolishi mumkin. Diqqat: MongoWriteConcernError "yozuv bekor qilindi" degani emas — primary'da yozuv bo'lishi mumkin, faqat kerakli tasdiq olinmadi (hujjat bo'yicha).

Tekshirib ko'ring: To'lov tizimida buyurtma holatini yozyapsiz. w: 1 va w: "majority" dan qaysi birini tanlaysiz? Nega?

Javob

w: "majority". w: 1 da primary yozuvni tasdiqlab, keyin secondary'larga uzatmasdan o'chib qolsa, yangi primary bu yozuvni bilmaydi. Eski primary qaytganda yozuv orqaga qaytariladi (rollback). Mijoz pul to'lagan, "tasdiqlandi" xabarini olgan — lekin buyurtma yo'q. Majority yozuv esa saylovdan keyin ham saqlanadi. Sekinlik — bu kafolat narxi.

4. Read concern va read preference

O'qishda ham ikki tanlov bor:

  • Read concern — qanday izchillikdagi ma'lumot o'qilsin: local (sukut — primary'dagi oxirgi, hali majority'ga yetmagan bo'lishi mumkin), majority (faqat ko'pchilikka yetgan — rollback bo'lmaydigan), snapshot (tranzaksiya ichida bir lahzadagi holat).
  • Read preference — qaysi a'zodan o'qilsin: primary (sukut), primaryPreferred, secondary, secondaryPreferred, nearest.

Secondary'dan o'qish primary yukini kamaytiradi, lekin ma'lumot eskirgan bo'lishi mumkin: foydalanuvchi vazifa qo'shib, sahifani yangilasa — uni ko'rmasligi mumkin ("o'z yozuvini o'qiy olmaslik"). Shuning uchun ko'p ilovalar primary'dan o'qiydi, secondary'ni hisobot va tahlil uchun qoldiradi.

Kanon bitta tugunda readPreference=secondary ni sinadi:

js
const k2 = new MongoClient(MONGO_URL("bank").replace(
  "directConnection=true",
  "directConnection=true&readPreference=secondary"));
await k2.connect();
await k2.db("bank").collection("hisoblar").findOne();
text
o'qidi

Kutilgan xato chiqmadi: secondary yo'q, lekin o'qish muvaffaqiyatli. Sabab — directConnection=true: drayver topologiyani qidirmaydi va to'g'ridan shu serverdan (PRIMARY'dan) o'qidi. Ya'ni bu sinov secondary'dan o'qishni ko'rsatmaydi — faqat sinov muhitida sozlama jim e'tiborsiz qolishi mumkinligini. Haqiqiy secondary bilan — sinalmagan.

5. Tranzaksiya: bir nechta hujjat birga

5.1 Qachon kerak

Populate va embed vs reference darsida aytdik: bitta hujjatga yozish doim atomik. Shuning uchun Mongo'da ko'p muammo tranzaksiyasiz hal bo'ladi — bog'liq ma'lumotni bitta hujjatga joylab. Lekin ba'zan ikki hujjat bor: A hisobi va B hisobi. Pul o'tkazmasi — ikki o'zgarish, va ular "hammasi yoki hech biri" bo'lishi shart (Tranzaksiya asoslari).

MongoDB 4.0 dan beri replica set'da bir nechta hujjatli tranzaksiyalar bor. Ular session ichida ishlaydi.

5.2 withTransaction

Kanon o'tkazma funksiyasi (qatorlar bo'lingan):

js
await H.insertMany([
  { _id: "A", balans: 100 }, { _id: "B", balans: 50 },
]);

async function otkaz(summa, xatoQil = false) {
  const s = c.startSession();
  try {
    await s.withTransaction(async () => {
      const r = await H.updateOne(
        { _id: "A", balans: { $gte: summa } },
        { $inc: { balans: -summa } },
        { session: s },
      );
      if (r.modifiedCount !== 1) {
        throw new Error("mablag' yetarli emas");
      }
      if (xatoQil) throw new Error("ataylab xato (2-yozuvdan oldin)");
      await H.updateOne(
        { _id: "B" }, { $inc: { balans: summa } }, { session: s });
    }, {
      readConcern: { level: "snapshot" },
      writeConcern: { w: "majority" },
    });
    return "ok";
  } catch (x) {
    return `bekor: ${x.message}`;
  } finally {
    await s.endSession();
  }
}
console.log(await otkaz(30), "|", await otkaz(30, true), "|",
  await otkaz(500));

H — bank bazasidagi hisoblar kolleksiyasi. Muhim qismlar:

  • c.startSession() — session ochish; har so'rovga { session: s } — "bu so'rov tranzaksiya ichida". Unutilsa, so'rov tranzaksiyadan tashqarida bajariladi va bekor qilinmaydi.
  • { _id: "A", balans: { $gte: summa } } — filtrning o'zi mablag'ni tekshiradi. modifiedCount !== 1 — pul yetmadi. Tekshirish va yechish bitta atomik amalda (Optimistic concurrency darsidagi shartli UPDATE naqshi).
  • withTransaction(fn) — tranzaksiyani boshlaydi, fn ni bajaradi, commit qiladi. fn xato tashlasa — abort: hamma o'zgarish bekor.
  • readConcern: "snapshot", writeConcern: "majority" — tranzaksiya bir lahzadagi holatni ko'radi va commit ko'pchilikka yetadi.
text
ok | bekor: ataylab xato (2-yozuvdan oldin) | bekor: mablag' yetarli emas
[{"_id":"A","balans":70},{"_id":"B","balans":80}]
  1. 30 o'tdi: A 100 → 70, B 50 → 80.
  2. Ataylab xato: A dan 30 yechilgan edi, lekin B ga qo'shishdan oldin xato — ikkalasi bekor, A hali 70.
  3. 500 — mablag' yetmadi.

Jami: 70 + 80 = 150. Boshida ham 150 edi.

5.3 Raqobat: 20 parallel o'tkazma

Endi qiyin holat: A da 70 so'm, bir vaqtda 20 ta "10 so'm o'tkaz" so'rovi:

js
const natijalar = await Promise.all(
  Array.from({ length: 20 }, () => otkaz(10)));
text
{"ok":7,"bekor":13,"balans":[{"_id":"A","balans":0},{"_id":"B","balans":150}],"jami":150}

7 tasi o'tdi (7 × 10 = 70 — A dagi hamma pul), 13 tasi "mablag' yetarli emas". A — 0, manfiy emas. Jami — 150. Pul na yo'qoldi, na paydo bo'ldi.

Ichkarida nima bo'ldi? Bir vaqtda bitta hujjatni o'zgartirmoqchi bo'lgan tranzaksiyalar to'qnashadi — MongoDB ikkinchisiga WriteConflict xatosini beradi (Postgres'dagi serialization failure kabi — Deadlock va serialization failure). Bu xato TransientTransactionError belgisi bilan keladi, va withTransaction bunday tranzaksiyani o'zi qayta boshlaydi. Qayta urinishda A ning yangi balansi ko'rinadi — pul tugagan bo'lsa, "mablag' yetarli emas". Kanon chiqishida qayta urinishlar soni ko'rinmaydi — faqat yakuniy natija; mexanizm hujjat bo'yicha.

Ikki o'tkazma A da 10 so'm qolgan paytda to'qnashsa, hujjat bo'yicha shunday bo'ladi:

Qadam 1-sessiya 2-sessiya Natija
1 tranzaksiya boshlandi (snapshot: A = 10) tranzaksiya boshlandi (snapshot: A = 10) ikkalasi bir xil holatni ko'radi
2 updateOne A: 10 → 0 — 1-sessiya hujjatni o'zgartirdi, hali commit yo'q
3 — updateOne A WriteConflict: hujjatni boshqa tranzaksiya o'zgartirgan
4 B ga +10, commit withTransaction qayta boshlaydi A = 0, B oshdi
5 — yangi snapshot: A = 0, filtr balans >= 10 mos emas "mablag' yetarli emas" — bekor

Jadvalga qarang (A va B — hisoblar, 1 va 2 — sessiyalar): ikkinchi o'tkazma eski balansni ko'rib pul yechib qo'ymaydi — u xato bilan to'xtaydi va qayta urinishda haqiqiy balansni ko'radi.

withTransaction siz, startTransaction/commitTransaction bilan qo'lda yozilsa — qayta urinish mantiqini o'zingiz yozishingiz kerak. Shuning uchun withTransaction tavsiya qilinadi.

5.4 Tranzaksiyasiz — yarim holat

Kanon tranzaksiyasiz o'tkazma ikki alohida updateOne bilan qanday buzilishini ko'rsatdi:

js
await H.updateOne({ _id: "A" }, { $inc: { balans: -5 } });
// ... jarayon shu yerda yiqildi deb faraz qilamiz (B ga yozilmadi)
text
{"yarim_holat_jami":145,"kutilgan":150}

A dan 5 yechildi, B ga yetmadi — 5 so'm yo'qoldi. (Kanon keyin uni qaytardi.) Ikki yozuv orasida server o'chishi, deploy yoki kill — hammasi shunga olib keladi.

5.5 Tranzaksiya cheklovlari

Hujjat bo'yicha (kanon sinamadi):

  • Tranzaksiya sukut bo'yicha 60 soniyadan uzoq yashay olmaydi (transactionLifetimeLimitSeconds) — qisqa saqlang.
  • Faqat replica set yoki sharded klasterda; oddiy bitta serverda (standalone) — yo'q.
  • Ko'p hujjatli tranzaksiya oddiy yozuvdan qimmat. MongoDB hujjatining o'zi aytadi: ko'p hollarda to'g'ri sxema (embed) tranzaksiyaga ehtiyojni yo'qotadi. Tranzaksiya — istisno, odatiy yo'l emas.

Tekshirib ko'ring: withTransaction ichida H.updateOne(…) ga { session: s } berishni unutdingiz. Xato bo'lib tranzaksiya bekor bo'lsa, nima bo'ladi?

Javob

Session'siz so'rov tranzaksiyaga kirmaydi — u darhol, alohida bajariladi va abort uni bekor qilmaydi. Natija — xuddi «Tranzaksiyasiz — yarim holat» dagidek. Bundan tashqari, withTransaction qayta urinsa, session'siz yozuv ikki marta bajarilishi mumkin. Har so'rovga session berilganini kod ko'rib chiqishda alohida tekshiring.

6. Change streams: o'zgarishlarni tinglash

6.1 watch

Change stream — kolleksiya (yoki baza) o'zgarishlarini real vaqtda oqim sifatida olish. Ichida u oplog'ni o'qiydi — shuning uchun replica set kerak. Postgres'dagi LISTEN/NOTIFY ga o'xshaydi, lekin farqi katta: o'zgarishning o'zini (hujjatni) beradi va uzilishdan keyin davom eta oladi.

js
const oqim = V.watch(
  [{ $match: { operationType: {
    $in: ["insert", "update", "delete"] } } }],
  { fullDocument: "updateLookup" },
);
for await (const e of oqim) {
  kelgan.push(`${e.operationType} ${e.documentKey._id}`
    + (e.fullDocument ? " " + e.fullDocument.matn : ""));
  token = e._id;            // resume token
  if (kelgan.length === 3) break;
}

(Kanonda tinglash alohida async funksiyada ishladi; keyin u 500 ms kutib, insert, update va delete qildi.)

  • watch(pipeline) — aggregation bosqichlari bilan filtr: faqat kerakli hodisalar.
  • fullDocument: "updateLookup" — update hodisasida o'zgargan maydonlardan tashqari to'liq hujjat ham kelsin (hujjatni alohida o'qib qo'shadi).
  • e._id — resume token: oqimdagi shu nuqtaning "xatcho'pi".
text
keldi (40 ms): [ 'insert 1 Non olish', 'update 1 Non olish (bajarildi)', 'delete 1' ]

Uch o'zgarish 40 ms ichida keldi. Delete'da fullDocument yo'q — hujjat endi mavjud emas.

6.2 Resume token: hech narsa yo'qolmaydi

Asosiy savol: tinglovchi servis qayta ishga tushsa (deploy, xato), shu orada bo'lgan o'zgarishlar-chi? Kanon oqimni yopdi, yopiq paytda hujjat qo'shdi, keyin oxirgi token bilan qayta ochdi:

js
await oqim.close();
await V.insertOne({ _id: 2, matn: "Oqim yopiq paytda qo'shildi" });
const davom = V.watch([], { resumeAfter: token });
const keyingi = await davom.next();
text
resumeAfter bilan — yopiq paytdagi o'zgarish yo'qolmadi: insert 2 Oqim yopiq paytda qo'shildi

Oqim yopiq paytdagi insert yo'qolmadi. Token'ni bazada saqlab borsangiz (har qayta ishlangan hodisadan keyin), servis qayta ishga tushganda aynan to'xtagan joydan davom etadi. LISTEN/NOTIFY'da bunday imkoniyat yo'q edi: tinglovchi yo'q paytdagi xabar yo'qoladi.

Chegara: token oplog'da hali bor bo'lsagina ishlaydi. Oplog cheklangan hajmli (eski yozuvlar o'chib boradi); servis juda uzoq to'xtab qolsa, token eskiradi va oqimni qayta boshlash kerak bo'ladi (hujjat bo'yicha).

Change streams nimaga ishlatiladi: kesh tozalash, qidiruv indeksini yangilash, boshqa servislarga hodisa yuborish, real vaqtdagi bildirishnomalar (WebSocket — 29-qismda). Extended reference nusxalarini sinxronlash (sxema dizayn naqshlari) ham shu yo'l bilan qilinadi.

sequenceDiagram
  participant A as Ilova
  participant M as MongoDB (oplog)
  participant T as Tinglovchi servis
  A->>M: insert / update / delete
  M-->>T: hodisa + resume token
  T->>T: tokenni saqlash
  Note over T: servis qayta ishga tushdi
  A->>M: insert (servis yo'q paytda)
  T->>M: watch(resumeAfter: token)
  M-->>T: o'tkazib yuborilgan hodisa

Diagrammaga qarang: tinglovchi o'chiq paytdagi hodisa oplog'da kutib turdi va token bilan qaytib kelganda yetib keldi.

7. Loyiha qadami: kanon tajribasi, loyihaga kirmaydi

vazifalar-api Postgres'da: tranzaksiyalar (26-qism), real vaqt — 29-qismda. Kanon Mongo'da (bitta tugunli rs0):

  1. Holat: bitta PRIMARY, writeMajorityCount: 1.
  2. Write concern: w:1 561 ms, majority 2 577 ms, j:true 2 513 ms, w:0 61 ms; w:3 — Not enough data-bearing nodes.
  3. Tranzaksiya: ok / ataylab xato → ikkalasi bekor / mablag' yetmaydi; 20 parallel — 7 ok, 13 bekor, jami 150; tranzaksiyasiz — 145.
  4. Change streams: 3 hodisa 40 ms da, resumeAfter — yo'qotishsiz.
  5. readPreference=secondary + directConnection — PRIMARY'dan o'qidi; haqiqiy failover sinalmagan.

8. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
Muhim yozuvga w: 0 yoki w: 1 Failover'da yozuv yo'qolishi mumkin w: "majority" (sukut)
w > a'zolar soni, wtimeoutMS siz So'rov osilib qoladi wtimeoutMS bering
Tranzaksiyada { session } unutilgan O'zgarish bekor qilinmaydi Har so'rovga session
Qo'lda startTransaction, qayta urinishsiz WriteConflict'da xato withTransaction
Har narsaga tranzaksiya Sekin, murakkab Avval sxema: embed
Uzoq tranzaksiya 60 s chegara, qulflar Qisqa saqlang
Change stream token'ini saqlamaslik Qayta ishga tushganda hodisalar yo'qoladi Token'ni bazada saqlang
Secondary'dan o'qib, darhol yangi yozuvni kutish "Saqlandi, lekin ko'rinmayapti" Primary'dan o'qing

9. Mashqlar

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

1-mashq (oson): to'ldiring

  • Yagona yozadigan a'zo —
  • O'zgarishlar jurnali (kolleksiya) —
  • Ko'pchilik a'zolar tasdiqlashi: w: ""
  • O'tkazib yuborilgan hodisalardan davom etish sozlamasi —
  • Kanonda 20 parallel o'tkazmadan nechtasi o'tdi?
Yechim
  • primary — secondary'lar oplog'dan nusxa oladi.
  • oplog — Postgres WAL'iga o'xshash.
  • majority — MongoDB 5.0 dan sukut.
  • resumeAfter — resume token bilan.
  • 7 — A dagi 70 so'm 10 dan yetti marta yetdi; 13 tasi "mablag' yetarli emas".

2-mashq (o'rta): ko'pchilik va chidamlilik

kopchilik(n) — n ta ovoz beruvchi a'zoli set uchun ko'pchilik sonini (Math.floor(n / 2) + 1), chidamlilik(n) — nechta a'zo yo'qolsa ham set primary sayloy oladi (n - kopchilik(n)) qaytarsin. 1 dan 7 gacha jadval chiqaring va 4 a'zoli set nega 3 a'zolidan yaxshi emasligini ko'rsating.

Ishora: String.prototype.padStart — jadval ustunlari uchun.

Yechim
js
const kopchilik = (n) => Math.floor(n / 2) + 1;
const chidamlilik = (n) => n - kopchilik(n);

console.log("a'zo | ko'pchilik | yo'qotsa bo'ladi");
for (let n = 1; n <= 7; n++) {
  console.log(String(n).padStart(4), "|",
    String(kopchilik(n)).padStart(10), "|",
    String(chidamlilik(n)).padStart(16));
}
console.log("4 vs 3:", chidamlilik(4) === chidamlilik(3)
  ? "bir xil chidamlilik, bitta ortiqcha server" : "farq bor");

Konsolda:

text
a'zo | ko'pchilik | yo'qotsa bo'ladi
   1 |          1 |                0
   2 |          2 |                0
   3 |          2 |                1
   4 |          3 |                1
   5 |          3 |                2
   6 |          4 |                2
   7 |          4 |                3
4 vs 3: bir xil chidamlilik, bitta ortiqcha server

Birinchi qator — kanon holati: writeMajorityCount: 1, bitta tugun yo'qolsa — hammasi to'xtaydi. Juft sonlar chidamlilikni oshirmaydi — shuning uchun replica set'lar 3, 5, 7 a'zoli. 2 a'zoli set hatto 1 tadan ham yomon: bittasi o'chsa, ikkinchisi ko'pchilik bo'lolmaydi.

3-mashq (qiyin): o'tkazma modeli

Xotiradagi hisoblar bilan ikki funksiya yozing:

  1. otkaz(h, summa) — atomik: A da balans >= summa bo'lsa, A dan yechib B ga qo'shsin va "ok" qaytarsin, aks holda hech narsani o'zgartirmay "bekor".
  2. yarimOtkaz(h, summa) — A dan yechib, B ga yozishdan oldin "uzilish" (throw) — tranzaksiyasiz holat.

Kanonni takrorlang: A 70, B 80; 20 ta 10 so'mlik otkaz → ok/bekor soni va jami; keyin yangi hisoblar (A 70, B 80) bilan bitta yarimOtkaz(5) → jami.

Ishora: atomiklikni JavaScript'da bitta sinxron funksiya ta'minlaydi — u ichida boshqa kod ishlamaydi (event loop).

Yechim
js
function otkaz(h, summa) {
  if (h.A < summa) return "bekor";
  h.A -= summa;
  h.B += summa;
  return "ok";
}

function yarimOtkaz(h, summa) {
  h.A -= summa;
  throw new Error("jarayon yiqildi (B ga yozilmadi)");
}

const h = { A: 70, B: 80 };
const natijalar = Array.from({ length: 20 }, () => otkaz(h, 10));
const ok = natijalar.filter((x) => x === "ok").length;
console.log(JSON.stringify({ ok, bekor: 20 - ok, ...h,
  jami: h.A + h.B }));

const h2 = { A: 70, B: 80 };
try {
  yarimOtkaz(h2, 5);
} catch (x) {
  console.log(x.message);
}
console.log(JSON.stringify({ yarim_holat_jami: h2.A + h2.B,
  kutilgan: 150 }));

Konsolda:

text
{"ok":7,"bekor":13,"A":0,"B":150,"jami":150}
jarayon yiqildi (B ga yozilmadi)
{"yarim_holat_jami":145,"kutilgan":150}

Sonlar kanon bilan bir xil: 7/13, A 0, jami 150; yarim holat — 145. JavaScript'da atomiklik "tekin": funksiya oxirigacha bajariladi, boshqa chaqiruv o'rtaga kirmaydi. MongoDB'da 20 so'rov haqiqatan parallel keladi — shuning uchun session, snapshot va WriteConflict'dagi qayta urinish kerak. Ikkinchi qism esa ko'rsatadi: ikki alohida yozuv orasidagi uzilish — tranzaksiyasiz yo'qotish.

10. Real ishda

  • Prodakshn Mongo — deyarli doim kamida 3 a'zoli replica set (Atlas'da avtomatik); lokal sinov va CI'da — bitta tugunli rs0 (kanondagidek), chunki tranzaksiya va change streams uchun shart.
  • Write concern — sukut majority; tezlik uchun w: 1 ga tushirish — ongli, hujjatlashtirilgan qaror bo'lsin.
  • Change streams — kesh tozalash, qidiruv tizimiga sinxronlash, hodisalar navbatiga uzatish; token saqlash — majburiy qism.
  • Intervyu savollari: "replica set'da nechta a'zo va nega toq?", "write concern majority nima kafolatlaydi?", "Mongo'da tranzaksiya bormi va qachon kerak?", "change stream uzilsa nima bo'ladi?".

Xulosa

  • Replica set: primary yozadi, secondary'lar oplog'dan nusxa oladi, saylov ko'pchilik bilan — a'zolar soni toq (3, 5, 7).
  • Write concern narxi (kanon, 300 yozuv, bitta tugun): w:0 61, w:1 561, j:true 2 513, majority 2 577 ms; sukut — majority.
  • Read concern — izchillik darajasi, read preference — qaysi a'zo; secondary'dan o'qish eskirgan bo'lishi mumkin.
  • Tranzaksiya — session + withTransaction (snapshot, majority); WriteConflict'da o'zi qayta urinadi: 20 parallel — 7 ok, jami 150; tranzaksiyasiz — 145.
  • Change streams — oplog asosidagi real vaqt oqimi; resume token bilan uzilishdan keyin hech narsa yo'qolmaydi.
  • Kanon — bitta tugun: haqiqiy failover va secondary'dan o'qish sinalmagan.

Keyingi dars: MongoDB xavfsizligi va operatsiyasi — operator injection, sanitizeFilter, foydalanuvchi rollari, mongodump/mongorestore va Atlas imkoniyatlari.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
MongoDB replica set, tranzaksiyalar va change streams: write concern, session va real vaqt — IlmHamroh