Django-da Race Condition Muammosi va select_for_update Yechimi

Muallif: Javohir Abdullayev · · Django

Django-da Race Condition Muammosi va select_for_update Yechimi

Kirish: Kutilmagan Balans Yo'qotishlari va Race Condition

Har bir backend dasturchi o'z faoliyatida kamida bir marta pinhona xatolar bilan to'qnash keladi. Shulardan eng xavflisi va aniqlash qiyin bo'lgani — Race Condition (poyga holati) dir. Kichik loyihalarda va lokal muhitda hamma narsa benuqson ishlayotgandek tuyuladi. Ammo saytga bir vaqtning o'zida yuzlab foydalanuvchilar kirib, bir xil resursga (masalan, hisob balansi yoki chipta xaridiga) murojaat qilganda jiddiy moliyaviy va mantiqiy xatolar yuzaga keladi.

Ushbu maqolada real elektron tijorat va to'lov tizimlarida uchraydigan klassik muammoni, uning sabablarini hamda Django ORM yordamida select_for_update() orqali qanday to'g'ri hal qilishni tahlil qilamiz.

Real Loyihadagi Muammo (Case Study)

Tasavvur qiling, siz foydalanuvchilarning ichki hamyon balansini boshqaradigan tizim yaratdingiz. Har bir foydalanuvchida 100 000 so'm mablag' bor. Foydalanuvchi bir vaqtning o'zida ikkita turli brauzer tabidan yoki Telegram bot orqali ketma-ket ikki marta 80 000 so'mlik mahsulot sotib olish tugmasini bosdi.

Odatda yangi o'rganayotgan dasturchilar kodni quyidagicha yozishadi:

def withdraw_money(user_id, amount):
    wallet = Wallet.objects.get(user_id=user_id)
    if wallet.balance >= amount:
        wallet.balance -= amount
        wallet.save()
        return True
    return False

Nega bu kod xavfli?

Agar ikkita so'rov serverga deyarli bir vaqtda (bir necha millisekund farq bilan) kelsa, ikkala jarayon ham bazadan balansni 100 000 so'm deb o'qiydi. Har ikkala so'rov 100 000 >= 80 000 shartini to'g'ri deb hisoblaydi va balansdan 80 000 ayirib bazaga 20 000 qilib saqlaydi. Natijada foydalanuvchi jami 160 000 so'mlik mahsulot oladi, ammo balansidan faqat 80 000 so'm yechiladi! Kompaniya 60 000 so'm zarar ko'radi.

Yechim: Ma'lumotlar Bazasidagi Tranzaksiyalar va select_for_update

Ushbu muammoni hal qilishning eng ishonchli usuli — ma'lumotlar bazasi darajasida qatorni bloklash (row-level locking). Django buni transaction.atomic() va select_for_update() metodi orqali juda qulay tarzda amalga oshirish imkonini beradi.

To'g'rilangan Kod Namunasi

from django.db import transaction

def safe_withdraw_money(user_id, amount):
    with transaction.atomic():
        wallet = Wallet.objects.select_for_update().get(user_id=user_id)
        
        if wallet.balance >= amount:
            wallet.balance -= amount
            wallet.save()
            return True
        else:
            return False

Bu Qanday Ishlaydi?

  • transaction.atomic(): Barcha operatsiyalarni bitta tranzaksiya ichiga oladi. Agar biron bir joyda xatolik yuz bersa, barcha o'zgarishlar bekor qilinadi (rollback).
  • select_for_update(): SQL darajasida SELECT ... FOR UPDATE so'rovini yuboradi. Bu PostgreSQL yoki MySQL bazasida aynan shu yozuv ustida qulf (lock) o'rnatadi.
  • Ikkinchi so'rov kelganda, birinchi tranzaksiya tugamaguncha (commit bo'lguncha) kutib turadi. Birinchi tranzaksiya yakunlangach, ikkinchi so'rov allaqachon yangilangan balansni (20 000 so'm) o'qiydi va balans yetarli emasligi sababli rad etiladi.

F Expressions Yordamida Qo'shimcha Himoya

Oddiy hisob-kitoblarda yana bir ajoyib vosita — Django F() ifodalaridir. U xotiradagi qiymatni emas, to'g'ridan-to'g'ri bazadagi qiymatni yangilaydi:

from django.db.models import F

Wallet.objects.filter(id=wallet.id, balance__gte=amount).update(balance=F('balance') - amount)

Ushbu yondashuv bitta SQL so'rovi bilan tekshirish va ayirishni amalga oshiradi hamda kichik operatsiyalar uchun juda tez ishlaydi.

Muhim Tavsiyalar va Ehtiyot Choralari

  1. Deadlock xavfi: Bir nechta obyektlarni bir vaqtda qulflaganda, doimo ularni bir xil tartibda (masalan, ID bo'yicha saralab) bloklang.
  2. nowait parametridan foydalanish: Agar navbatda uzoq kutishni xohlamasangiz, select_for_update(nowait=True) yozib, qulflangan bo'lsa darhol xatolik qaytarishingiz mumkin.
  3. Faqat kerakli joyda ishlating: Har bir oddiy o'qish so'rovida qulflash tizim samaradorligini pasaytiradi, shuning uchun faqat moliyaviy va muhim tranzaksiyalarda foydalaning.

Xulosa

Race condition xatolari loyiha foydalanuvchilar soni oshganda eng ko'p boshog'riq keltiradigan muammolardan biridir. Django ORM taqdim etadigan select_for_update() va tranzaksiyalarni to'g'ri qo'llash orqali siz loyihangizning moliyaviy xavfsizligini va ma'lumotlar aniqligini 100% kafolatlashingiz mumkin.

Teglar: #Django #Backend #PostgreSQL #Ma'lumotlar bazasi