← Back to blog

Running a real Postgres app on Multigres, at scale

Benchmarking Multigres connection pooling at scale and proving Postgres compatibility by running Miniflux unchanged.

Connection management is one of the least-scalable parts of Postgres. Because Postgres uses a process-per-connection architecture, production deployments are often fronted by a connection pooler.

Multigres provides a scalable connection pooler natively. Unlike other popular connection poolers, users don't need to choose between different "modes", like Statement Mode or Session Mode.

In this post, we test the scalability of Multigres' connection pooler. We also demonstrate Postgres compatibility using Miniflux, a feature-rich Postgres application.

The full demo:

Compatibility beyond the wire-protocol

There are many scalable databases that are "Postgres-compatible". These databases speak the wire protocol on the front but run their own engine on the back. The protocol gets you connected but the engine is where compatibility breaks down.

Multigres doesn't have its own engine. It is more like an "operating system" around native Postgres, so compatibility isn't an issue. The Multigres gateway speaks the wire protocol end-to-end and forwards to a real Postgres instance.

Testing Postgres compatibility with Miniflux

To demonstrate this compatibility, we searched for an application that uses a variety of advanced Postgres features. We found Miniflux, a self-hosted feed reader.

The Miniflux docs explicitly say it requires Postgres, not "a Postgres-compatible database". It uses extensions, JSON columns for feed metadata, full-text search, LISTEN/NOTIFY, and various other niche Postgres features.

The Miniflux Docker image takes a Postgres connection string. Pointing it at a Multigres cluster works the same way as pointing it at a Postgres instance. Miniflux starts, runs its migrations, creates its tables, and begins fetching feeds. As far as Miniflux is concerned, it is talking to Postgres.

Testing scalability to 16,000 connections

To test scalability in the video demonstration above, we run a tool called SupaFireHose. It's configured for 20,000 concurrent connections, high read and write QPS, and a churn rate of 2,000 connections per second. The demonstration runs on a single MacBook.

The connection count climbs and settles in the 16,000 to 18,000 range. The limit is the laptop, not Multigres. The gateway could accept more, but the kernel runs out of file descriptors.

We felt it was important to include connection churn to mimic real workloads that open, use, drop, and reopen connections.

Most poolers impose a hard limit on client connections. Once that limit is reached, new connections are rejected. In contrast, the Multigres gateway absorbs the churn of client connections while the pooler keeps the backend connection set stable, and the application never notices.

Designing a scalable connection pooler

Multigres splits connection pooling into two distinct jobs:

  1. MultiGateway is the tier that accepts client connections. It scales horizontally: connection capacity grows by adding gateways, not by tuning a single process. That is where the large connection counts come from.
  2. MultiPooler sits behind MultiGateway and runs next to every Postgres instance. It manages the real Postgres connections - a small, stable set that client connections are multiplexed onto.

The gateway gives you the connections and the pooler keeps the backend side small. Both are managed by the same Multigres machinery that is responsible for replication and failover.

Pooling as a property of the cluster

In Multigres, connection pooling is a property of the cluster, not a service bolted on in front of it. Each Postgres instance has its own MultiPooler, co-located with it and registered in the same topology as the rest of the cluster.

This pooler also supervises the Postgres instance. If Postgres goes down, the pooler is the component that notices and brings it back, through pgctld. MultiOrch handles failover across instances, and the gateway watches the topology - when the primary moves, the gateway routes to whichever pooler now owns it. The application keeps talking to the gateway and is unaware that anything has changed.

Technical Deep Dive

How the gateway and pooler are built, and what the pooler does once it owns the connections, are their own topics. Earlier posts go deep on each piece: