Skip to main content

Connections and serverless

A query is cheap; the connection it rides on is not. Getting this wrong is a common reason an app that worked in testing falls over under real load.

Why connections are expensive

Opening a database connection means a TCP handshake, authentication, and - in PostgreSQL - the server starting a whole backend process for that connection. That costs time and memory, and the server caps how many can exist at once (max_connections, often a few hundred). If every web request opens its own connection, a traffic spike exhausts the limit and new requests fail.

Connection pooling

A connection pool keeps a set of connections open and lends them out: a request borrows one, runs its queries, and returns it. No per-request handshake, and the number of real connections stays bounded.

The counter-intuitive part is sizing: bigger is not better. Each connection consumes server memory, and beyond roughly the number of CPU cores extra connections mostly add contention. A small pool (often single or low double digits per app instance) usually gives higher throughput than a large one. The failure mode to know is pool exhaustion - all connections busy, so requests queue and time out; the cause is usually a slow query or a leaked connection that was never returned.

Dedicated poolers like PgBouncer and pgcat sit in front of PostgreSQL and multiplex many clients onto few real connections.

The serverless connection problem

Serverless functions (AWS Lambda, edge functions) scale to many short-lived instances, each wanting its own connection - which blows straight past max_connections. The fixes:

  • an external pooler (PgBouncer, or the platform's built-in pooler) between functions and the database;
  • HTTP / serverless drivers that send queries over HTTP instead of a long-lived TCP connection - e.g. Neon's serverless driver, or data-API layers.

Where databases run in 2026

Stage 0 noted the shift to managed and serverless databases; here is the lay of the land:

  • Serverless Postgres - Neon and Supabase: scale compute to zero when idle, branch a database like git, bill by usage.
  • PlanetScale (MySQL/Vitess) - horizontal scale and safe online schema changes.
  • Amazon Aurora - including Aurora Serverless v2, which scales capacity automatically.
  • Turso (libSQL, a SQLite fork) - databases at the edge, close to users.
  • Distributed SQL - CockroachDB and TiDB offer serverless tiers too.

The common thread is separating storage from compute and paying for what you use - which is also why "relational databases are expensive to run" no longer holds.

Securing the connection: TLS

A connection carries credentials and data across a network. Without encryption, anyone on the path can read or tamper with it. Use TLS for every connection that leaves the host - and in the cloud, that is all of them.

PostgreSQL sets the policy through the sslmode parameter in the connection string:

# weak - encrypts if offered, but does NOT check the server's identity
postgresql://app@db.example.com/shop?sslmode=require

# strong - encrypts AND verifies the certificate chain + hostname
postgresql://app@db.example.com/shop?sslmode=verify-full
require is not enough on its own

sslmode=require encrypts traffic but skips certificate validation, so it does not stop a man-in-the-middle who presents any certificate. verify-full checks the chain and the hostname - use it whenever you can supply the CA root. Managed providers publish their CA bundle for exactly this.

Managing secrets

The connection above contains a password. Never hard-code credentials or commit them to git. A leaked repo is one of the most common breach causes.

The progression, weakest to strongest:

  • Environment variables - the baseline. Code reads process.env.DATABASE_URL; the value is injected at deploy time, never written in source.

    // config is supplied by the environment, not the codebase
    const pool = new Pool({ connectionString: process.env.DATABASE_URL });
  • A secret manager - HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, or Azure Key Vault. The app fetches the credential at startup (or per-request) over an authenticated channel, so the secret never sits in plaintext env files or CI logs.

  • Short-lived, rotated credentials. The strongest option: the secret manager issues a credential that expires in minutes or hours and rotates automatically. A stolen credential is useless soon after. Some platforms go further with IAM-based auth, where the database trusts a cloud identity and no static password exists at all.

Rotation is the point

The reason to centralise secrets is not tidiness - it is rotation. When a credential leaks (or an employee leaves), you want to revoke and reissue everywhere in one action, without a redeploy. Build for that from the start.

Quick quiz

Connections and serverless

6 questions

1Why use a connection pool?

2What pool size tends to give the best throughput?

3Why do serverless functions strain a traditional database?

4What do modern managed/serverless databases (Neon, Aurora Serverless) have in common?

5Which sslmode actually protects against a man-in-the-middle?

6What is the main reason to keep database credentials in a secret manager rather than a config file?

Next up

Caching - shield the database and speed reads with cache-aside, TTLs, and the invalidation problem.