Optimistic vs Pessimistic Locking কী?

মাল্টি-থ্রেডেড বা মাল্টি-ইউজার অ্যাপ্লিকেশনে যখন একই ডেটা একাধিক ব্যবহারকারী একই সময়ে পরিবর্তন করার চেষ্টা করে, তখন Concurrency Conflict বা ডেটা অসঙ্গতি তৈরি হতে পারে। এই সমস্যা সমাধানের জন্য দুটি প্রধান কৌশল ব্যবহার করা হয়:

  • Optimistic Locking — আশাবাদী পদ্ধতি: Conflict হবে না ধরে নেওয়া হয়
  • Pessimistic Locking — হতাশাবাদী পদ্ধতি: Conflict হবেই ধরে নেওয়া হয়

Optimistic Locking

ধারণা

Optimistic Locking-এ ধরে নেওয়া হয় যে Conflict খুব কমই হয়। তাই ডেটা পড়ার সময় কোনো Lock দেওয়া হয় না। তবে ডেটা সেভ করার সময় যাচাই করা হয় — এর মধ্যে অন্য কেউ ডেটা পরিবর্তন করেছে কিনা। যদি করে থাকে, তাহলে OptimisticLockException ছুড়ে দেওয়া হয়।

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

  1. ডেটা পড়া হয় এবং একটি Version Number বা Timestamp সংরক্ষণ করা হয়
  2. ডেটা আপডেট করার সময় চেক করা হয় — Version Number আগের মতো আছে কিনা
  3. যদি Version Number মিলে যায় → Update সফল, Version বাড়িয়ে দেওয়া হয়
  4. যদি Version Number না মিলে → Exception throw করা হয়

Spring Boot-এ Optimistic Locking Implementation

import jakarta.persistence.*;

@Entity
@Table(name = "products")
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int stock;
    private double price;

    // @Version অ্যানোটেশন Optimistic Locking সক্রিয় করে
    @Version
    private Long version;

    // Constructors
    public Product() {}

    public Product(String name, int stock, double price) {
        this.name = name;
        this.stock = stock;
        this.price = price;
    }

    // Getters and Setters
    public Long getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getStock() { return stock; }
    public void setStock(int stock) { this.stock = stock; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
    public Long getVersion() { return version; }
}

Repository

import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

Service — Optimistic Locking Exception হ্যান্ডেল করা

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ProductService {

    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public Product updateStock(Long productId, int newStock) {
        try {
            Product product = productRepository.findById(productId)
                .orElseThrow(() -> new RuntimeException("Product not found"));

            product.setStock(newStock);
            return productRepository.save(product);

        } catch (ObjectOptimisticLockingFailureException e) {
            throw new RuntimeException(
                "অন্য কেউ এই ডেটা পরিবর্তন করেছে। অনুগ্রহ করে আবার চেষ্টা করুন।"
            );
        }
    }
}

Optimistic Locking এর SQL আচরণ

যখন আপডেট হয়, Hibernate এরকম SQL তৈরি করে:

UPDATE products
SET stock = ?, price = ?, version = version + 1
WHERE id = ? AND version = ?;

যদি version মিলে যায় → Row আপডেট হয়, version বাড়ে যদি version না মিলে → কোনো Row আপডেট হয় না → Exception!


Pessimistic Locking

ধারণা

Pessimistic Locking-এ ধরে নেওয়া হয় যে Conflict সবসময়ই হবে। তাই ডেটা পড়ার সময়ই ডেটাবেজ লেভেলে Lock দেওয়া হয়, যাতে অন্য কোনো Transaction একই সময়ে সেই ডেটা পরিবর্তন না করতে পারে।

Pessimistic Locking-এর ধরন

Lock Type বিবরণ
PESSIMISTIC_READ অন্যরা পড়তে পারবে, কিন্তু লিখতে পারবে না
PESSIMISTIC_WRITE অন্যরা পড়তেও পারবে না, লিখতেও পারবে না
PESSIMISTIC_FORCE_INCREMENT Write Lock + Version বাড়ানো

Spring Boot-এ Pessimistic Locking Implementation

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import jakarta.persistence.LockModeType;
import java.util.Optional;

public interface ProductRepository extends JpaRepository<Product, Long> {

    // Pessimistic Write Lock — শুধুমাত্র এই Transaction শেষ হওয়া পর্যন্ত অন্য কেউ এক্সেস করতে পারবে না
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Optional<Product> findByIdWithPessimisticWriteLock(Long id);

    // Pessimistic Read Lock
    @Lock(LockModeType.PESSIMISTIC_READ)
    @Query("SELECT p FROM Product p WHERE p.id = :id")
    Optional<Product> findByIdWithPessimisticReadLock(Long id);
}

Service — Pessimistic Locking ব্যবহার

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class InventoryService {

    @Autowired
    private ProductRepository productRepository;

    @Transactional
    public Product reduceStock(Long productId, int quantity) {
        // Lock দিয়ে Product লোড করা হলো
        Product product = productRepository
            .findByIdWithPessimisticWriteLock(productId)
            .orElseThrow(() -> new RuntimeException("Product not found"));

        if (product.getStock() < quantity) {
            throw new RuntimeException("পর্যাপ্ত Stock নেই!");
        }

        product.setStock(product.getStock() - quantity);
        return productRepository.save(product);
        // Transaction শেষে Lock স্বয়ংক্রিয়ভাবে মুক্ত হয়
    }
}

Pessimistic Locking এর SQL আচরণ

-- PESSIMISTIC_WRITE এর জন্য
SELECT * FROM products WHERE id = ? FOR UPDATE;

-- PESSIMISTIC_READ এর জন্য
SELECT * FROM products WHERE id = ? FOR SHARE;

Optimistic vs Pessimistic — পার্থক্য

বিষয় Optimistic Locking Pessimistic Locking
পদ্ধতি পড়ার সময় Lock নেই, সেভ করার সময় চেক পড়ার সময়ই Lock দেওয়া হয়
Performance বেশি — Lock নেওয়া হয় না কম — ডেটাবেজ লক রাখে
Conflict হলে Exception throw করে অন্যরা অপেক্ষা করে
Deadlock ঝুঁকি নেই আছে
ব্যবহারযোগ্যতা কম Conflict হলে আদর্শ বেশি Conflict হলে আদর্শ
Implementation @Version অ্যানোটেশন @Lock অ্যানোটেশন
DB Lock ডেটাবেজে কোনো Lock থাকে না ডেটাবেজে Lock থাকে

কখন কোনটি ব্যবহার করবেন?

Optimistic Locking ব্যবহার করুন যখন:

  • Read-heavy অ্যাপ্লিকেশন — বেশিরভাগ সময় ডেটা পড়া হয়, কম লেখা হয়
  • Conflict হওয়ার সম্ভাবনা কম
  • High Concurrency দরকার
  • উদাহরণ: Blog post এডিট, Profile আপডেট, Settings পরিবর্তন

Pessimistic Locking ব্যবহার করুন যখন:

  • Write-heavy অ্যাপ্লিকেশন — একই ডেটায় একাধিক ব্যবহারকারী লেখে
  • Conflict হওয়ার সম্ভাবনা বেশি
  • ডেটার সঠিকতা (Accuracy) সর্বোচ্চ অগ্রাধিকার
  • উদাহরণ: Bank account transaction, Ticket booking, Inventory management

বাস্তব উদাহরণ — টিকেট বুকিং সিস্টেম

Optimistic Locking দিয়ে:

@Entity
public class Ticket {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String eventName;
    private int availableSeats;

    @Version
    private Long version;

    // Getters & Setters
    public int getAvailableSeats() { return availableSeats; }
    public void setAvailableSeats(int seats) { this.availableSeats = seats; }
}

@Service
public class TicketService {

    @Autowired
    private TicketRepository ticketRepository;

    @Transactional
    public String bookTicket(Long ticketId, int seats) {
        try {
            Ticket ticket = ticketRepository.findById(ticketId)
                .orElseThrow(() -> new RuntimeException("Ticket not found"));

            if (ticket.getAvailableSeats() < seats) {
                return "পর্যাপ্ত সিট নেই।";
            }

            ticket.setAvailableSeats(ticket.getAvailableSeats() - seats);
            ticketRepository.save(ticket);
            return "বুকিং সফল হয়েছে!";

        } catch (ObjectOptimisticLockingFailureException e) {
            return "বুকিং ব্যর্থ হয়েছে। আবার চেষ্টা করুন।";
        }
    }
}

Pessimistic Locking দিয়ে:

public interface TicketRepository extends JpaRepository<Ticket, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT t FROM Ticket t WHERE t.id = :id")
    Optional<Ticket> findByIdForUpdate(Long id);
}

@Service
public class TicketService {

    @Autowired
    private TicketRepository ticketRepository;

    @Transactional
    public String bookTicketSafe(Long ticketId, int seats) {
        // Lock নেওয়া হলো — অন্য কেউ এখন এক্সেস করতে পারবে না
        Ticket ticket = ticketRepository.findByIdForUpdate(ticketId)
            .orElseThrow(() -> new RuntimeException("Ticket not found"));

        if (ticket.getAvailableSeats() < seats) {
            return "পর্যাপ্ত সিট নেই।";
        }

        ticket.setAvailableSeats(ticket.getAvailableSeats() - seats);
        ticketRepository.save(ticket);
        return "বুকিং সফল হয়েছে!";
        // Transaction শেষে Lock মুক্ত হবে
    }
}

Optimistic Locking — Retry Mechanism

Optimistic Locking Exception হলে Retry করার কৌশল:

import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.orm.ObjectOptimisticLockingFailureException;

@Service
public class RetryableProductService {

    @Autowired
    private ProductRepository productRepository;

    // Exception হলে ৩ বার পর্যন্ত Retry করবে, প্রতিবার ১০০ms দেরি করবে
    @Retryable(
        retryFor = ObjectOptimisticLockingFailureException.class,
        maxAttempts = 3,
        backoff = @Backoff(delay = 100)
    )
    @Transactional
    public Product updateProductPrice(Long id, double newPrice) {
        Product product = productRepository.findById(id)
            .orElseThrow(() -> new RuntimeException("Product not found"));
        product.setPrice(newPrice);
        return productRepository.save(product);
    }
}

সংক্ষেপে মূল বিষয়

বিষয় বিবরণ
@Version Optimistic Locking সক্রিয় করে
@Lock(PESSIMISTIC_WRITE) Write Lock — শুধু এই Transaction লিখতে পারবে
@Lock(PESSIMISTIC_READ) Read Lock — অন্যরা পড়তে পারবে, লিখতে পারবে না
OptimisticLockException Version মিসম্যাচ হলে এই Exception আসে
@Retryable Optimistic Conflict হলে স্বয়ংক্রিয় Retry করে
FOR UPDATE Pessimistic Write Lock এর SQL কমান্ড

উপসংহার

Optimistic Locking এবং Pessimistic Locking — দুটোই Concurrency Control এর গুরুত্বপূর্ণ কৌশল। সঠিক পদ্ধতি নির্বাচন নির্ভর করে আপনার অ্যাপ্লিকেশনের ব্যবহারের ধরনের উপর।

যদি আপনার অ্যাপ্লিকেশনে Conflict কম হয় এবং Performance গুরুত্বপূর্ণ, তাহলে Optimistic Locking বেছে নিন। আর যদি ডেটার নির্ভুলতা সর্বোচ্চ গুরুত্বপূর্ণ এবং Conflict বেশি হওয়ার সম্ভাবনা থাকে, তাহলে Pessimistic Locking ব্যবহার করুন।

Spring Data JPA উভয় পদ্ধতিকেই সুন্দরভাবে সাপোর্ট করে — @Version এবং @Lock অ্যানোটেশনের মাধ্যমে খুব সহজেই এই দুটি কৌশল আপনার প্রজেক্টে প্রয়োগ করা সম্ভব।

Share