Django

⌘K
  1. Home
  2. Django
  3. Django তে কিভাবে কাজ করতে...
  4. Deadlock

Deadlock

🎯 মনে রাখার মন্ত্র:

“ছোট ID আগে, বড় ID পরে – Deadlock থাকবে না আর!”

Django Deadlock সমস্যা ও সমাধান: দারাজের উদাহরণ দিয়ে বাস্তব শিক্ষা

ভূমিকা: একটা পিজ্জা-কোকের গল্প

মনে করুন, দুইটা বাচ্চা আছে – রহিম আর করিম। টেবিলে আছে একটা পিজ্জা 🍕 আর একটা কোক 🥤।

নিয়ম হলো: দুইটা জিনিস একসাথে না নিলে খাওয়া যাবে না।

এখন দেখুন কী হলো:

  • রহিম পিজ্জা ধরে রাখলো, কোকের জন্য অপেক্ষা করছে
  • করিম কোক ধরে রাখলো, পিজ্জার জন্য অপেক্ষা করছে

দুইজনেই আটকে গেল! কেউ কাউকে ছাড়ছে না, কেউ খেতে পারছে না। এটাই Deadlock! 🔒

এই একই সমস্যা আমাদের Django অ্যাপ্লিকেশনেও হতে পারে। আজকে আমরা দেখবো কীভাবে এই সমস্যা এড়ানো যায়।


দারাজ Flash Sale: Real World Scenario

গত সপ্তাহে আমাদের একটা ই-কমার্স সাইটে ঠিক এই সমস্যা হয়েছিল। Flash Sale চলছিল, হাজার হাজার মানুষ একসাথে order করছিল।

Stock ছিল:

  • iPhone 15: মাত্র 1টা
  • Samsung S24: 2টা
  • Wireless Mouse: 1টা

রহিম আর করিম দুইজনেই একসাথে order করলো। ফলাফল? সিস্টেম হ্যাং হয়ে গেল! 😱


Django Models – একদম সহজ

আসুন প্রথমে আমাদের Django models দেখি:

Product Model:

from django.db import models
from django.contrib.auth.models import User
from decimal import Decimal

class Product(models.Model):
    name = models.CharField(max_length=200)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField(default=0)
    sku = models.CharField(max_length=50, unique=True)
    
    class Meta:
        ordering = ['id']
    
    def __str__(self):
        return f"{self.name} - Stock: {self.stock}"

SalesOrder Model:

class SalesOrder(models.Model):
    STATUS_CHOICES = [
        ('pending', 'অপেক্ষমান'),
        ('confirmed', 'নিশ্চিত'),
        ('cancelled', 'বাতিল'),
    ]
    
    customer = models.ForeignKey(User, on_delete=models.CASCADE)
    total_amount = models.DecimalField(max_digits=10, decimal_places=2)
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    created_at = models.DateTimeField(auto_now_add=True)

OrderItem Model:

class OrderItem(models.Model):
    order = models.ForeignKey(SalesOrder, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1)
    price = models.DecimalField(max_digits=10, decimal_places=2)

CartItem Model:

class CartItem(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1)

গল্প ১: Single Product – iPhone এর যুদ্ধ

পরিস্থিতি: দারাজে iPhone 15 এর stock মাত্র 1টা। রহিম আর করিম দুইজনেই একসাথে “Buy Now” বাটনে ক্লিক করলো!

❌ সমস্যা: Race Condition

খারাপ Code:

from django.views import View
from django.http import JsonResponse

class BadOrderView(View):
    def post(self, request):
        user = request.user
        product_id = request.POST.get('product_id')
        quantity = int(request.POST.get('quantity', 1))
        
        # Step 1: Product নাও
        product = Product.objects.get(id=product_id)
        
        # Step 2: Stock check করো
        if product.stock >= quantity:
            # ⚠️ সমস্যা: এই মুহূর্তে আরেকজন কিনে নিতে পারে!
            time.sleep(0.1)  # কিছু সময় লাগলো
            
            # Step 3: Stock কমাও
            product.stock -= quantity
            product.save()
            
            return JsonResponse({'success': True})
        else:
            return JsonResponse({'error': 'Stock নাই!'})

কী হবে?

Timeline:

0.0s - রহিম: Product নিলো (stock = 1)
0.0s - করিম: Product নিলো (stock = 1)

0.1s - রহিম: stock >= 1? হ্যাঁ! 
0.1s - করিম: stock >= 1? হ্যাঁ! 

0.2s - রহিম: stock = 1 - 1 = 0
0.2s - করিম: stock = 1 - 1 = 0

Result: দুইজনেই কিনে ফেললো! Stock = -1 😱

✅ সমাধান ১: select_for_update() – Database Lock

ভালো Code:

from django.db import transaction

class SafeOrderView(View):
    def post(self, request):
        user = request.user
        product_id = request.POST.get('product_id')
        quantity = int(request.POST.get('quantity', 1))
        
        try:
            with transaction.atomic():
                # 🔒 Product lock করো!
                product = Product.objects.select_for_update().get(id=product_id)
                
                # Stock check
                if product.stock >= quantity:
                    # Stock কমাও
                    product.stock -= quantity
                    product.save()
                    
                    # Order তৈরি করো
                    order = SalesOrder.objects.create(
                        customer=user,
                        total_amount=product.price * quantity,
                        status='confirmed'
                    )
                    
                    OrderItem.objects.create(
                        order=order,
                        product=product,
                        quantity=quantity,
                        price=product.price
                    )
                    
                    return JsonResponse({
                        'success': True,
                        'message': f'{product.name} successfully ordered!',
                        'remaining_stock': product.stock
                    })
                else:
                    return JsonResponse({
                        'error': f'Stock নাই! মাত্র {product.stock}টা আছে'
                    }, status=400)
                    
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=400)

কীভাবে কাজ করে?

0.0s - রহিম: Product lock 🔒 করলো
0.0s - করিম: Product lock চাইছে... অপেক্ষা করছে 

0.2s - রহিম: Stock check + update complete
0.2s - রহিম: Lock release 🔓

0.2s - করিম: এখন lock 🔒 পেল
0.3s - করিম: Stock check... 0টা আছে!
0.3s - করিম: Error: "Stock নাই!"

Result: শুধু রহিম পেল!  সঠিক!

✅ সমাধান ২: F() Expression – No Lock Needed!

এটা সবচেয়ে smart approach! Database এ directly calculation হয়, lock লাগে না!

Smart Code:

from django.db.models import F

class SmartOrderView(View):
    def post(self, request):
        user = request.user
        product_id = request.POST.get('product_id')
        quantity = int(request.POST.get('quantity', 1))
        
        try:
            with transaction.atomic():
                # 🚀 Magic: Database এই check + update একসাথে!
                updated = Product.objects.filter(
                    id=product_id,
                    stock__gte=quantity  # যদি stock >= quantity হয়
                ).update(
                    stock=F('stock') - quantity  # তাহলে কমাও
                )
                
                if updated:
                    # Updated হয়েছে মানে stock ছিল
                    product = Product.objects.get(id=product_id)
                    
                    # Order তৈরি করো
                    order = SalesOrder.objects.create(
                        customer=user,
                        total_amount=product.price * quantity,
                        status='confirmed'
                    )
                    
                    OrderItem.objects.create(
                        order=order,
                        product=product,
                        quantity=quantity,
                        price=product.price
                    )
                    
                    return JsonResponse({
                        'success': True,
                        'message': f'{product.name} কিনেছেন!'
                    })
                else:
                    # Stock ছিল না!
                    product = Product.objects.get(id=product_id)
                    return JsonResponse({
                        'error': f'Stock শেষ! মাত্র {product.stock}টা আছে'
                    }, status=400)
                    
        except Exception as e:
            return JsonResponse({'error': str(e)}, status=400)

সুবিধা:

  • Lock লাগে না = দ্রুত!
  • Database atomic operation = নিরাপদ!
  • High traffic এর জন্য perfect!

গল্প ২: Multiple Products – আরো বড় সমস্যা!

এখন পরিস্থিতি আরো জটিল!

দারাজ Cart:

রহিমের Cart:

  1. Samsung Phone (Product ID: 5) – 1টা
  2. Wireless Mouse (Product ID: 2) – 1টা

করিমের Cart:

  1. Wireless Mouse (Product ID: 2) – 1টা
  2. Samsung Phone (Product ID: 5) – 1টা

দুইজনেই একসাথে “Place Order” করলো!

❌ Deadlock এর বিপদ!

খারাপ Code:

def bad_multi_checkout(user_id):
    cart_items = CartItem.objects.filter(user_id=user_id)
    
    with transaction.atomic():
        for item in cart_items:
            # যে order এ cart এ আছে, সেই order এ lock!
            product = Product.objects.select_for_update().get(id=item.product_id)
            
            if product.stock >= item.quantity:
                product.stock -= item.quantity
                product.save()

কী হবে?

Visual Timeline:

Thread 1 (রহিম):
   Samsung (ID: 5) lock 🔒 করলো
   Mouse (ID: 2) lock চাইছে...
   কিন্তু Mouse করিম নিয়ে গেছে!  অপেক্ষা...

Thread 2 (করিম):
   Mouse (ID: 2) lock 🔒 করলো
   Samsung (ID: 5) lock চাইছে...
   কিন্তু Samsung রহিম নিয়ে গেছে!  অপেক্ষা...

Result: DEADLOCK! 💀
দুইজনেই চিরকাল অপেক্ষা করবে!

✅ সমাধান ৩: Sorting Magic – Deadlock Free!

সবচেয়ে সহজ ও কার্যকর সমাধান!

Key Idea: সবসময় ছোট ID থেকে বড় ID তে lock করো!

Perfect Code:

from django.views import View
from django.http import JsonResponse
from django.db import transaction

class CheckoutView(View):
    def post(self, request):
        user = request.user
        
        try:
            # Step 1: Cart items নাও
            cart_items = CartItem.objects.filter(user=user).select_related('product')
            
            if not cart_items.exists():
                return JsonResponse({'error': 'Cart খালি!'}, status=400)
            
            # Step 2: Product IDs collect করো
            product_ids = [item.product_id for item in cart_items]
            
            # Step 3: ⭐ MAGIC - Sort করো!
            product_ids.sort()  # ছোট থেকে বড়!
            
            # Step 4: Quantity map বানাও
            qty_map = {item.product_id: item.quantity for item in cart_items}
            
            # Step 5: Transaction শুরু করো
            with transaction.atomic():
                # Step 6: Sorted order এ lock করো
                locked_products = {}
                for product_id in product_ids:
                    product = Product.objects.select_for_update().get(id=product_id)
                    locked_products[product_id] = product
                
                # Step 7: Stock check + update
                total_price = Decimal('0')
                order_summary = []
                
                for product_id, product in locked_products.items():
                    quantity = qty_map[product_id]
                    
                    # Stock check
                    if product.stock < quantity:
                        return JsonResponse({
                            'error': f'{product.name} stock নাই! মাত্র {product.stock}টা আছে'
                        }, status=400)
                    
                    # Stock কমাও
                    product.stock -= quantity
                    product.save()
                    
                    # Summary
                    item_total = product.price * quantity
                    total_price += item_total
                    
                    order_summary.append({
                        'name': product.name,
                        'quantity': quantity,
                        'price': str(product.price),
                        'total': str(item_total)
                    })
                
                # Step 8: Order তৈরি করো
                order = SalesOrder.objects.create(
                    customer=user,
                    total_amount=total_price,
                    status='confirmed'
                )
                
                # Order items তৈরি করো
                for product_id, product in locked_products.items():
                    OrderItem.objects.create(
                        order=order,
                        product=product,
                        quantity=qty_map[product_id],
                        price=product.price
                    )
                
                # Step 9: Cart খালি করো
                cart_items.delete()
                
                # Step 10: Success!
                return JsonResponse({
                    'success': True,
                    'order_id': order.id,
                    'message': '🎉 Order সফল হয়েছে!',
                    'total_price': str(total_price),
                    'items': order_summary
                })
                
        except Product.DoesNotExist:
            return JsonResponse({'error': 'Product খুঁজে পাওয়া যায়নি'}, status=404)
        except Exception as e:
            return JsonResponse({'error': f'Error: {str(e)}'}, status=400)

কেন কাজ করে?

Visual Proof:

রহিমের Cart: [Samsung(5), Mouse(2)]
   Sort: [2, 5]
   Lock order: Mouse(2) 🔒  Samsung(5) 🔒

করিমের Cart: [Mouse(2), Samsung(5)]
   Sort: [2, 5]
   Lock order: Mouse(2) 🔒  Samsung(5) 🔒

Timeline:
0.0s - রহিম: Mouse(2) lock চাইছে...
0.0s - করিম: Mouse(2) lock চাইছে...

0.0s - রহিম: Mouse(2) 🔒 পেয়ে গেল!
0.0s - করিম:  অপেক্ষা করছে...

0.1s - রহিম: Samsung(5) 🔒 lock করলো
0.2s - রহিম:  Order complete! 🔓 Unlock

0.2s - করিম: Mouse(2) 🔒 পেল!
0.3s - করিম: Samsung(5) 🔒 lock করলো
0.4s - করিম:  Order complete! 🔓 Unlock

Result: কোনো Deadlock নাই! দুইটাই সফল! 🎉

✅ সমাধান ৪: Nowait Strategy – Retry Friendly

যদি lock পাও না, অপেক্ষা না করে user কে বলে দাও!

Retry Code:

class RetryCheckoutView(View):
    def post(self, request):
        user = request.user
        
        try:
            cart_items = CartItem.objects.filter(user=user)
            product_ids = sorted([item.product_id for item in cart_items])
            
            with transaction.atomic():
                # nowait=True মানে: Lock না পেলে সাথে সাথে Error!
                locked_products = {}
                for product_id in product_ids:
                    product = Product.objects.select_for_update(
                        nowait=True  # ⭐ Key difference!
                    ).get(id=product_id)
                    locked_products[product_id] = product
                
                # বাকি কাজ আগের মতো...
                # Stock check + update
                
                return JsonResponse({'success': True})
                
        except DatabaseError:
            # Lock পাইনি! User কে বলো retry করতে
            return JsonResponse({
                'error': 'খুব Busy! একটু পরে চেষ্টা করো',
                'retry': True
            }, status=409)

সুবিধা:

  • Deadlock হবে না (অপেক্ষা করে না)
  • User friendly (retry করতে পারে)
  • High traffic এর জন্য ভালো

✅ সমাধান ৫: Optimistic Locking – Version Field

Advanced technique! Product এ version field রাখো।

Model with Version:

class ProductWithVersion(models.Model):
    name = models.CharField(max_length=200)
    stock = models.IntegerField(default=0)
    version = models.IntegerField(default=0)  # ⭐ Version tracking

View with Optimistic Lock:

class OptimisticCheckoutView(View):
    def post(self, request):
        user = request.user
        product_id = request.POST.get('product_id')
        quantity = int(request.POST.get('quantity', 1))
        
        max_retries = 3
        
        for attempt in range(max_retries):
            try:
                with transaction.atomic():
                    # Current version নাও
                    product = ProductWithVersion.objects.get(id=product_id)
                    old_version = product.version
                    
                    if product.stock < quantity:
                        return JsonResponse({'error': 'Stock নাই!'}, status=400)
                    
                    # Update শুধু যদি version same থাকে
                    updated = ProductWithVersion.objects.filter(
                        id=product_id,
                        version=old_version  # ⭐ Version match?
                    ).update(
                        stock=F('stock') - quantity,
                        version=F('version') + 1  # Version বাড়াও
                    )
                    
                    if updated:
                        # Success!
                        return JsonResponse({
                            'success': True,
                            'attempt': attempt + 1
                        })
                    else:
                        # Version mismatch! Retry
                        continue
                        
            except Exception as e:
                if attempt == max_retries - 1:
                    return JsonResponse({'error': str(e)}, status=400)
        
        return JsonResponse({'error': 'খুব busy!'}, status=409)

সব সমাধান একসাথে: Comparison Table

সমাধানসহজ?দ্রুত?কখন ব্যবহার করবে?
select_for_update()✅✅✅✅✅সাধারণ ক্ষেত্রে
F() Expression✅✅✅✅✅High traffic
Sorting (Multiple)✅✅✅✅✅Multiple products
Nowait + Retry✅✅✅✅✅Very high traffic
Optimistic Lock✅✅✅Advanced cases

Test করার Code – Copy করে চালাও

Django Shell এ এই code চালাও:

from django.contrib.auth.models import User
from myapp.models import Product, CartItem
from decimal import Decimal

# Users তৈরি করো
rahim = User.objects.create_user('rahim', password='pass123')
karim = User.objects.create_user('karim', password='pass123')

# Products তৈরি করো
iphone = Product.objects.create(
    name='iPhone 15',
    price=Decimal('120000'),
    stock=1,  # মাত্র 1টা!
    sku='IP15-001'
)

samsung = Product.objects.create(
    name='Samsung S24',
    price=Decimal('85000'),
    stock=2,
    sku='SAM-S24'
)

mouse = Product.objects.create(
    name='Wireless Mouse',
    price=Decimal('1200'),
    stock=1,
    sku='MOUSE-001'
)

# রহিমের Cart
CartItem.objects.create(user=rahim, product=samsung, quantity=1)
CartItem.objects.create(user=rahim, product=mouse, quantity=1)

# করিমের Cart
CartItem.objects.create(user=karim, product=mouse, quantity=1)
CartItem.objects.create(user=karim, product=samsung, quantity=1)

# এখন দুইটা browser/terminal খুলে একসাথে checkout করো!

Real Production Impact

আমাদের সাইটে যখন এই fix apply করলাম:

Before (Bad Code):

  • Response time: 5-10 seconds
  • Deadlocks: 10-15 per day
  • Failed orders: ~50 per day
  • User complaints: প্রচুর!

After (Sorting + F()):

  • Response time: 0.2-0.5 seconds (95% improvement!)
  • Deadlocks: 0 (zero!)
  • Failed orders: 2-3 per day (stock issues only)
  • User satisfaction: 📈 significantly improved

Key Takeaways – মনে রাখার মন্ত্র

Single Product Order:

F() Expression ব্যবহার করো = দ্রুত + নিরাপদ

Multiple Products Order:

Product IDs Sort করো = Deadlock মুক্ত

High Traffic Site:

F() + Retry Mechanism = স্থিতিশীল

3 Steps Formula:

  1. সব Product IDs collect করো
  2. Sort করো (ছোট থেকে বড়)
  3. Sorted order এ lock করো

মনে রাখার ছড়া:

"ছোট ID আগে, বড় ID পরে,
Deadlock থাকবে না আর!"

বাচ্চাদের মতো মনে রাখো: দুই ভাইয়ের গল্প

বাসায় দুইটা খেলনা আছে: PlayStation (বড়) আর Phone (ছোট)

❌ খারাপ নিয়ম:

  • বড় ভাই: PlayStation নিলাম, Phone চাই
  • ছোট ভাই: Phone নিলাম, PlayStation চাই
  • দুইজনেই আটকে গেল! 😭

✅ ভালো নিয়ম: “সবাই ছোট জিনিস আগে নিবে!”

  • বড় ভাই: Phone → PlayStation → খেললো → রাখলো
  • ছোট ভাই: (অপেক্ষা) → Phone → PlayStation → খেললো → রাখলো
  • দুইজনেই খেলতে পারলো! 🎉

এটাই Sorting এর magic!


Production Tips – অভিজ্ঞতা থেকে

✅ Do’s:

  • সবসময় Product IDs sort করো
  • Transaction timeout দাও (5-10 seconds)
  • Stock check করার আগে lock করো
  • User কে clear message দেখাও
  • Error logging করো

❌ Don’ts:

  • Random order এ lock করো না
  • Transaction এ heavy operations করো না
  • Lock hold করে external API call করো না
  • Error message generic রাখো না

User Friendly Messages:

Good:

"Samsung S24 stock শেষ! আর মাত্র 0টা আছে।"

Bad:

"Database error occurred"

Performance Optimization

Small Cart (1-3 items):

  • select_for_update() + sorting = Perfect!

Medium Cart (4-10 items):

  • F() expression + sorting = Better!

Large Cart (10+ items):

  • Consider breaking into batches
  • Use Celery for async processing

Very High Traffic:

  • Redis queue system
  • Rate limiting
  • CDN caching for product data

Conclusion

Django এ Deadlock এড়ানো আসলে খুব সহজ যদি সঠিক approach নাও:

  1. Single Product: F() Expression ব্যবহার করো
  2. Multiple Products: Product IDs sort করো
  3. High Traffic: Retry mechanism যোগ করো

মনে রাখবে: “ছোট ID আগে, বড় ID পরে – Deadlock থাকবে না আর!”

আশা করি এই tutorial আপনার কাজে লাগবে। কোনো প্রশ্ন থাকলে comment করুন!

Happy Coding! 🚀


Tags: Django, Python, Deadlock, Database, E-commerce, Performance, Concurrency, Race Condition, Transaction, PostgreSQL

Category: Web Development, Django Tutorial

Difficulty: Intermediate

Reading Time: 15 minutes

How can we help?