Order ids were duplicating in production: 160 distinct (tenant, orderid) pairs
are shared by more than one order, worst of them "1135-1" on 108 orders, and
every order tenant 1147 has ever placed is numbered "1147-1".
getSequenceno read MAX(seqno)+1 and updateSeqno incremented, both against
r.db rather than the order's transaction and separated by the whole order
insert. Two concurrent orders therefore read the same number before either
wrote, and an order that rolled back still consumed one. Three further
defects made it worse:
- A NULL orderseqno made COALESCE(MAX(orderseqno) + 1, 1) evaluate
NULL + 1 = NULL and fall through to a hardcoded "<tenantid>-1". The
increment then computed NULL + 1 = NULL too, so the counter could never
leave NULL and every subsequent order reused that same id.
- Tenants with several ordersequences rows (tenant 1135 has ~25) hit a
GROUP BY returning multiple rows, of which Scan kept the first
arbitrarily, while the increment updated all of them.
- A tenant with no row at all fell back to "<tenantid>-1" indefinitely,
because nothing ever created one.
nextSequenceNo replaces both functions with a single UPDATE ... RETURNING run
inside the caller's transaction, so the counter row stays locked until the
order commits and concurrent orders queue rather than collide. A NULL seeds
from the tenant's existing order count — at least as high as any number
already issued, so recovery cannot reissue a used id — the counter is pinned
to the tenant's lowest sequenceid so reads and writes address one row, and a
missing row is created on first use.
Verified against production data in rolled-back transactions: tenant 1147
(NULL) now yields 1147-9, 1147-10, ...; tenant 1135 (NULL plus duplicate rows)
1135-356 onward; tenant 916 keeps its 916-2024115209 subprefix format; an
unknown tenant creates its row and starts at 1. Eight concurrent allocations
produced eight distinct ids. Two real orders through the API returned 1147-9
and 1147-10, then were cancelled with stock restoring to its baseline.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
51 KiB
51 KiB