Keling kichik app yasab ko'ramiz. Aytaylik user registratsiya qilmoqchimiz, stack uchun Express.js + PostgreSQL:
const express = require("express");
const pg = require("pg").Pool;
const app = express();
app.use(express.json());
const dbClient = new pg({ host: "localhost", user: "root", password: "1234", database: "test" });
async function checkEmailExist(email) {
const users = await dbClient.query(
`SELECT id
FROM users
WHERE email = $1
LIMIT 1`,
[email]
);
return users.rowCount > 0;
}
async function registerUser(email) {
return await dbClient.query(
`INSERT INTO users (email)
VALUES ($1)
RETURNING id`,
[email]
);
}
app.post("/register", async (req, res) => {
// get email from request body
const { email } = req.body;
// check if email is not taken
const emailExist = await checkEmailExist(email);
if (emailExist) return res.status(400).json({ error: "Email already exist" });
// register new user
await registerUser(email);
return res.status(201).json({ data: "User registered successfully!" });
});
async function main() {
try {
await dbClient.connect();
app.listen(3000, () => console.log("OLD server is running on port 3000"));
} catch (error) {
console.error(error);
}
}
main();
Boshlanishiga hammasi yaxshi. App lauch bo'ldi va minglab foydalanuvchilar ro'yhatdan o'tdi. Keyin database'ga bir qarab ko'rsangiz ba'zi email'lar duplicate bo'lgan. WTH!
Shu joyida concurrency esga tushadi va bugni reproduce qilishga urinib ko'rasiz(pastdagi script). Ya'ni faraz qilaylik, checkEmailExist
funksiyasi ishlashi uchun biror N vaqt oladi va shu vaqt oralig'idagi boshqa /register endpointga kelgan requestlar ham execute bo'ladi. Concurrency! Va qarabsizki sizda duplikatlar bor:
const axios = require("axios");
async function register() {
return await axios.post("http://localhost:3000/register", {
email: "johndoe@gmail.com",
});
}
(async () => {
for (let i = 0; i < 5; i++) {
register()
.then((res) => console.log(res.data))
.catch((err) => console.error(err.response?.data));
}
})(); // Surprise! We have 5 johndoe@gmail.com's in db
Qanday yechim qilamiz? Transactions! Bizga wrapper funksiya kerak bo'ladi:
async function $transaction(f) {
try {
// start db transaction
await dbClient.query("BEGIN");
await dbClient.query("SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
// execute main function
const result = await f();
// commit db transaction
await dbClient.query("COMMIT");
return result;
} catch (error) {
await dbClient.query("ROLLBACK");
throw error;
}
}
checkEmailExist
va registerUser
funksiyalari o'zgarmaydi, rout handlerni azgina o'zgartiramiz:
app.post("/register", async (req, res) => {
// get email from request body
const { email } = req.body;
try {
await $transaction(async () => {
// check if email is not taken
const emailExist = await checkEmailExist(email);
if (emailExist) {
throw "Email already exist";
}
// register new user
await registerUser(email);
});
return res.json({ data: "User registered successfully!" });
} catch (error) {
console.error(error);
return res.json({ error });
}
});
Va qarabsizki muammo hal!
Qanday test qilaman? checkEmailExist
funksiyani sekin ishlashini simulate qilamiz, masalan quyidagini qo'shib qo'yamiz:
// simulate delay
await new Promise((resolve) => setTimeout(resolve, 1000));
Top comments (0)