Convex OCC Sharded Counter
Every Convex mutation is a serializable transaction over the whole database, so the read-modify-write that silently loses updates on Postgres at READ COMMITTED is simply correct here, with no BEGIN, no isolation level to pick, and no row lock to forget; a read that found nothing even conflicts with an insert into the range it searched, which is the unique constraint you never had to write. What you pay instead is contention: every writer to one hot document invalidates every other writer's read set, retries go quadratic in the writer count, and Convex eventually gives up with Write conflict: Optimistic concurrency control. This spreads increments across N shard rows selected by a hash of a caller token, never Math.random(), which returns a new value on every OCC re-execution and would move the retry to a different row. The rollup is a query, because a query never conflicts and re-runs reactively, while summing every shard inside a mutation would put all N back into one read set and undo the sharding entirely; a limit a mutation genuinely has to enforce gets a per-shard slice of the budget so the check reads only the row it is about to write. Also documents the read-set rule behind all of it, that .filter() has no index and scans the table, so a mutation written that way conflicts with every unrelated insert and works fine at 100 rows before throwing at 100k. Ships with a small model of Convex OCC and READ COMMITTED that runs the same handler under both engines, and an eight-property demo that runs under bun with no deployment. Pinned to [email protected].
npx shadcn@latest add https://ui.aryank.space/r/convex-occ-sharded-counter.jsonInstalls from ui.aryank.space. To add it by hand, copy the files in Files below, or register the @compronents namespace via the docs.
Both mutations read the same document, so both read sets cover it. The first commit invalidates the second, which aborts and re-runs from the latest timestamp. The total is never wrong, but exactly one writer commits per round, so under load the retries go quadratic and Convex eventually returns Write conflict: Optimistic concurrency control.