Connection strings and limits

Reading · 5 min · Module 9, lesson 3 of 410 min left in this module

Module 9 · DatabasesLesson 3 of 4

Goal: Read a connection string, keep it secret, and size connection pools to fit a database's connection limit.

3:57 · captions and chapters · narrated with an AI-generated voice
Transcript

Narration uses an AI-generated voice.

[00:00] Where we're going

By the end of this video, you'll be able to read a database connection string, and keep it secret. And you'll know how to size connection pools to fit the database's limit.

[00:10] Reading one

Your app finds its database through one connection string. Here's one, taken apart. First, the scheme. Which kind of database this is. Then the user, and that user's password. Next, the host and the port. Where the database lives. Then the database name. And last, any options.

[00:33] Where it comes from

Your database provider gives you this string. Apps usually read it from an environment variable, called DATABASE URL. One catch. If the password has an at sign, a colon or a slash, those must be percent-encoded. Left as they are, many parsers split the string in the wrong place, and read part of the password as the host. Encoded, the at sign becomes percent four oh.

[01:01] Keep it secret

Because it holds the password, anyone with the string can read, change or delete your data. So the whole string is a secret. So, three habits. First, never commit it to git, even in a private repository. History keeps it after you delete the line. Second, store it as a secret where your app runs, and read it from the environment. Third, give each environment its own database and string. Then staging can never touch production data. If a string does leak, change the database user's password straight away. Deleting the commit doesn't take it back.

[01:42] Connection pools

Now, the limits. Opening a connection takes a round trip, a TLS handshake and a login. So apps keep a connection pool. A few open connections, borrowed for each query, and handed back. Each open connection costs the database memory. So every database has a connection limit.

Here's the part that's easy to miss. Pools multiply. Each copy of your app keeps its own pool. So four servers, with a pool of ten each, hold forty connections. PostgreSQL allows a hundred by default. Small hosted plans often allow fewer.

[02:23] Over the limit

Go over the limit, and new connections are refused. PostgreSQL says: sorry, too many clients already. Requests fail, even though the database itself is fine. It often happens the moment you add servers for more traffic.

[02:38] Sizing pools

So size pools from the limit. Take what the database allows, minus a few spare. Then divide by the number of app processes that can run at once. With many processes, a pooler can sit in between, so hundreds of app connections share a few real ones.

[02:59] On ComputeSphere

On ComputeSphere, store the connection string as a secret. Name it DATABASE URL, for example. Secrets are write-only. Nobody can read the value back. And each spherelet keeps its own pool, so size it with your spherelet count in mind.

[03:17] Recap

So, a quick recap. One string tells your app where its database is, and how to log in. It's a secret, so keep it out of git. And every pool shares one limit. Size them from it.

Here's a question to check yourself. Your database allows a hundred connections. You run three servers with a pool of thirty each, then scale to four. What happens? Connections are refused. Four times thirty is a hundred and twenty. Next, you'll check what you've learned about databases. I'll see you in the next lesson.

Key idea

Your app finds its database through one connection string, and because it contains the password, the whole string is a secret. Each open connection costs the database memory, so every database has a connection limit, and all your servers share it.

Reading one

Your database provider gives you this string. Apps usually read it from an environment variable called DATABASE_URL, and most database libraries accept it as it is.

If the password contains characters such as @, : or /, they must be percent-encoded in the string: p@ss becomes p%40ss. Left as they are, many parsers split the string in the wrong place and read part of the password as the host.

Keep it out of your code

Anyone with the string can read, change or delete your data. So:

  • never commit it to git, even in a private repository; history keeps it after you delete the line;
  • store it as a secret in the place that runs your app, and read it from the environment;
  • give each environment its own database and string, so staging can never touch production data.

If a string does leak, change the database user's password straight away. Deleting the commit doesn't take it back.

Connection limits

Opening a connection takes a network round trip, the TLS handshake and a login, so apps don't open one per request. They keep a connection pool: a few open connections, borrowed for each query and handed back.

Pools multiply. Each copy of your app keeps its own pool, so 4 servers with a pool of 10 each hold 40 connections before anyone else connects. PostgreSQL allows 100 by default, and small hosted plans often allow fewer.

Go over the limit and new connections are refused. PostgreSQL says sorry, too many clients already, and requests fail even though the database itself is fine. This often happens the moment you add servers to handle more traffic.

Size pools from the limit: divide what the database allows, minus a few spare, by the number of app processes that can run at once.

When there are too many app processes

If you run many app processes, a pooler such as PgBouncer sits between the apps and the database: hundreds of app connections share a few real ones. Some hosted providers include one; its address goes in the connection string instead of the database's.

Check yourself

Your database allows 100 connections. You run 3 servers with a pool of 30 each, then scale to 4 during a traffic spike. What happens?

In the docs