Prisma Postgres pooler stalls a new client’s first query after ~15 minutes idle
Package and version
[email protected] @prisma/[email protected]
What happened?
Prisma Postgres pooler (pooled.db.prisma.io, Vercel Marketplace) stalls a new client's first statement when it hands out a server connection that sat idle ~15 minutes. The query never returns. Prisma has no bound on that wait (socket_timeout does not cover prepare_typed), so the request hangs until the host kills it.
We hit this on a Vercel cron (GET /api/cron, every 15 minutes, maxDuration = 300). After switching production DB_URL from the direct host (db.prisma.io) to the pooled host (2026-09-08), every other cron tick timed out:
- success ~1s → leave one server connection idle in the pooler
- ~899s later the next client gets that connection
- first statement (
SELECT 1/$executeRaw) never gets a reply - Vercel 504: "Task timed out after 300 seconds" (~40 × 504/day)
- pooler then drops that server connection; the next tick succeeds; cycle repeats
During a hang, pg_stat_activity (via the direct host) showed our backend idle / ClientRead, pg_locks empty, no running query. The hang also happens on a brand-new serverless process with no prior Prisma connection, so it is not a stale client socket. connect_timeout never fires (TCP+TLS+auth already succeeded). Setting socket_timeout=30 did not help.
A second SELECT 1 issued while the first is still pending returns immediately on a new pooled connection.
What did you expect to happen?
The first query on a pooled connection should return or fail within connect_timeout / socket_timeout. The pooler should reset or discard a server connection that has been idle ~15 minutes instead of handing it to a new client in a half-dead state. socket_timeout should also cover the prepare path (prepare_typed), not only execute.
Minimal reproduction
Cannot share the production DB. Repro is timing + the Prisma Postgres pooler, not schema:
Prisma Postgres via Vercel Marketplace. App uses the pooled URL (
pooled.db.prisma.io) withconnection_limit=5&pool_timeout=10. Do not addpgbouncer=true(Prisma Postgres already tracks prepared statements).Prisma ORM 6.19, Next.js on Vercel, a
*/15 * * * *cron whose first statement is:await prisma.$executeRaw
SELECT 1; // then any cheap readsLet a run succeed (opens a server connection, then the instance goes idle).
Wait ~15 minutes with no other traffic on that pooled connection.
Hit the cron again.
Expected: SELECT 1 returns in <1s. Actual: it never returns; function 504s at 300s. Next tick after the kill succeeds.
Workaround that recovered every stalled tick: race the operation against 8s and re-issue the read on a new pooled connection. Writes are not retried.
Environment
- Node: 24.x on Vercel (local 24.13.1)
- OS: Vercel Fluid / iad1 (local macOS 15)
- Package manager: npm
- Database: Prisma Postgres (Vercel Marketplace), pooled host pooled.db.prisma.io, direct host db.prisma.io
- Prisma ORM: 6.19.2 (library engine)
- Framework: Next.js 16.1 on Vercel
- Cron: GET /api/cron every 15 minutes
Additional context
Example recovered tick after our workaround (same stall, then re-issue):
- 2026-09-13 12:00:33 UTC
- deployment dpl_AMCMLbxjapx4phrsxBPLWv4tyEtj
- log: {"evt":"prisma.stall","model":null,"operation":"$queryRaw","attempt":1,"reissue":true,"stallMs":8000}
- then HTTP 200 in 8.6s (was 504 / 300s before the workaround)
Questions:
- What is the Prisma Postgres pooler’s idle / reset behaviour around ~900s?
- Can
socket_timeoutcoverprepare_typed? - Can the pooler reset a ~15-min-idle server connection instead of handing it out half-dead?
Happy to share more Vercel request IDs. Please do not ask for the connection string.
Source: prisma/prisma