๐Ÿ”’ Concurrency & Locking in Distributed Systems

Welcome back to The Code Hut Distributed Systems series! In this post, we’ll explore one of the core challenges in distributed systems: concurrency control ⚡๐Ÿ›ก️

๐ŸŒŸ Why Concurrency Control Matters

In distributed systems, multiple nodes or services often need to read and write the same data concurrently. Without proper control:

  • ❌ Data may become inconsistent
  • ❌ Two services may overwrite each other's changes
  • ❌ Unexpected errors or crashes can occur

✨ Optimistic Locking

Optimistic locking assumes conflicts are rare. Changes are made without locking, but before committing, the system checks if the data was modified by another transaction.

In Java (JPA/Hibernate), we use a @Version field:


@Entity
public class Account {
    @Id
    @GeneratedValue
    private Long id;
    private double balance;

    @Version
    private Long version; // Hibernate checks this on update
}

If two transactions modify the same row concurrently, Hibernate throws OptimisticLockException. We can handle this gracefully with retries ๐Ÿ”.

๐Ÿ” Example: Retry with Optimistic Locking


int retries = 3;
boolean success = false;

while(!success && retries > 0) {
    try {
        transferMoney(fromId, toId, amount); // method using @Version entity
        success = true;
    } catch (OptimisticLockException e) {
        retries--;
        if(retries == 0) throw e;
    }
}

๐Ÿ›ก️ Pessimistic Locking

Pessimistic locking assumes that conflicts are likely. When a transaction reads a record, it immediately locks it, preventing other transactions from modifying (and sometimes even reading) that data until the first transaction completes.

This approach is common in scenarios where losing updates is unacceptable — such as financial transactions or inventory updates — and ensures strong consistency ✅.

Low-Level Example (EntityManager)


Account account = em.find(Account.class, 1L, LockModeType.PESSIMISTIC_WRITE);
account.setBalance(account.getBalance() + 100);

Here, the database executes a SELECT ... FOR UPDATE under the hood. Other transactions attempting to update the same record will wait until the lock is released, ensuring data integrity but potentially reducing concurrency ⚠️.

High-Level Example (Spring Data JPA)

Spring Data provides a cleaner way to apply pessimistic locks directly in the repository layer using @Lock.


@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {

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

Then in a transactional service:


@Service
public class AccountService {

    @Autowired
    private AccountRepository accountRepository;

    @Transactional
    public void deposit(Long accountId, double amount) {
        Account account = accountRepository.findByIdForUpdate(accountId)
            .orElseThrow(() -> new EntityNotFoundException("Account not found"));

        account.setBalance(account.getBalance() + amount);
    }
}

This ensures that while one transaction is updating the account, others attempting to modify it will block until the first one completes. It’s simple, declarative, and ideal for critical sections of business logic.

Tip ๐Ÿ’ก: You can also configure lock timeouts via @QueryHints:


@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({ @QueryHint(name = "jakarta.persistence.lock.timeout", value = "3000") })
@Query("SELECT a FROM Account a WHERE a.id = :id")
Optional<Account> findByIdForUpdateWithTimeout(@Param("id") Long id);

Use pessimistic locks when correctness outweighs performance, but keep transactions short to avoid deadlocks and contention. ⚙️

✅ When to Use Which?

  • Optimistic Locking: Low contention, high scalability, retries acceptable.
  • Pessimistic Locking: High contention, critical operations (e.g., financial transactions).

Next in the Series ๐Ÿš€

In the next post, we’ll explore Consensus & Coordination ๐Ÿค, including Raft, Paxos, and leader election in distributed systems.

Label for this post: Distributed Systems

Comments

Popular posts from this blog

๐Ÿ› ️ The Code Hut - Index

๐Ÿ›ก️ Resilience Patterns in Distributed Systems

๐Ÿ›ก️ Thread-Safe Programming in Java: Locks, Atomic Variables & LongAdder