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.
Ushbu bo‘lim mundarijasi
MongoDB da har bir hujjat yangilanishi allaqachon atomar. Tranzaksiya esa boshqa savolga javob beradi: bir nechta hujjatni birga o'zgartirish kerak bo'lsa-chi?
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 #
db.hisob.updateOne({ _id: 1 }, { $inc: { balans: -50000 } })
db.hisob.findOne({ _id: 1 })
{
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:
db.hisob.updateOne(
{ _id: 2 },
{ $inc: { balans: 30000 }, $set: { oxirgi_amal: "kirim" } }
)
db.hisob.findOne({ _id: 2 })
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
{ _id: 2, egasi: 'Malika', balans: 150000, oxirgi_amal: 'kirim' }
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:
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 })
{
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 #
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 })
{
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.
withTransaction - xavfsizroq yo'l #
Qo'lda commit va abort yozish o'rniga, yordamchi
funksiya ishlatish afzal:
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()
{ _id: 1, egasi: 'Husanboy', balans: 475000 }
2
withTransaction ikkita ishni o'zi bajaradi:
| Vazifa | Izoh |
|---|---|
Xato bo'lsa abort | try/catch yozish shart emas |
| Vaqtinchalik xatoda qayta urinish | Tarmoq uzilishi, reja almashuvi |
MongoDB da tranzaksiyaning 60 soniyalik odatiy chegarasi bor. Undan uzunlari majburan to'xtatiladi.
Lekin muammo chegaraga yetgunicha boshlanadi:
| Tranzaksiya davomiyligi | Oqibat |
|---|---|
| 10 ms | Sezilmaydi |
| 1 soniya | Qulflar ushlanib turadi |
| 10 soniya | Boshqa yozuvlar navbatga tushadi |
| 60 soniya | Majburan 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.
rs.status().set
rs.status().members.length
db.hello().isWritablePrimary
rs0
1
true
Replika to'plami - bir xil ma'lumotning bir nechta nusxasi:
| Rol | Vazifasi |
|---|---|
| Primary | Barcha yozuvlarni qabul qiladi |
| Secondary | Primary dan nusxa oladi, o'qish uchun ishlatilishi mumkin |
| Arbiter | Ma'lumot saqlamaydi, faqat ovoz beradi |
Primary ishdan chiqsa, qolganlar ovoz berib yangi primary tanlaydi. Bu odatda 10-12 soniya ichida sodir bo'ladi.
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 #
| Sozlama | Ma'nosi |
|---|---|
w: "majority" | Yozuv nusxalarning ko'pchiligiga yetdi |
j: true | Diskdagi jurnalga yozildi |
readConcern: "local" | Shu serverdagi holat (odatiy) |
readConcern: "majority" | Qaytmaydigan ma'lumot |
readPreference: "secondary" | O'qishni secondary dan olish |
db.hisob.insertOne(
{ _id: 9, egasi: "Kamola", balans: 300000 },
{ writeConcern: { w: "majority", j: true } }
)
db.hisob.find({ _id: 9 }).readConcern("majority").toArray()
{ 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:
| Vaqt | Amal | Natija |
|---|---|---|
| 0 ms | Foydalanuvchi profilni saqlaydi | Primary ga yozildi |
| 5 ms | Sahifa qayta yuklanadi | Secondary dan o'qiladi |
| 5 ms | Eski 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.
- Bitta hujjatni
$incbilan yangilang. - Bu amal nega tranzaksiyasiz ham atomar ekanini yozing.
- Sxema to'g'ri bo'lsa tranzaksiya nega kamdan-kam kerakligini tushuntiring.
- Ikki hisob orasida pul o'tkazuvchi tranzaksiya yozing.
abortTransactionbilan bekor qiling va natijani tekshiring.withTransactionbilan xuddi shuni qayta yozing.- Uning ikkita afzalligini sanang.
- Tranzaksiya ichida nima qilmaslik kerakligini yozing.
rs.status()bilan replika holatini ko'ring.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. withTransactionxatoda 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 uchunreadConcern: "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.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.