Skip to content

fix(state): retry state-store queries past a Prisma Postgres cold-start (FT-5226)#82

Merged
wmadden-electric merged 1 commit into
mainfrom
claude/state-store-coldstart-retry
Jul 15, 2026
Merged

fix(state): retry state-store queries past a Prisma Postgres cold-start (FT-5226)#82
wmadden-electric merged 1 commit into
mainfrom
claude/state-store-coldstart-retry

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

A freshly provisioned or idle-resumed Prisma Postgres database refuses connections while its upstream warms up (FT-5226) — the edge proxy answers Failed to connect to upstream database until the real Postgres is reachable. This is the same behavior the cold-connect canary tracks; pg-connection.ts documents upstream database as its signature.

The gap

The state layer already rides this out on the bootstrap migrationlayer.ts retries the first connect for 2 minutes, with a comment naming exactly this case. But the per-op state queries that run afterwards did not: a bare Effect.tryPromise in service.ts (every CRUD op) and in lock.ts (checkLive).

So when the state DB had gone idle, the first query during a plan/destroy hit the cold upstream and failed the whole deploy. Surfaced by a Deploy, verify, destroy CI run: StateStoreError: Failed to connect to upstream database at plan.make during destroy, ~13s in — far short of the 2-min migration retry, because it came from the unretried query path, not bootstrap.

The fix

retryColdStart (new transient.ts) wraps the CRUD attempt and checkLive, retrying every 5s for up to 2 minutes — the same budget bootstrap already uses.

The retry is deliberately scoped to connection establishment failures (ECONNREFUSED/ENOTFOUND/EAI_AGAIN and the upstream database message). Mid-session drops — a terminated/reset connection, e.g. postgres.js's CONNECTION_ENDED after a pool ends — are not retried: for the state store those are the lost-lease signal checkLive must surface loudly, not paper over. Verified empirically that a refused connect (ECONNREFUSED) and a query-after-.end() (CONNECTION_ENDED) have distinct signatures, and that lock.ts's lease-loss test (which ends the pool) still fails fast rather than retrying.

This makes the set narrower than @internal/prisma-cloud's pg-connection.ts (which also retries mid-session drops for the runtime store client). That helper can't be imported here regardless: @internal/prisma-cloud depends on @internal/lowering, so importing it back would cycle — hence the small, purpose-scoped local predicate.

Tests / verification

  • New transient.test.ts: predicate classification (cold-start vs lost-lease vs dead-pool vs real query error, incl. the cause unwrap) and retry behavior (retries a cold start to success; never retries a lost lease) with an injected instant schedule.
  • Full state suite (real Postgres) green — lock.test.ts lease-loss/FT-5219 tests finish in <1s, confirming the dead-pool path is not retried.
  • @internal/lowering 97 pass; full workspace build (24/24) and typecheck (50/50) green.

Follow-up to #77 (the storage rename). Independent change.

🤖 Generated with Claude Code

@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@prisma/composer@82
npm i https://pkg.pr.new/@prisma/composer-prisma-cloud@82

commit: 787a703

…rt (FT-5226)

A freshly provisioned or idle-resumed Prisma Postgres database refuses
connections while its upstream warms up (FT-5226) — the edge proxy
answers "Failed to connect to upstream database" until the real Postgres
is reachable. The state layer already rides this out on the bootstrap
migration (layer.ts retries it for 2 minutes), but the per-op state
queries that run afterwards did not: a bare Effect.tryPromise in
service.ts (every CRUD op) and in lock.ts (checkLive). So when the state
DB had gone idle, the first query during a plan/destroy failed the whole
deploy — observed in CI as a StateStoreError at plan.make during destroy.

Extend the same cold-start budget to those queries: retryColdStart wraps
the CRUD attempt and checkLive, retrying every 5s for up to 2 minutes.

The retry is deliberately scoped to connection ESTABLISHMENT failures —
ECONNREFUSED/ENOTFOUND/EAI_AGAIN and the "upstream database" message.
Mid-session drops (a terminated/reset connection, e.g. postgres.js's
CONNECTION_ENDED after the pool ends) are NOT retried: for the state
store those are the lost-lease signal checkLive must surface loudly, not
paper over. This makes the set narrower than @internal/prisma-cloud's
pg-connection.ts, which can't be imported here anyway (it depends on
@internal/lowering — importing back would cycle).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the claude/state-store-coldstart-retry branch from a7539f5 to 787a703 Compare July 15, 2026 15:37
@wmadden-electric wmadden-electric merged commit d90773e into main Jul 15, 2026
15 checks passed
@wmadden-electric wmadden-electric deleted the claude/state-store-coldstart-retry branch July 15, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants