Supabase Supavisor: when the pooler saves you and when it does not
Supavisor multiplexes thousands of serverless connections into a small Postgres pool, but it adds latency and breaks prepared statements. Here is when it fits.
Supavisor is Supabase's connection pooler written in Elixir, designed to multiplex thousands of short-lived client connections into a small pool of real Postgres connections. It solves a specific problem: serverless functions open and discard database connections faster than Postgres can accept them, and a 60-connection ceiling stops a Vercel deployment dead under load.
It is not a free upgrade. Every query through Supavisor costs roughly 2ms of extra latency compared to a direct connection or to PgBouncer co-located with the database, and transaction mode disables features your ORM may quietly rely on. The right answer depends on where your code runs and how long your sessions live.
What Supavisor actually does
Postgres treats every client connection as a real OS process. A single Supabase project tops out somewhere between 60 and a few hundred direct connections, depending on the compute tier. Open more, and new clients get rejected with too many connections.
A connection pooler sits between your application and Postgres. It maintains a small pool of real backend connections and lends them out to many client connections on demand. When a client finishes a transaction, the pooler returns the backend connection to the pool. The application thinks it has its own connection. Postgres thinks it has one well-behaved client.
Supavisor is Supabase's implementation of this pattern. It uses Elixir/OTP, which is designed for millions of lightweight processes, and Supabase has published benchmarks showing 1 million client connections handled by a single cluster. PgBouncer, by contrast, is single-threaded C, lightweight per client (about 2MB RAM per 1,000 clients) but capped around 10,000 clients per process before CPU becomes the bottleneck.
When Supavisor saves you
1. Serverless functions on Vercel, Netlify, AWS Lambda
This is the canonical case. Every function invocation may open a fresh database connection. Under load, a Next.js app on Vercel routinely produces hundreds of concurrent functions, each wanting a connection. Vercel's own writeup of the problem describes exactly this failure mode: works in development, falls over in production with too many connections errors. Transaction mode through Supavisor on port 6543 is the right default for any function that runs short queries and finishes.
2. Horizontally autoscaling containers
Same shape as serverless but slower to fail. Each replica multiplies the connection count. A 20-replica deployment with a pool of 10 per replica is 200 connections, which is past the limit of every Supabase compute tier under medium. Supavisor lets each replica keep its application-side pool while the database sees a much smaller fan-in.
3. IPv4-only environments
Supabase's direct connection is IPv6-only since the 2024 PgBouncer/IPv4 deprecation. Many CI providers, legacy hosting, and some corporate networks still cannot reach IPv6 addresses. Supavisor on either port speaks both IPv4 and IPv6, which is the cheapest fix short of the IPv4 add-on. Supabase's compatibility doc spells this out.
4. Multi-tenant SaaS with many databases
If you isolate tenants per Postgres database or per schema, Supavisor's per-tenant pool isolation prevents one noisy tenant from draining a shared pool. PgBouncer can do this too but requires manual configuration; Supavisor was built for it.
When Supavisor hurts you
1. Latency-sensitive read paths
Tembo's benchmarks measured Supavisor's latency at 80% to 160% higher than PgBouncer co-located with the database. Peak throughput in their tests was 21,700 transactions per second for Supavisor against 44,096 for PgBouncer. Tembo's writeup is worth reading in full before you assume the pooler is free. For a write-heavy SaaS doing 10 queries per HTTP request, that 2ms-per-query overhead becomes a 20ms response budget you do not get back.
2. Prepared statements in transaction mode
Transaction mode pools backend connections per query, not per session. Prepared statements live on a session, so they break. Supavisor 1.0 added partial support for named prepared statements by broadcasting PREPARE across all backend connections, but ORM authors have reported edge cases that still misbehave under high concurrency. If you use Prisma with transaction mode, you need pgbouncer=true in the connection string to disable prepared statements, or you will get cryptic errors at production load.
3. Session features that silently fail
Transaction mode also breaks SET statements that span multiple queries, advisory locks, LISTEN/NOTIFY, and any temp table that needs to outlive a single transaction. If your application relies on any of these, you need session mode on port 5432, which gives back exclusive connections at the cost of losing the pooling benefit.
4. Long-lived connections you actually want
A background worker that holds a connection to stream NOTIFY events or to keep pg_advisory_lock for a leader election is not a pooling problem. It is a connection that has to exist. Routing it through Supavisor in transaction mode means it never returns the backend connection to the pool, which defeats the point and consumes a pool slot for nothing.
Session mode vs transaction mode after February 2025
On 28 February 2025, Supavisor deprecated session mode on port 6543. Since that date, port 6543 only serves transaction mode, and port 5432 only serves session mode. Connection strings that used to work on 6543 with session-mode behaviour silently broke. Check your existing strings: the port you used last year may not do what you remembered.
The mental model is simple. If your client opens, runs one or a few queries inside a transaction, and disconnects, use 6543 in transaction mode. If your client expects to keep a connection alive across many queries with session state, use 5432 in session mode. There is no third option that splits the difference.
The pool size question
Supabase shares connection budget across Supavisor pools and direct connections. If your compute tier allows 60 backend connections and Supavisor is configured with a pool of 30 in transaction mode plus 30 in session mode, both pools fighting for connections can briefly create 60 backend connections at peak. Add the database's own internal services (Auth, Realtime, Storage, PostgREST) and you can exhaust headroom before your application code ever runs.
Supabase's own guidance is to keep Supavisor's pool under 40% of total backend connections when you also rely on the database REST API, and only push to 80% when you control all consumers. Application-side pool sizing matters too: in Vercel functions, start with connection_limit=1 in your Prisma URL and increase cautiously. Every replica adds to the total.
When to skip Supavisor entirely
Three cases. First, a single long-running Node.js server with its own application pool of, say, 10 connections. It does not need Supavisor; the direct connection is faster and simpler. Second, the Supabase JavaScript client (@supabase/supabase-js) hits PostgREST over HTTP, not the database directly, so it is not part of this conversation at all. Third, an edge runtime that needs fetch-based access, where you use Supabase's data API rather than a Postgres driver.
A pragmatic default for 2026
For a new Next.js SaaS deployed on Vercel with Supabase as the database, the configuration we ship by default looks like this. Transaction mode on port 6543 for the application's main Postgres connection, with connection_limit=1 and pgbouncer=true if Prisma is in the stack. Session mode on port 5432 for migrations and for any worker that needs prepared statements or advisory locks. A direct IPv6 connection only for one-off admin tasks run from a developer's machine. Application code never opens a direct connection from a serverless function. Row-level security still runs at the database, so the latency cost of Supavisor compounds with every policy check.
That setup is not the fastest. PgBouncer co-located on the database would be 80% faster on raw throughput and would cut about 2ms off every query. We accept the trade because Supavisor is what Supabase ships and supports, because the per-tenant pool isolation matters for multi-tenant work, and because the failure mode of running without any pooler in front of a serverless deployment is much worse than 2ms.
The right answer is rarely "always use Supavisor" or "avoid it". It is to know what each port does, what each mode breaks, and where your queries actually run. Connection pooling is plumbing. Choose the wrong one and your too many connections error tells you Monday morning, not at 3am on Saturday.
Sources
- Supavisor: scaling Postgres to 1 million connections (Supabase Blog)
- Connect to your database (Supabase Docs)
- Supavisor FAQ (Supabase Docs)
- Supavisor and connection terminology explained (Supabase Docs)
- Benchmarking PostgreSQL connection poolers: PgBouncer, PgCat and Supavisor (Tembo)
- The real serverless compute to database connection problem, solved (Vercel)
- Supabase and your network: IPv4 and IPv6 compatibility (Supabase Docs)
- Troubleshooting Prisma errors (Supabase Docs)
Frequently asked questions
- Should I always use Supavisor with Supabase?
- No. Use it whenever your application code runs in a serverless or autoscaling environment, or when you need IPv4 connectivity. Skip it for a single long-running Node.js server with a sane in-process pool, for the Supabase JavaScript client (which talks to PostgREST over HTTP, not to Postgres), and for background workers that need persistent session state. The pooler costs about 2ms per query, so it is not free.
- Why does Prisma fail with Supavisor in transaction mode?
- Prisma creates prepared statements in the background, and transaction mode pools backend connections per query rather than per session. Prepared statements live on a single session, so they break when the next query lands on a different backend. Adding pgbouncer=true to the connection string tells Prisma to disable prepared statements. Supavisor 1.0 added partial named prepared statement support, but expect rough edges under high concurrency until your specific Supavisor version is confirmed stable.
- What changed with the February 2025 session mode deprecation?
- Before 28 February 2025, port 6543 served both transaction mode and session mode behaviour depending on configuration. After that date, port 6543 is transaction-only and port 5432 is session-only. Connection strings that used 6543 with implicit session behaviour silently broke. If you are still seeing odd behaviour, audit every connection string in your project and confirm each one targets the right port for the mode you actually want.
- How big should my Supavisor pool be?
- Stay under 40% of total backend connections when the database REST API and other Supabase services share that budget. Push toward 80% only when you control every consumer. On Vercel functions, start the application-side pool at connection_limit=1 per replica and raise it cautiously. Every replica multiplies the total. The combined ceiling of transaction-mode plus session-mode pools can briefly equal the entire backend budget at peak, so size both pools with the database compute tier in mind, not just the workload.
Studio
Start a project.
One partner for the digital product you need to build. Faster delivery, modern tech, lower costs. One team, one invoice.