fix(retry): herken de Prisma 7 driver-adapter foutvormen #97

Merged
janpeter merged 1 commit from claude/strange-proskuriakova-fef463 into main 2026-07-26 08:59:00 +02:00
Owner

Wat er stuk was

De retry-lussen matchten op velden die Prisma 7 + @prisma/adapter-pg niet meer vult. Beide predicaten herkenden niets en degradeerden stil naar één poging.

Gemeten op Postgres 17.9 / Prisma 7.8:

Postgres Komt aan als Detail zit in
23505 PrismaClientKnownRequestError P2002, meta.target afwezig meta.driverAdapterError.cause.constraint.fields
40001 in de callback P2034 (zoals vanouds) meta.driverAdapterError.cause
40001 bij COMMIT kale DriverAdapterError, geen code, geen meta cause

De P2002-message rendert nog steeds Unique constraint failed on the fields: (`product_id`, `code`) — die tekst komt uit constraint.fields, niet uit meta.target. De melding suggereert dus een veld dat leeg is.

Getroffen: withCodeUniqueRetry (nul retries, altijd), withSerializableRetry (commit-time conflicten) en create-sprint.ts (identieke blindheid).

Wat er verandert

  • src/lib/prisma-driver-error.ts (nieuw) — haalt de driver-adapter-cause uit beide verpakkingen; predicaten matchen op cause.originalCode (de SQLSTATE). De legacy meta.target-tak blijft staan voor andere adapters.
  • src/lib/retry-backoff.ts (nieuw) — full jitter, 5 ms basis / 100 ms plafond. Zonder wachttijd retryen de verliezers van een contended create in dezelfde tick en botsen opnieuw.
  • create-sprint.ts — zelfde defect, gaat mee.
  • CLAUDE.md — foutvormen-tabel en waarom mocks hier niet als guard werken.

Metingen (vóór → na)

Scenario Vóór Na
echte 23505withCodeUniqueRetry 3/3 niet geretryd, gooide door 5/5 geretryd, hersteld
echte 40001 bij commit → withSerializableRetry 3/3 één kant gerejecteerd 5/5 beide geslaagd
6 gelijktijdige create_pbi 16 van 60 handlers faalden na alle 4 pogingen 1 van 60

Over de bestaande integratietest

create-concurrency.integration.test.ts was geen betrouwbare guard: op een snelle lokale DB slaagt hij ook met de retry volledig kapot, omdat het conflict daar op 40001 uitkomt (wél geretryd) in plaats van 23505 (niet geretryd). Op een tragere/remote DB faalt hij deterministisch. Vandaar twee nieuwe tests die fouten afspelen die Postgres écht opwierp — bevestigd: ze falen zónder de fix en slagen ermee. Handgebouwde error-objecten asserten de vorm waaruit ze gebouwd zijn en zien deze regressie principieel niet.

Verificatie

  • npx vitest run mét TEST_DATABASE_URL: 1266/1266 groen (zonder DB: 1263 + 3 skipped)
  • npm run typecheck + npm run typecheck:tests: schoon
  • 10× herhaalde real-DB-runs van de integratiesuite: 10/10 groen
  • A/B met de fix ge-stasht om elke meting te ijken

Caveat

Er stond geen Postgres op deze machine; de metingen komen van een wegwerp-PG 17.9 met het schema via prisma db push (dus zonder CHECK-constraints — niet relevant voor dit pad, wel voor wie de opzet hergebruikt).

## Wat er stuk was De retry-lussen matchten op velden die Prisma 7 + `@prisma/adapter-pg` niet meer vult. Beide predicaten herkenden **niets** en degradeerden stil naar één poging. Gemeten op Postgres 17.9 / Prisma 7.8: | Postgres | Komt aan als | Detail zit in | |---|---|---| | `23505` | `PrismaClientKnownRequestError` P2002, **`meta.target` afwezig** | `meta.driverAdapterError.cause.constraint.fields` | | `40001` in de callback | P2034 (zoals vanouds) | `meta.driverAdapterError.cause` | | `40001` bij COMMIT | **kale `DriverAdapterError`**, geen `code`, geen `meta` | `cause` | De P2002-*message* rendert nog steeds ``Unique constraint failed on the fields: (`product_id`, `code`)`` — die tekst komt uit `constraint.fields`, niet uit `meta.target`. De melding suggereert dus een veld dat leeg is. Getroffen: `withCodeUniqueRetry` (nul retries, altijd), `withSerializableRetry` (commit-time conflicten) en `create-sprint.ts` (identieke blindheid). ## Wat er verandert - **`src/lib/prisma-driver-error.ts`** (nieuw) — haalt de driver-adapter-cause uit beide verpakkingen; predicaten matchen op `cause.originalCode` (de SQLSTATE). De legacy `meta.target`-tak blijft staan voor andere adapters. - **`src/lib/retry-backoff.ts`** (nieuw) — full jitter, 5 ms basis / 100 ms plafond. Zonder wachttijd retryen de verliezers van een contended create in dezelfde tick en botsen opnieuw. - **`create-sprint.ts`** — zelfde defect, gaat mee. - **`CLAUDE.md`** — foutvormen-tabel en waarom mocks hier niet als guard werken. ## Metingen (vóór → na) | Scenario | Vóór | Na | |---|---|---| | echte `23505` → `withCodeUniqueRetry` | 3/3 **niet geretryd**, gooide door | 5/5 geretryd, hersteld | | echte `40001` bij commit → `withSerializableRetry` | 3/3 **één kant gerejecteerd** | 5/5 beide geslaagd | | 6 gelijktijdige `create_pbi` | **16 van 60** handlers faalden na alle 4 pogingen | **1 van 60** | ## Over de bestaande integratietest `create-concurrency.integration.test.ts` was geen betrouwbare guard: op een snelle lokale DB slaagt hij **ook met de retry volledig kapot**, omdat het conflict daar op `40001` uitkomt (wél geretryd) in plaats van `23505` (niet geretryd). Op een tragere/remote DB faalt hij deterministisch. Vandaar twee nieuwe tests die fouten afspelen die Postgres écht opwierp — bevestigd: ze falen zónder de fix en slagen ermee. Handgebouwde error-objecten asserten de vorm waaruit ze gebouwd zijn en zien deze regressie principieel niet. ## Verificatie - `npx vitest run` mét `TEST_DATABASE_URL`: **1266/1266 groen** (zonder DB: 1263 + 3 skipped) - `npm run typecheck` + `npm run typecheck:tests`: schoon - 10× herhaalde real-DB-runs van de integratiesuite: 10/10 groen - A/B met de fix ge-`stash`t om elke meting te ijken ## Caveat Er stond geen Postgres op deze machine; de metingen komen van een wegwerp-PG 17.9 met het schema via `prisma db push` (dus **zonder** CHECK-constraints — niet relevant voor dit pad, wel voor wie de opzet hergebruikt).
fix(retry): herken de Prisma 7 driver-adapter foutvormen
All checks were successful
CI / Verify (pull_request) Successful in 1m46s
297ae5cb26
De retry-lussen matchten op velden die Prisma 7 + @prisma/adapter-pg niet meer
vult: bij P2002 is `meta.target` leeg (de kolommen staan in
meta.driverAdapterError.cause.constraint.fields), en een 40001 bij COMMIT wordt
nooit P2034 maar ontsnapt als kale DriverAdapterError zonder code of meta.
Beide predicaten herkenden dus niets en degradeerden stil naar één poging —
groen in review, nul retries in productie.

Matcht nu op cause.originalCode (de SQLSTATE) via het nieuwe
src/lib/prisma-driver-error.ts; de legacy meta.target-tak blijft staan voor
andere adapters. create-sprint.ts had dezelfde blindheid en gaat mee.

Daarnaast full-jitter backoff tussen pogingen: zonder wachttijd retryen de
verliezers van een contended create in dezelfde tick en botsen opnieuw.

Gemeten op Postgres 17.9 / Prisma 7.8, vóór -> na:
- echte 23505 door withCodeUniqueRetry: 3/3 niet geretryd -> 5/5 hersteld
- echte 40001 bij commit door withSerializableRetry: 3/3 één kant
  gerejecteerd -> 5/5 beide geslaagd
- 6 gelijktijdige create_pbi-calls: 16 van 60 handlers faalden na alle vier
  pogingen -> 1 van 60

De guards zitten in create-concurrency.integration.test.ts: die speelt fouten
af die Postgres écht opwierp (vereist TEST_DATABASE_URL, skipt zonder). Een
handgebouwd error-object assert de vorm waaruit het gebouwd is en ziet deze
regressie principieel niet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
janpeter/scrum4me-mcp!97
No description provided.