Booking.com, Airbnb, Calendly va 42Mentors bularni barchasi bir toifadagi ishni bajaradi ya'ni siz uchun
- Uchrashuvlarni
- Ovqat, turar joy va boshqa turdagi narsalarni
band qila oladi. Ularda eng kam va shu o'rinda uchrashi mumkin bo'lgan eng yomon muammo bu double booking problem xisoblanadi.
Double Booking Problem
Tasavvur qiling siz yaratgan tizimga ikki inson bir vaqtda kirishdi. Ikkisiga ham aynan 18-Aprel 20:00 vaqti yoqdi va buyurtma qilish
tugmachasiga bosdi. Agar siz bunga qarshi chora ko'rmagan bo'lsangiz tizim ikkisiga ham muvaffaqiyatli buyurtma qildingiz deya javob qaytaradi. Ammo bu juda yomon 😢
Bu muammoni hal qilish uchun baxtimizga PostgreSQL, MySQL kabi katta ma'lumotlar omborini yaratgan va contribute qilgan insonlar yechim berishgan.
SELECT ... FOR UPDATE
SELECT id, name, school_number FROM schools;
Yuqorida keltirlgan selectni hammamiz bilamiz menimcha, ammo FOR UPDATE qo'shimchasi haqida katta ehtimol bilan eshitmaganmiz (chunki ishimiz tushmaganligidan bo'lsa kerak albatta).
Postgres o'z dokumentatsiyasida SELECT
haqida juda ko'p zo'r ma'lumotlar bergan. SELECT ... FOR UPDATE haqida esa The Locking Clause bo'limida yoritib berishgan.
SELECT ... FOR UPDATE bu ham biror qator tanlash uchun ishlatiladi ammo oddiy tanlabgina qo'ymay o'sha qatorni qulflab ham qo'yadi (locks the row) ya'ni concurrency problem oldini olish uchun. Ishlash jaryoni 3 bosqichda amalga oshadi:
- Row locking -- siz tanlagan qatorni tanlaydi va uni ustida operatsiyalar bajarlimasligi uchun uni yopadi.
- Transaction scope -- boshlangan transaction rollback yoki commit qilinmagunicha yopiq saqlab turadi.
- Concurrent Modification -- manashu transaction tugamagunicha boshqa tranzaksiyalarni bloklaydi.
Amaliyot
Amaliyot uchun JavaScript va Postgresql dan foydlansak bo'ladi. Amaliyotdan avval ma'lumotlar omboriga ushbu queryni ma'lumotlar omboringizga kiritib, ozgina ma'lumotlar qo'shib oling.
CREATE TABLE slots (
id SERIAL PRIMARY KEY,
name VARCHAR(150),
isbooked BOOLEAN
)
PostgreSQL serverni ishga tushuring va Epxress JS da slots nomli route yarating. Ushbu route ni vazifasi barcha mavjud slotlarni qaytarishdir!
app.get('/slots', async (req, res) => {
// slots tabledan hamma columnlarni so'raymiz
const result = await pool.query('SELECT id, name, isbooked FROM slots')
// ma'lumotlar ombori qaytargan rowlarni jo'natamiz
res.send(result.rows)
})
Endi navbat band qilish qismiga, id va name bilan band qilish uchun query yozamiz:
app.put('/:id/:name', async (req, res) => {
try {
const id = req.params.id
const name = req.params.name
const conn = await pool.connect()
// Biz olmoqchi bo'lgan ma'lumot joylashgan qator so'raymiz (querying)
const sql =
'SELECT id, name, isbooked FROM slots where id = $1 AND isbooked = 0'
const result = await conn.query(sql, [id])
// Agar sql query hech nima qaytarmasa u banq qilingan bo'ladi
// shuning uchun yaxshiroq error message qaytaramiz
if (result.rowCount === 0) {
res.send({ error: 'Slot allaqachon band qilingan!' })
return
}
// Agar sql query row qaytarsa, o'sha rowni update qilamiz, tamam
const sqlU = 'UPDATE slots SET isbooked = 1, name = $2 WHERE id = $1'
const updateResult = await conn.query(sqlU, [id, name])
conn.release()
res.send(updateResult)
} catch (ex) {
console.log(ex)
res.send(500)
}
})
Demak hammasi tayyor! Dastur zo'r ishlaydi deb turgan vaqtingizda bir muammo bor biz hali ham FOR UPDATE
ishlatmadik. Agar ikkita browser oyansini ochib, debugger ishlatib ularni bir vaqtda book qilsangiz ikki insonga ham bir xil muvaffaqiyatli degan javob qaytaradi.
Ammo bu muammoni TRANSACTION va FOR UPDATE orqali bloklashimiz va yechishimiz mumkin!
app.put('/:id/:name', async (req, res) => {
try {
const id = req.params.id
const name = req.params.name
const conn = await pool.connect()
// Transaction boshlab, muhitni isolate qilamiz
await conn.query('BEGIN')
// Biz olmoqchi bo'lgan ma'lumot joylashgan qator so'raymiz (querying)
const sql =
'SELECT id, name, isbooked FROM slots where id = $1 AND isbooked = 0 FOR UPDATE'
const result = await conn.query(sql, [id])
// Agar sql query hech nima qaytarmasa u banq qilingan bo'ladi
// shuning uchun yaxshiroq error message qaytaramiz
if (result.rowCount === 0) {
res.send({ error: 'Slot allaqachon band qilingan!' })
return
}
// Agar sql query row qaytarsa, o'sha rowni update qilamiz, tamam
const sqlU = 'UPDATE slots SET isbooked = 1, name = $2 WHERE id = $1'
const updateResult = await conn.query(sqlU, [id, name])
// Connectionni uzishdan oldin ma'lumotlarni COMMIT qilishni unutmang
await conn.query('COMMIT')
// Connectionni uzsak bo'ladi
conn.release()
res.send(updateResult)
} catch (ex) {
console.log(ex)
res.send(500)
}
})
Mana ko'rib turibsizki muammoni ozgina ish bilan hal qila oldik.
Albatta buni ham o'ziga yarasha yomon tomonlari mavjud, ular haqida ko'proq dokumentatsiya o'qish va tajriba qilib ko'rishni tavsiya qilaman.
Postni foydali topgan bo'lsangiz, o'zingiz ham o'rganing va tanishlaringizga ham o'rgating (share qiling)!