When working with shared data in a concurrent environment, ensuring consistency and avoiding corruption are non-negotiable. That’s where locking mechanisms come into play. Today, let’s dive into pessimistic locking—a tried-and-true method to handle contention for shared resources—and see how it works through a practical example.

I’ll keep this grounded in real-world scenarios so you can relate it to challenges you’ve likely faced (or will face) in your engineering journey.

What Pessimistic Locking Is

Pessimistic locking is like putting a “DO NOT DISTURB” sign on a resource. When a thread acquires a lock on shared data, no other thread can modify it until the lock is released. It assumes contention will happen and proactively prevents conflicts.

This approach is often necessary when the stakes are high, and we can’t afford even a single data inconsistency. But it comes at a cost: reduced concurrency. Let’s unpack how it works with an example.

Example: E-Commerce Inventory Management

Imagine an e-commerce platform where users are placing orders for products. The platform needs to update the inventory in real time, ensuring that two users can’t buy the same item simultaneously if there’s only one unit left.

Here’s how pessimistic locking might handle this:

Step-by-Step Workflow

Fetch and Lock the Shared Data:
The shared data here is the product inventory stored in a database.
A thread (e.g., representing a user placing an order) fetches the relevant inventory record and locks it. This is done using a SELECT ... FOR UPDATE statement in SQL, which ensures no other thread can modify the record until the lock is released.
For example:

    SELECT quantity 
    FROM inventory 
    WHERE product_id = 'ABC123' 
    FOR UPDATE;

    Process the Data:
    With the lock in place, the thread can safely check the available inventory, calculate pricing, apply discounts, and validate the transaction. This processing step can include several operations, but all must happen within the scope of the lock.

    Update the Data:
    After confirming that the transaction is valid (e.g., the user can buy 2 units of a product with sufficient stock), the thread updates the inventory:

      UPDATE inventory 
      SET quantity = quantity - 2 
      WHERE product_id = 'ABC123';

      Commit the Transaction and Release the Lock:
      Once the transaction is committed, the lock is released, allowing the next waiting thread to acquire it and proceed.

        Characteristics of Pessimistic Locking

        1. Long Lock Duration

        In pessimistic locking, the lock is held from the moment the transaction begins until it’s fully committed. This ensures data consistency but increases the potential for contention, especially in high-concurrency environments.

        2. Sequential Processing

        If multiple threads attempt to update the same resource, they are processed one by one. This guarantees consistency but can slow down throughput as threads wait for their turn.

        3. High Contention Scenarios

        Pessimistic locking shines in scenarios with high contention, where multiple threads are likely to access the same resource simultaneously. By locking early, we avoid the risk of conflicting updates.

        Performance Considerations

        While pessimistic locking ensures data consistency, it comes with trade-offs:

        1. Thread Blocking:
          Other threads attempting to access the locked resource are blocked, leading to potential bottlenecks in high-traffic systems.
        2. Deadlocks:
          Poor lock management can result in deadlocks, where two or more threads wait indefinitely for resources locked by each other. For instance, Thread A locks Resource 1 and waits for Resource 2, while Thread B locks Resource 2 and waits for Resource 1.
        3. Scalability Challenges:
          In systems with high concurrency, the sequential nature of pessimistic locking can limit scalability. Threads waiting for locks consume resources, reducing overall system throughput.

        When to Use Pessimistic Locking

        Pessimistic locking is best suited for scenarios where:

        • High Contention is Expected:
          For example, an auction system where users place bids on the same item in real-time.
        • Data Integrity is Critical:
          Systems where a single inconsistency can lead to significant issues, like financial systems or inventory management.
        • Conflicts are Frequent:
          If updates to the same resource are common, pessimistic locking ensures consistency without requiring retry logic (as in optimistic locking).

        Pessimistic Locking Exists For a Reason

        While working on a financial reconciliation system where accuracy was non-negotiable. We used pessimistic locking to ensure that each transaction was processed sequentially. Even though the locks slightly slowed down the system, the trade-off was worth it for the consistency it provided. One time, a race condition slipped through during testing when we experimented with optimistic locking—it was a reminder that pessimistic locking, while conservative, can be a lifesaver in the right context.

        Quick Example

        Code Example: Pessimistic Locking in SQL 
        -- Step 1: Start a transaction
        BEGIN TRANSACTION;
        
        -- Step 2: Fetch the inventory and lock the record
        SELECT InventoryCount
        FROM Products
        WHERE ProductID = 1
        WITH (UPDLOCK, ROWLOCK);
        
        -- Step 3: Process the data (e.g., check inventory, calculate discounts)
        -- Assuming the processing happens in your application logic
        
        -- Step 4: Update the inventory
        UPDATE Products
        SET InventoryCount = InventoryCount - 1
        WHERE ProductID = 1;
        
        -- Step 5: Commit the transaction, releasing the lock
        COMMIT TRANSACTION;

        In this example:

        • The WITH (UPDLOCK, ROWLOCK) clause ensures that the record is locked for update, preventing other threads from modifying it until the transaction is complete.
        • Other threads trying to access the same record will be blocked until the lock is released.

        C# Pessimistic Locking Example

        Pessimistic locking ensures that when a record is being updated, it is locked for other users, preventing them from making changes to it until the current transaction is complete. In Entity Framework, pessimistic locking can be achieved using SQL SELECT ... FOR UPDATE (in the case of SQL Server, this is usually done with WITH (ROWLOCK, UPDLOCK) hints).

        Here’s an example of how to implement pessimistic locking in Entity Framework using SQL Server with WITH (UPDLOCK):

        Define your model: Ensure you have a model that you want to lock during the transaction.

        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public decimal Price { get; set; }
        }

        Create the DbContext: You should have a DbContext class that includes the Product table.

        public class ApplicationDbContext : DbContext
        {
            public DbSet<Product> Products { get; set; }
        }

        Using Pessimistic Locking: The actual pessimistic locking in Entity Framework can be done through raw SQL execution. The key is using UPDLOCK and ROWLOCK to lock the record for update.

        using (var context = new ApplicationDbContext())
        {
            // Start a transaction
            using (var transaction = context.Database.BeginTransaction())
            {
                try
                {
                    // Get the product using raw SQL with pessimistic locking
                    var product = context.Products
                                         .FromSqlRaw("SELECT * FROM Products WITH (ROWLOCK, UPDLOCK) WHERE Id = {0}", 1)
                                         .FirstOrDefault();
        
                    if (product != null)
                    {
                        // Update the product
                        product.Price = 20.99m;
                        context.SaveChanges();
                    }
        
                    // Commit transaction
                    transaction.Commit();
                }
                catch (Exception ex)
                {
                    // Handle exception and rollback transaction if needed
                    transaction.Rollback();
                    Console.WriteLine(ex.Message);
                }
            }
        }

        Explanation:

        1. WITH (ROWLOCK, UPDLOCK):
          • ROWLOCK: Requests a lock on the row.
          • UPDLOCK: Acquires a lock to allow updates but prevents others from acquiring a conflicting lock.
        2. FromSqlRaw: This method allows you to execute raw SQL queries, which is needed because Entity Framework doesn’t have built-in support for FOR UPDATE.
        3. Transaction: The BeginTransaction and Commit ensure that the operations are atomic. If an error occurs, the transaction is rolled back, and no changes are made.

        Notes:

        • SQL Server is the only database that directly supports WITH (UPDLOCK) in this manner. For other databases (like PostgreSQL or MySQL), the equivalent locking mechanisms will vary, and raw SQL syntax would need to be adjusted accordingly.
        • Always handle exceptions carefully, as deadlocks or lock timeouts could cause failures.

        Takeaways

        Pessimistic locking is straightforward: acquire a lock, process safely, release the lock. It’s reliable, but the cost is reduced concurrency. The decision to use it boils down to a classic trade-off between safety and performance.

        In the next article, we’ll explore optimistic locking, where the system assumes the best-case scenario (low contention) and resolves conflicts only when they occur. We’ll also compare these two approaches to help you decide which fits your system’s needs.

        Let me know if you’ve encountered challenges with locking mechanisms—whether it’s contention, deadlocks, or performance hits. I’d love to hear your stories and discuss potential solutions!


        0 Comments

        Leave a Reply

        Avatar placeholder

        Your email address will not be published. Required fields are marked *