Skip to content

What a Managed Postgres Changes for Laravel

Serverless Postgres is priced and shaped for request-per-connection workloads. Laravel holds long-lived connections in its queue workers, and that mismatch is where the surprises are.

5 min read

Managed Postgres used to mean a server somebody else patched. It increasingly means a platform: storage separated from compute, connections brokered by a pooler, databases that branch like git, and compute that suspends when nobody is asking.

All of that is built for a workload where a request arrives, runs a query and goes away. Laravel is that workload on the web tier and the opposite of it everywhere else.

The shape mismatch, stated plainly

A Laravel application has three kinds of database client and they behave completely differently:

Web requests. PHP-FPM, connect and disconnect per request. This fits the serverless model exactly.

Queue workers. queue:work connects at boot and holds that connection for its entire life, whether or not it is processing anything. Twenty workers are twenty permanent connections before a single visitor arrives.

The scheduler. A cron entry running schedule:run every minute, forever.

Providers price and size around the first one. The second is what fills a connection allowance, and it is the most common cause of a connection-limit error. The third is what quietly defeats scale-to-zero.

Scale-to-zero and the scheduler

This is the one people get wrong first and it is arithmetic rather than opinion.

Compute suspends after some idle period. schedule:run issues a query every sixty seconds. If the idle threshold is longer than a minute, the database never suspends and you are paying for always-on compute with cold-start characteristics layered on top.

There are three honest responses. Accept it and size for always-on, in which case a conventional instance may be cheaper. Move the scheduler to something that does not touch the database when there is nothing due - which means the schedule definition has to be readable without a query. Or accept cold starts on a staging environment and not on production, which is where scale-to-zero genuinely pays.

Nobody should discover this from an invoice.

Pooling modes, which is the one that breaks things

Every serverless Postgres puts a pooler in front. The mode decides what your application can do.

Session pooling hands you a real backend connection for the life of your connection. Everything works. You also get far fewer effective connections, which is usually why you came.

Transaction pooling hands you a backend only for the duration of a transaction. This is what gives you thousands of client connections over a small number of real ones, and it removes anything that spans statements:

  • Server-side prepared statements, which PDO uses by default. This is the one that bites Laravel specifically, and the fix is a connection-string flag or an emulation setting rather than a code change - but it has to be set deliberately.
  • Advisory locks, which means withoutOverlapping() on a schedule and ShouldBeUnique on a job may not behave as written if they are backed by the database.
  • SET statements, so anything that sets a timezone or a search path per connection stops holding.
  • LISTEN/NOTIFY, and any long-lived cursor.

The practical arrangement is two connections in config/database.php: the pooled endpoint for web requests, the direct endpoint for migrations, queue workers and anything holding a lock. That is ten lines and it prevents most of this article.

Branching, which is the genuinely new capability

A branch is a copy-on-write database at production scale, created in seconds. For Laravel that lands on a specific problem: migrations that are safe against forty rows and lock for four minutes against four million.

A branch per pull request, migrations run against it in CI, the timing recorded. Now a dangerous schema change fails a build instead of a deploy. This is the strongest argument for a platform over an instance, and it is worth more than the pricing model.

Choosing between them

A conventional managed instance - RDS, Cloud SQL, a provider's plain Postgres - when the application has a steady worker fleet, when you want predictable connection counts, and when the ops overhead you are buying away is patching rather than scaling. This is still the right answer for most business applications and it is unfashionable to say so.

Serverless Postgres with branching when the migration-testing story matters to you, when environments are numerous and short-lived, or when traffic is genuinely spiky. Budget the pooling work rather than assuming it is a connection-string change.

A platform with auth and storage attached when you are using those too. If you are using it only as a database, you are choosing a Postgres provider and should compare it as one.

Anything MySQL-compatible but sharded only with the constraint list read first. Referential integrity, transactions that cross shards, and ORDER BY across a sharded table are where the model shows through, and Laravel migrations will happily write DDL that the platform accepts and does not enforce.

The check that costs an afternoon

Before committing, run the real thing: a migration with an index build on a production-sized table, your actual worker count connected at once, and a scheduled command holding a lock. Three measurements, one afternoon.

Every problem in this article is cheap to find that way and expensive to find in the second month.

Where the provider's regions run is not only a latency question. New Zealand has a rule about sending data offshore that applies to us as much as to you. Kuwait classifies the data first and picks hosting per table afterwards. In both, the region list belongs in the schema conversation.

Related questions

Does scale-to-zero work with a Laravel application?
Rarely, and for a reason that is easy to miss: the scheduler runs every minute. A `schedule:run` cron entry is a query every sixty seconds, which means the compute never idles long enough to suspend. You get the cold-start behaviour without the saving unless you deliberately arrange for quiet periods.
Do we still need a pooler?
Almost certainly, and the important part is which mode. Transaction pooling is the one that gives you the connection count, and it breaks anything that assumes a session - advisory locks, `SET` statements, `LISTEN/NOTIFY`, and server-side prepared statements, which PDO uses by default. Session pooling keeps those and gives back much less.
Is database branching worth it?
For testing migrations against production-shaped data, yes, and it is the feature most worth having that a conventional instance cannot give you. A branch per pull request means a destructive migration is caught by CI on real row counts rather than against a seeded table with forty rows.
What about PlanetScale and other Vitess-style platforms?
They are MySQL-compatible rather than Postgres, and historically the significant constraint was foreign keys - the sharding model makes cross-shard referential integrity expensive, and support has changed over time. Check the current behaviour before writing `constrained()` in a migration, because a foreign key that is accepted and not enforced is worse than one that is refused.

← Back to all articles

Call us+1 848 272 7583WhatsApp+90 850 308 5436Emailinfo@codefacture.comContact page