17-bo‘lim

Tranzaksiyalar va replika to'plami

Bitta hujjat atomarligi, ko'p hujjatli tranzaksiya, sessiya va commit, replika to'plami nima uchun kerak, o'qish va yozish kafolatlari.

🕑 12 daqiqa o‘qish 📄 844 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Bitta hujjat allaqachon atomar
  2. Tranzaksiya qachon kerak
  3. Bekor qilish
  4. withTransaction - xavfsizroq yo'l
  5. Replika to'plami
  6. O'qish va yozish kafolatlari
  7. Xulosa

MongoDB da har bir hujjat yangilanishi allaqachon atomar. Tranzaksiya esa boshqa savolga javob beradi: bir nechta hujjatni birga o'zgartirish kerak bo'lsa-chi?

JavaScript
db.hisob.insertMany([
  { _id: 1, egasi: "Husanboy", balans: 500000 },
  { _id: 2, egasi: "Malika",   balans: 120000 },
  { _id: 3, egasi: "Nodira",   balans: 80000 }
])
db.amal.insertMany([
  { _id: 1, hisob_id: 1, tur: "kirim", summa: 500000 }
])

Bitta hujjat allaqachon atomar #

JavaScript
db.hisob.updateOne({ _id: 1 }, { $inc: { balans: -50000 } })
db.hisob.findOne({ _id: 1 })
Natija
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 1, egasi: 'Husanboy', balans: 450000 }

Bu amal bo'linmas: boshqa hech kim yarim bajarilgan holatni ko'ra olmaydi. Bu kafolat tranzaksiyasiz ham mavjud.

Xuddi shu kafolat bitta hujjat ichidagi bir nechta maydonga ham tegishli:

JavaScript
db.hisob.updateOne(
  { _id: 2 },
  { $inc: { balans: 30000 }, $set: { oxirgi_amal: "kirim" } }
)
db.hisob.findOne({ _id: 2 })
Natija
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{ _id: 2, egasi: 'Malika', balans: 150000, oxirgi_amal: 'kirim' }
Sxema to'g'ri bo'lsa, tranzaksiya kamdan-kam kerak

Bu MongoDB ning asosiy g'oyalaridan biri.

Relyatsion bazada buyurtma va uning qatorlari ikki jadvalda yotadi - shuning uchun ularni birga yozish uchun tranzaksiya shart.

MongoDB da esa qatorlar buyurtma ichida bo'lsa, ularning hammasi bitta hujjat - va bitta hujjat yozuvi allaqachon atomar. Tranzaksiya kerak emas.

Shuning uchun tranzaksiyaga ehtiyoj paydo bo'lsa, avval shunday savol bering: bu ma'lumot haqiqatan ikki xil hujjatda bo'lishi kerakmi?

Ba'zan javob "ha" bo'ladi - pul o'tkazmasi shunga misol. Ikki hisob mustaqil obyekt, ularni bitta hujjatga qo'shib bo'lmaydi.

Tranzaksiya qachon kerak #

Pul o'tkazmasi - klassik misol. Ikkita alohida hujjat birga o'zgarishi kerak:

JavaScript
const s = db.getMongo().startSession()
s.startTransaction()
const h = s.getDatabase("dokon").hisob
h.updateOne({ _id: 1 }, { $inc: { balans: -70000 } })
h.updateOne({ _id: 2 }, { $inc: { balans: 70000 } })
s.commitTransaction()
s.endSession()
db.hisob.find({ _id: { $in: [1, 2] } }, { egasi: 1, balans: 1 })
Natija
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}


[
  { _id: 1, egasi: 'Husanboy', balans: 430000 },
  { _id: 2, egasi: 'Malika', balans: 190000 }
]

Ikkala yangilash birga saqlandi.

Bekor qilish #

JavaScript
const s = db.getMongo().startSession()
s.startTransaction()
const h = s.getDatabase("dokon").hisob
h.updateOne({ _id: 1 }, { $set: { balans: 0 } })
s.abortTransaction()
s.endSession()
db.hisob.findOne({ _id: 1 }, { egasi: 1, balans: 1 })
Natija
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0
}


{ _id: 1, egasi: 'Husanboy', balans: 500000 }

abortTransaction hamma o'zgarishni bekor qildi - balans o'z joyida qoldi.

Tranzaksiyaning to'rt qadami 1. startSession sessiya ochiladi 2. startTransaction amallar boshlanadi 3. Amallar update, insert ... 4a. commitTransaction hammasi saqlandi 4b. abortTransaction hech nima saqlanmadi Sessiyani har doim yoping endSession chaqirilmasa, ochiq tranzaksiya qulflarni ushlab turadi Dasturda try/catch/finally ichida yozing
withTransaction yordamchisi bu to'rt qadamni o'zi boshqaradi

withTransaction - xavfsizroq yo'l #

Qo'lda commit va abort yozish o'rniga, yordamchi funksiya ishlatish afzal:

JavaScript
const s = db.getMongo().startSession()
s.withTransaction(() => {
  const d = s.getDatabase("dokon")
  d.hisob.updateOne({ _id: 1 }, { $inc: { balans: -25000 } })
  d.amal.insertOne({ _id: 2, hisob_id: 1, tur: "chiqim", summa: 25000 })
})
s.endSession()
db.hisob.findOne({ _id: 1 }, { egasi: 1, balans: 1 })
db.amal.countDocuments()
Natija
{ _id: 1, egasi: 'Husanboy', balans: 475000 }
2

withTransaction ikkita ishni o'zi bajaradi:

VazifaIzoh
Xato bo'lsa aborttry/catch yozish shart emas
Vaqtinchalik xatoda qayta urinishTarmoq uzilishi, reja almashuvi
Tranzaksiyani qisqa tuting

MongoDB da tranzaksiyaning 60 soniyalik odatiy chegarasi bor. Undan uzunlari majburan to'xtatiladi.

Lekin muammo chegaraga yetgunicha boshlanadi:

Tranzaksiya davomiyligiOqibat
10 msSezilmaydi
1 soniyaQulflar ushlanib turadi
10 soniyaBoshqa yozuvlar navbatga tushadi
60 soniyaMajburan to'xtatiladi

Tranzaksiya ichida hech qachon quyidagilarni bajarmang:

  • tashqi API ga so'rov;
  • fayl yuklash yoki yuborish;
  • foydalanuvchidan javob kutish;
  • katta hisobot o'qish.

Ularni tranzaksiyadan oldin yoki keyin bajaring. Tranzaksiya ichida faqat bir-biriga bog'liq yozuvlar qolsin.

Replika to'plami #

Tranzaksiyalar faqat replika to'plamida ishlaydi. Yolg'iz server (standalone) da ular mavjud emas.

JavaScript
rs.status().set
rs.status().members.length
db.hello().isWritablePrimary
Natija
rs0
1
true

Replika to'plami - bir xil ma'lumotning bir nechta nusxasi:

RolVazifasi
PrimaryBarcha yozuvlarni qabul qiladi
SecondaryPrimary dan nusxa oladi, o'qish uchun ishlatilishi mumkin
ArbiterMa'lumot saqlamaydi, faqat ovoz beradi

Primary ishdan chiqsa, qolganlar ovoz berib yangi primary tanlaydi. Bu odatda 10-12 soniya ichida sodir bo'ladi.

Nima uchun tranzaksiya replika talab qiladi

Tranzaksiya "yo hammasi, yo hech nima" kafolatini berishi uchun, o'zgarishlarni bekor qila olishi kerak. Buning uchun MongoDB oplog - operatsiyalar jurnalidan foydalanadi.

Oplog esa replika to'plamining mexanizmi: u secondary serverlarga nimani takrorlash kerakligini aytadi. Standalone serverda oplog yo'q.

Amalda bu cheklov emas: ishlab chiqarishda MongoDB deyarli har doim replika to'plami sifatida ishlatiladi - hatto bitta server bo'lsa ham. Sabab oddiy: replikasiz disk buzilsa, ma'lumot butunlay yo'qoladi.

Sinov uchun bir a'zoli replika to'plami yetarli - aynan shu darslikdagi kabi.

O'qish va yozish kafolatlari #

SozlamaMa'nosi
w: "majority"Yozuv nusxalarning ko'pchiligiga yetdi
j: trueDiskdagi jurnalga yozildi
readConcern: "local"Shu serverdagi holat (odatiy)
readConcern: "majority"Qaytmaydigan ma'lumot
readPreference: "secondary"O'qishni secondary dan olish
JavaScript
db.hisob.insertOne(
  { _id: 9, egasi: "Kamola", balans: 300000 },
  { writeConcern: { w: "majority", j: true } }
)
db.hisob.find({ _id: 9 }).readConcern("majority").toArray()
Natija
{ acknowledged: true, insertedId: 9 }
[ { _id: 9, egasi: 'Kamola', balans: 300000 } ]
readPreference: "secondary" - eskirgan ma'lumot

"O'qishni secondary ga o'tkazsak, primary yengillashadi" degan fikr mantiqiy ko'rinadi. Lekin uning narxi bor.

Secondary primary dan kechikib boradi - odatda bir necha millisekund, lekin yuk ostida bir necha soniya bo'lishi mumkin.

Natijada klassik xato:

VaqtAmalNatija
0 msFoydalanuvchi profilni saqlaydiPrimary ga yozildi
5 msSahifa qayta yuklanadiSecondary dan o'qiladi
5 msEski ma'lumot ko'rsatiladi"Saqlanmadi" degan taassurot

Shuning uchun:

  • Foydalanuvchi o'zi yozgan ma'lumotni o'qiyotgan bo'lsa - har doim primary dan.
  • Hisobot, tahlil, kechikishi muhim bo'lmagan o'qishlar - secondary dan.

Kechikishni o'lchash uchun rs.printSecondaryReplicationInfo() buyrug'i bor.

Amaliy topshiriq
  1. Bitta hujjatni $inc bilan yangilang.
  2. Bu amal nega tranzaksiyasiz ham atomar ekanini yozing.
  3. Sxema to'g'ri bo'lsa tranzaksiya nega kamdan-kam kerakligini tushuntiring.
  4. Ikki hisob orasida pul o'tkazuvchi tranzaksiya yozing.
  5. abortTransaction bilan bekor qiling va natijani tekshiring.
  6. withTransaction bilan xuddi shuni qayta yozing.
  7. Uning ikkita afzalligini sanang.
  8. Tranzaksiya ichida nima qilmaslik kerakligini yozing.
  9. rs.status() bilan replika holatini ko'ring.
  10. readPreference: "secondary" qanday muammo tug'dirishini tushuntiring.

Xulosa #

  • Bitta hujjatga tegadigan har qanday yangilash allaqachon atomar.
  • Bu kafolat hujjat ichidagi bir nechta maydonga ham tegishli.
  • Sxema to'g'ri loyihalangan bo'lsa, tranzaksiya kamdan-kam kerak bo'ladi.
  • Tranzaksiya sessiya ichida ochiladi: startSession -> startTransaction -> commit/abort.
  • withTransaction xatoda avtomatik bekor qiladi va vaqtinchalik xatoda qayta urinadi.
  • Tranzaksiyaning odatiy chegarasi 60 soniya - lekin uni ancha qisqaroq tuting.
  • Tranzaksiya ichida tashqi API, fayl yoki foydalanuvchi javobini kutmang.
  • Tranzaksiyalar faqat replika to'plamida ishlaydi - oplog talab qilinadi.
  • Muhim yozuvlar uchun w: "majority", muhim o'qishlar uchun readConcern: "majority".
  • readPreference: "secondary" eskirgan ma'lumot beradi - foydalanuvchi o'z yozuvini o'qiyotganda ishlatmang.

Keyingi bo'limda o'zgarishlar oqimi va gorizontal kengayishni ko'ramiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.