Why Your Vercel Functions Are Stuck in One Region
Vercel Functions support 19 regions. You can deploy a Next.js API route to São Paulo, Tokyo, or Frankfurt. The compute layer is global.
In practice, most Vercel projects deploy functions to whichever region matches their database. PostgreSQL on us-east-1? Functions go to iad1. Database in eu-west-1? Functions go to dub1. The Vercel docs say it directly: "configure your function region to match your database region." Compute follows data.
This means the global serverless infrastructure that Vercel built goes unused. A user in Singapore hits your API, the request flies to Virginia, queries the database across localhost, and flies back. The function is fast. The network is slow. Your P95 latency is 400ms+ and there's nothing in your code to fix.
Fluid compute is great, but not global
Vercel's original attempt at solving this was Edge Functions: run JavaScript globally, in the region closest to the user. The idea was right. The execution was limited. The edge runtime lacked full Node.js compatibility, had a restricted API surface, and couldn't hold persistent TCP connections to databases. A function running closer to the user still had to cross the ocean to reach PostgreSQL.
Vercel moved away from Edge Functions and built Fluid compute. Fluid compute is genuinely good infrastructure: full Node.js compatibility, standards-based, persistent connections, scale to one, longer execution times. The engineering is a clear improvement over the edge runtime.
But Fluid compute's default model runs your functions "closer to where your data lives." The direction changed. Instead of distributing compute to users, the default optimizes for colocating compute with the database. The data layer problem that limited Edge Functions was never addressed. It just stopped being the focus.
The connection tax
Even when your function is colocated with your database, serverless PostgreSQL connections are expensive.
Each new connection requires:
- TCP handshake: 1 round trip
- TLS negotiation: 1-2 round trips (TLS 1.3 is 1 RTT; TLS 1.2 is 2)
- PostgreSQL startup message: 1 round trip
- Authentication (SCRAM-SHA-256): 2 round trips (client-first, server-challenge, client-response, server-final)
Five to six round trips before your first query executes. When function and database are in the same region, each round trip is sub-millisecond, so this costs ~5ms. Tolerable.
When they're in different regions, the math breaks. us-east-1 to ap-southeast-1 (Singapore) is ~150ms per round trip. That's 750ms+ of connection overhead before a single row is returned. Your API endpoint takes 800ms to serve 50 rows that PostgreSQL produces in 2ms.
Vercel Functions can reuse connections across invocations when instances stay warm. But this is an optimization, not a guarantee. Under load, new instances spin up with cold connections. During traffic spikes, most connections are new.
Connection exhaustion
The problem compounds under concurrency. Each function invocation opens its own connection to PostgreSQL. 200 concurrent users means 200 simultaneous connections. Most managed PostgreSQL instances (RDS, Cloud SQL, Supabase) cap at 100-500 connections.
Serverless compute scales to thousands of instances. PostgreSQL was designed for a fixed number of long-lived clients. The mismatch is architectural: you hit too many connections errors during the exact moments your application needs to scale.
The solutions that exist lock you in
Cloudflare Hyperdrive is the closest thing to a real solution. It does connection pooling and query caching at the wire protocol level, globally distributed, with support for any PostgreSQL database. The engineering is solid. But it only works from Cloudflare Workers. If you're on Vercel, Lambda, Railway, or anything else, it's not available.
Neon's serverless driver replaces TCP with WebSocket/HTTP connections, eliminating the 4-RTT handshake. But it only works with Neon databases. If you're on RDS, Cloud SQL, Supabase, or self-hosted PostgreSQL, it's not an option. It also doesn't solve the global problem: your queries still travel to whichever single region Neon hosts your database.
Prisma Accelerate provides connection pooling and caching, but requires the Prisma ORM. Teams using Drizzle, Knex, Kysely, or raw pg can't use it.
Self-hosted PgBouncer works with any client, but runs in a single region, doesn't cache queries, and adds operational overhead you're using serverless to avoid.
Each approach either locks you into a compute platform, a database provider, a specific ORM, or a single region. None of them solve the actual problem: making your data layer globally reachable without vendor lock-in.
The missing layer
The problem was never that compute couldn't be global. Vercel solved that. The problem is that PostgreSQL is a single-region service, and everything between your function and your database (connection setup, query execution, result transfer) pays the cost of that distance.
The fix is a proxy layer that:
- Runs in multiple regions, close to where functions execute
- Maintains persistent, authenticated TLS connections to the upstream database
- Pools connections so thousands of function invocations share a small number of database connections
- Caches read query results regionally so repeated reads never cross the ocean
PgBeam is that layer. It speaks the PostgreSQL wire protocol natively. No SDK, no ORM adapter, no driver swap. You change one environment variable:
# Before: direct to RDS in us-east-1
DATABASE_URL=postgresql://user:pass@mydb.us-east-1.rds.amazonaws.com:5432/mydb
# After: through PgBeam
DATABASE_URL=postgresql://user:pass@abc.gw.pgbeam.app:5432/mydbYour application code stays identical:
// Prisma
const users = await prisma.user.findMany();
// Drizzle
const users = await db.select().from(usersTable);
// Raw pg
const { rows } = await pool.query("SELECT * FROM users");Warm connections change the math
Latency-based DNS resolves your PgBeam hostname to the nearest proxy node. When a Vercel Function connects, it completes the PostgreSQL handshake locally in single-digit milliseconds. The proxy already holds open, authenticated TLS connections to your upstream database. The expensive cross-region connection setup is paid once per upstream pool connection and shared across thousands of function invocations.
This matters even for writes and cache misses. A function in Mumbai (bom1) querying a database in us-east-1:
- Direct: 5-6 round trips at ~180ms each for connection setup alone. Total with query: ~1,484ms.
- Through PgBeam (cache miss): local connection to PgBeam Mumbai (~5ms), query forwarded over PgBeam's existing upstream connection. Total: ~332ms.
- Through PgBeam (cache hit): served from PgBeam's Mumbai cache. Total: ~10ms.
Even without caching, warm upstream connections cut Mumbai-to-Virginia latency by 4.5x. With caching, it's 148x. These are real measurements from 20 Vercel regions.
PgBeam eliminates connection setup overhead, not the speed of light. Writes and cache misses from Mumbai still cross the ocean, but they do it over an already-open connection instead of establishing a new one from scratch on every request.
How the pool works
PgBeam's default mode is transaction pooling. A function acquires an upstream connection for the duration of a transaction (or a single query outside a transaction), then returns it. 200 concurrent function invocations share 10-20 upstream connections, keeping your database's connection count stable during traffic spikes.
When a connection returns to the pool, PgBeam checks whether the session was modified: SET statements, PREPARE, temporary tables, LISTEN/NOTIFY. Clean sessions go back immediately. Dirty sessions get a DISCARD ALL before returning. This matters because most serverless queries are stateless CRUD operations that don't touch session state. If your application relies on prepared statements or session variables, session mode is also available, where each client gets a dedicated upstream connection for the lifetime of the session.
The pool maintains separate connection groups per credential pair. Multi-tenant setups where different services use different database users work without configuration.
If the upstream database goes down, a circuit breaker trips after consecutive connection failures and backs off exponentially. New connections fail immediately instead of hanging for TCP timeouts. When the database recovers, a probe connection succeeds and the circuit closes.
Query caching for reads that don't need to cross regions
PgBeam caches query results at the proxy layer. When enabled, repeated reads are served from a regional cache without hitting the upstream database.
You control caching per query with SQL comments. Product catalogs and feature flags get cached. Transactional queries always hit the database. The cache uses stale-while-revalidate: when an entry expires, the first request triggers a background refresh while still serving the stale result. This avoids thundering herd on popular queries.
The cache is TTL-based. A write in one region does not immediately invalidate caches in other regions. If you cache a query with a 60-second TTL, reads in other regions may return stale data for up to 60 seconds after a write. For queries where this matters, don't cache them.
Global routing unlocks global functions
PgBeam runs in 6 regions today: us-east-1, us-west-2, eu-west-1, ap-south-1, ap-southeast-1, and ap-northeast-1. These 6 were chosen to maximize global coverage across Vercel's 19 function regions. Latency-based DNS routes each connection to the nearest proxy.
This is what makes global Vercel Functions practical. Deploy your functions to multiple regions. Each function connects to the nearest PgBeam node. Cached reads resolve locally. Writes and cache misses route through PgBeam's warm upstream connections to the origin database, still faster than a cold connection from the function directly.
Real numbers from our benchmark page, where 20 Vercel Functions (one per Vercel region) query a PostgreSQL database in us-east-1:
| Vercel region | Direct | PgBeam (miss) | PgBeam (hit) | Proxy region |
|---|---|---|---|---|
iad1 Washington, D.C. | 222ms | 159ms | 13ms | us-east-1 |
pdx1 Portland | 581ms | 214ms | 11ms | us-west-2 |
dub1 Dublin | 646ms | 217ms | 10ms | eu-west-1 |
hnd1 Tokyo | 1,186ms | 279ms | 11ms | ap-northeast-1 |
bom1 Mumbai | 1,484ms | 332ms | 10ms | ap-south-1 |
sin1 Singapore | 1,782ms | 377ms | 10ms | ap-southeast-1 |
gru1 São Paulo | 1,003ms | 553ms | 74ms | us-east-1 |
syd1 Sydney | 1,566ms | 1,019ms | 567ms | ap-southeast-1 |
Regions with a nearby PgBeam proxy node (top 6) see the largest gains. Regions without one (São Paulo, Sydney) still benefit from connection pooling but the cache-hit latency reflects the distance to the nearest proxy. All 20 regions are available on the benchmark page.
Your function is no longer stuck in iad1. The database is still in one region, but the data layer is global.
No lock-in
PgBeam works with any PostgreSQL database, any ORM, and any compute platform. If you move from Vercel to Lambda, or from RDS to Supabase, the connection string stays the same. No SDK to remove, no driver to swap, no code to change.
Try it
Sign up at dash.pgbeam.com, add your database, swap the host in your .env, and deploy. Check the live benchmarks to see latency numbers from 20 Vercel regions running against a real PostgreSQL database.