Build1 publisher3 min readPublished
A conditional SQL update should decide when a 10-minute inventory hold expires
Reservation expiry belongs in the database row, a dev.to guide argues, because 300 holds ending at once need five minutes of a 60-per-minute notification API. With the two separated, a throttled notification backlog can grow for minutes without keeping a single item off sale.
The Engineer · Build desk

What happened
- The guide says cron can find overdue reservations but cannot meter a per-minute API, and a delayed queue job cannot tell whether inventory is still held.
- Its Go example flips a row from held to expired only when expires_at has passed, and inserts an outbox row only when exactly one row changed.
- The outbound notification is recorded in an outbox keyed on reservation ID plus event type, in the same transaction, and a dispatcher sends it after commit.
- Paging keys on the age of the oldest overdue, still-held reservation crossing product tolerance, with the last successful scan time recorded so a dead scheduler shows.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Queue backlog and 429 counts become a separate notification incident, so on-call teams need an inventory page built on a query of the reservations table.
- constraint No external API call can run inside the expiry transaction, so a timed-out request can never hold a row lock open or produce a second expiry event.
- cost Adopting the pattern means building an outbox dispatcher, transaction retry handling and a deduplication check in each consumer, because a publish can succeed while its acknowledgment fails.
According to the dev.to guide, three kinds of caller can reach the same reservation: a second worker, a retried delivery, or the scheduler's scan [15]. Any of them may carry a job payload saying the hold is still live. The guide says to check state again inside the transaction and trust that over the payload [15]. A purchase that already committed must never be reversed by a late worker [16].
The Go example is the part of the post I would copy. Its core is one statement: `UPDATE reservations SET state = 'expired' WHERE id = ? AND state = 'held' AND expires_at <= ?` [9]. If the database's isolation behaves the way the guide asks readers to confirm [11], one caller gets `RowsAffected()` equal to 1 and inserts the outbox row. Every other caller gets 0 and commits a transaction that wrote nothing [9]. The unique `(reservation_id, event_type)` pair on the outbox table is a second guard if two writers ever both believe they won [9].
The capacity exercise uses a 10-minute hold and a notification API that admits 60 requests a minute. The author labels both as example inputs, not measured service limits [4]. At that ceiling, 300 holds ending at the same instant need at least five minutes of remote capacity for one request each, before retries or other clients are counted [5]. For the five minutes to carry over to a real system, the quota has to belong to this sender alone, each notification has to be a single request, and nothing can be retried. Few notification providers have that kind of week. The guide says shared quotas and 429 responses can make the drain longer [6].
Tie the notification to the release and the drain turns into stock that cannot be sold. The last hold in that burst would still be held about 15 minutes after it was placed, 1.5 times its stated life, and longer once 429s start [18]. The guide's position is that the notification delay must not extend the inventory hold [6].
The function takes `now` from its caller and binds it into the comparison with `expires_at` [9]. The guide asks readers to verify that timestamp binding keeps the conditional update's meaning intact. It also says the database transaction, not a worker's clock alone, must be the final arbiter of state [11]. I would compare against the database's own clock, so a worker whose clock runs fast cannot release a hold before its deadline.
On the outbound side, the guide defines HTTP 429 as the requester sending too many requests in a given time. It honors Retry-After when the response includes it and applies bounded backoff with jitter when it does not [14].
What to watch
- A measured quota from the actual notification provider, in place of the example 60-per-minute ceiling, would set the real drain time for a burst of expiries.
- The guide's full 429 handling, and whether its backoff caps keep a burst from stretching far past the drain estimate.