The document counter, and a number that cannot fork

Featurekamo-shared-library
Shipped
August 27, 2026 at 11:21 PM UTC
Author
Kamo
Commit
c42a68f

Every existing document number on this platform is a check-then-insert race with no unique constraint behind it: quote numbers are COUNT(...)+1, subscription invoice numbers load every row for the org into memory and take .size()+1, loan numbers pick a random value and fail open by returning null. Two concurrent writers read the same figure and both write a number one higher. An invoice number cannot be corrected after the fact — the customer already has the PDF — so this series is minted under a row lock instead. A table and not a sequence: there is no CREATE SEQUENCE anywhere on this platform, and declaring a @SequenceGenerator would make one exportable to KI's ddl-auto: update, which would create it START WITH 1 and hand out ids below 2^53 into a unique_rowid() schema. A sequence also cannot reset per year or per tenant, which is the shape an invoice series has. findForUpdate is the only read on the repository — it extends Repository, not JpaRepository, so there is no unlocked findById to reach for by accident. insertIfAbsent uses ON CONFLICT DO NOTHING rather than SuppressionService's catch-the-violation idiom. That idiom owns its whole transaction; this one runs inside the caller's, and a unique violation aborts a PostgreSQL/Yugabyte transaction outright, so the catch would read as handled while the invoice INSERT failed anyway. Observed failures, verbatim. RED (before the classes existed): package **************** does not exist symbol: class DocumentCounterRepository / DocumentType / DocumentNumberService MUTATION (@Lock(PESSIMISTIC_WRITE) removed from findForUpdate), after the fake was made to model a real read/write round trip and to hand each reader its own instance: **************** Expected size: 32 but was: 7 in: ["INV-2026-0007", "INV-2026-0005", "INV-2026-0006", "INV-2026-0003", "INV-2026-0004", "INV-2026-0001", "INV-2026-0002"] **************** [findForUpdate must instruct the database to lock the row] Expecting actual not to be null GREEN after revert: Tests run: 43, Failures: 0, Errors: 0, Skipped: 0 Honesty note recorded in the test's javadoc: with a bare Thread.yield() the concurrency test stayed GREEN with the lock removed, and it stayed green with read latency alone. It only bites once the fake hands each caller its own entity instance — which is what two persistence contexts actually do — and puts latency on the write. No database is involved in these tests; what they prove is that the service routes its read through a finder the database is told to lock. The database half is pinned by the annotation assertion and by UX_DOCCOUNTER_ORG_TYPE_YEAR.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing