Question

Supabase 'remaining connection slots are reserved' but only since deploying to Vercel

Solved · 348 viewsasked by wendy_l

Locally fine. On Vercel, after a few minutes of traffic (maybe 30 users), I get:

PrismaClientInitializationError: FATAL: remaining connection slots are reserved for non-replication superuser connections

My DATABASE_URL is the connection string from Supabase settings, port 5432. Claude Code set up Prisma with a singleton in lib/db.ts so I thought connections were reused.

What I’ve tried

Checked the singleton, it is there. Restarted the Supabase project, works for a few minutes then the error comes back.

Comment
Which connection string exactly, the direct one (db.xxx.supabase.co:5432) or a pooler one? hannah_reyes

3 answers

Marked as helpful by the asker
hannah_reyes

The singleton helps within one function instance, but on Vercel you have many instances, each holding its own connections, straight to Postgres on 5432. Small Supabase instances only allow a limited number of direct connections, so 30 users can fill them.

Use the pooler for the app and keep the direct connection for migrations only:

# .env (Vercel)
DATABASE_URL="postgresql://postgres.abcd:[pw]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1"
DIRECT_URL="postgresql://postgres.abcd:[pw]@aws-0-eu-central-1.pooler.supabase.com:5432/postgres"
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}

Port 6543 is transaction mode: connections go back to the pool after each transaction. pgbouncer=true stops Prisma from using prepared statements, which transaction mode does not support.

Comment
Switched, redeployed, 2 hours of traffic and zero errors. What does connection_limit=1 do exactly? wendy_l
Each function instance opens at most 1 connection to the pooler. Serverless instances handle one request at a time anyway, so more would just be idle. hannah_reyes
Bookmarking this, the pgbouncer=true part bit me last month with 'prepared statement already exists'. pawel_z
postgres_pete

To see who is holding connections before and after the change, run this in the SQL editor. This is what it looked like on a project of mine with the same setup:

If you see dozens of idle connections from the same app, that's serverless instances each keeping their own. After switching to the pooler they should mostly disappear from this list.

Comment
Before: 58 idle from prisma. After: 4. Very satisfying query. wendy_l
jb_supa

If you use supabase-js (not Prisma) for most queries, those go over HTTP through the Supabase API and don't hold Postgres connections at all. Only the direct Postgres clients need the pooler setup.

Comment