Backend Development › Transactions & Concurrency Control
Transaction Boundaries
Deciding where a transaction should start and end.
Also known as: transaction boundaries, transaction scope, transaction boundary
Transaction boundaries are the points where you open and close a transaction — deciding exactly which operations form one atomic unit. Getting them right is central to correctness and performance: too loose and you hold locks and connections too long; too tight and a logical change can fail halfway.
Good boundaries match a unit of work: one request or use case, containing the writes that must all succeed or all fail.
request → BEGIN → validate, write order, write payment → COMMIT
(not: BEGIN → write → call external API (slow) → write → COMMIT)
The classic mistakes:
- Holding a transaction across an external call. Calling a slow third-party API inside a transaction keeps row locks and a database connection held for the duration, blocking others and risking timeouts. Do external calls outside the transaction.
- Beginning too early. Opening a transaction before validation or fetching unrelated data wastes a connection and lengthens the transaction for no reason.
- One transaction for the whole request by default. Frameworks sometimes auto-wrap the entire request; a slow step then holds a connection, and any external side effect isn’t rolled back anyway.
- Too-small boundaries. Splitting a logically atomic change into separate transactions means a failure between them leaves partial state (see atomicity). Group the writes that belong together.
- Long transactions and MVCC. Long transactions block cleanup of dead row versions (see VACUUM) and can pin the database’s ability to reclaim space.
- Leaking connections. A transaction that stays open on a pooled connection ties it up; under load, connections exhaust and requests queue (see connection pooling).
- Assuming side effects roll back. Emails, messages and API calls within a transaction persist on rollback — use an outbox pattern for reliable side effects (see inbox pattern).
How to set them: one transaction per unit of work; external I/O and slow work outside it; validation and non-writing reads outside where possible; and always closed (committed or rolled back) promptly. The service layer is the natural place to define the boundary, since it knows where the use case begins and ends.