Urgent.News

What's breaking now, across thousands of outlets.

Tech

Handling Concurrent Requests in Symfony with Doctrine and PostgreSQL

Two requests reserve the last available seat. Two workers decrement the same stock quantity. Two administrators update the same order. Each operation can work correctly on its own and still produce an incorrect result when executed concurrently. In Symfony applications using Doctrine, the solution requires coordinating database access and making sure the ORM exposes the state that was actually…

Two requests can reserve the last available seat at the same time. If two workers modify the same stock quantity simultaneously, or two administrators update the same order concurrently, the operations might produce incorrect results. In Symfony applications using Doctrine with PostgreSQL at READ COMMITTED isolation level, coordinating database access is crucial to ensure the ORM exposes the state that was actually locked.

The article uses seat reservations as an example to illustrate the approach, but the same method applies to inventory, balances, and guarded status transitions. The solution requires protecting the whole decision: read current state, validate the invariant, and commit the corresponding changes together. Different operations may need different concurrency mechanisms.

Before diving into pessimistic locking using SELECT ... FOR UPDATE, it's helpful to consider the overall decision model. The typical mechanism involves reading the current state, validating several conditions, and then modifying the state. Pessimistic locking with SELECT ... FOR UPDATE is one approach, while conditional UPDATE handles edits spanning multiple HTTP requests. Optimistic locking uses UNIQUE constraints or primary keys with ON CONFLICT.

However, a transaction alone is not enough to prevent concurrency issues. Consider a read-check-write operation: find a seat, check if it's available, hold it for a reservation, and update the owner. Two requests can both read the seat as available before either writes its change. Even when wrapping this code in a transaction, PostgreSQL may allow both availability checks to pass.

This is because PostgreSQL serializes writes but an update with only a WHERE clause can still overwrite the previous owner. The application made its business decision using an earlier state, which follows PostgreSQL's READ COMMITTED behavior.

To address this, Doctrine provides LockMode::PESSIMISTIC_WRITE for short operations that read an entity, validate its state, and update it. On PostgreSQL, this uses SELECT ... FOR UPDATE. The transaction boundary should encompass the entire use case, and the locking query should be executed within this transaction. The controller should call the service, and external HTTP calls or other slow work should be kept outside the locked section.

Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.

Read the original at dev.to →

More in Tech

More from Sunday 27 September →