Zruck zum Blog, du Zniachtl
Juli 2026·3 Min. zum LesnVo da KI gmocht

Datenbank-Connection-Pooling vastehn

#databases#backend#performance

A Datenbankverbindung aufzumachen is teuer: a TCP-Handshake, Authentifizierung, manchmoi TLS-Aushandlung - üblicherweise Zehner von Millisekunden, bevor überhaupt a Query lauft. Machst pro Request oane Verbindung auf, zoilst die Kosten jedes Moi, und irgendwann lehnt die Datenbank neiche Verbindungen ab, weil's a hartes Limit hot, wie vül sie gleichzeitig offen hoitn kann. Connection Pooling löst beide Probleme, indem's a Menge bereits geöffneter Verbindungen zur Wiederverwendung bereithoit.

Wos a Pool tatsächlich mocht

A Connection Pool is a feste Menge an Datenbankverbindungen, de für die Lebensdauer deiner Anwendung leben, ned für die Lebensdauer von oam einzelnen Request. Wenn dein Code a Query ausführn muass, borgt er si a Verbindung aus dem Pool, nutzt sie und gibt sie zruck - es wird gor koa Socket auf- oda zugmocht.

const { Pool } = require('pg');

const pool = new Pool({
  host: 'localhost',
  database: 'app',
  max: 10, // maximum number of connections in the pool
  idleTimeoutMillis: 30000,
});

async function getUser(id) {
  const client = await pool.connect();
  try {
    const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
    return result.rows[0];
  } finally {
    client.release(); // return the connection to the pool, don't close it
  }
}

pool.connect() gibt dir a bestehende, untätige Verbindung, wenn oane da is, oda öffnet a neiche (bis max), wenn ned. client.release() is der Teil, den Leit vergessn - lass es weg, und die Verbindung kummt nie zruck in den Pool, so erschöpfst du ihn unter Last.

Warum a unbegrenzter Pool schlimmer is ois koaner

Es is verlockend, max "zur Sicherheit" hoch zu setzn. Das Problem: Deine Datenbank hot a a hartes Verbindungslimit (max_connections in Postgres, oft standardmäßig 100), geteilt über olle Anwendungsinstanzen, de mit ihr redn. Wennst 10 App-Instanzen mit je max: 50 fahrst, kannst 500 Verbindungen bei ana Datenbank anfordern, de nur 100 akzeptiert - und sobois das Limit erreicht is, wird jeder ondere Client abglehnt, a deine eigenen Admin-Tools.

A vernünftige Pool-Größe liegt als Startheuristik für die Datenbank söba eher bei (Anzoi CPU-Kerne × 2) + effektive Spindelzahl, dann aufgeteilt auf so vül Anwendungsinstanzen, wia sich verbinden - ned "so vül nebenläufige Requests, wia i erwart". Mehr Verbindungen, ois die CPU-Kerne von deiner Datenbank wirklich parallel bedienen kinnan, fügen nur Warteschlange dazu, koan Durchsatz.

Der häufigste Pooling-Bug: Beim Fehler ned freigebn

// Bad: if query() throws, the connection is never released
async function getUserBroken(id) {
  const client = await pool.connect();
  const result = await client.query('SELECT * FROM users WHERE id = $1', [id]);
  client.release();
  return result.rows[0];
}

Wenn client.query() wirft, läuft client.release() nie, und die Verbindung bleibt dauerhaft ausglehnt. Machst des oft gnua, schrumpft der Pool still und leise auf null nutzbare Verbindungen, während er von außen völlig gsund ausschaut. Die try/finally-Version oben is die Lösung - finally läuft, egal ob die Query klappt oda wirft.

pool.query() für einzelne Statements nutzn

De meisten Treiber bieten a Abkürzung, de a Verbindung ausleiht, a Query ausführt und sie automatisch freigibt:

const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);

Nutz des für einzelne, unabhängige Queries. Für Transaktionen is es ned geeignet, weil jeder Aufruf von pool.query() auf a ondere Verbindung laufn kann - und a Transaktion macht nur auf oaner bestimmten Verbindung für ihre ganze Dauer Sinn.

Transaktionen brauchn a explizite Verbindung

async function transferFunds(fromId, toId, amount) {
  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    await client.query('UPDATE accounts SET balance = balance - $1 WHERE id = $2', [amount, fromId]);
    await client.query('UPDATE accounts SET balance = balance + $1 WHERE id = $2', [amount, toId]);
    await client.query('COMMIT');
  } catch (err) {
    await client.query('ROLLBACK');
    throw err;
  } finally {
    client.release();
  }
}

Do führt derselbe client BEGIN, beide Updates und COMMIT aus - des hoit sie in oaner Transaktion. pool.query()-Aufrufe mittn einezumischn würd still und leise monche Statements auf ondere Verbindungen, außerhoib der Transaktion, laufn lassn.

Idle-Timeouts und veraltete Verbindungen

Verbindungen, de long untätig dasitzn, kinnan still und leise von der Datenbank, am Load Balancer oda ana Firewall dazwischen abgschnitten wern, ohne dass deine Anwendung des mitkriagt. idleTimeoutMillis sogt dem Pool, Verbindungen, de zu long untätig woarn, proaktiv zu schließn und zu ersetzn, statt zu warten, bis du versuchst, a tote zu nutzn, und mittn im Request a verwirrenden Fehler kriagst.

const pool = new Pool({
  max: 10,
  idleTimeoutMillis: 30000, // close idle connections after 30s
  connectionTimeoutMillis: 5000, // fail fast if no connection is available within 5s
});

connectionTimeoutMillis zählt genauso: Ohne ihn hängt a Request, der ka Verbindung kriagt, weil der Pool erschöpft is, ewig, statt schnell mit ana klaren Fehlermeldung zu scheitern.

Serverless ändert die Rechnung komplett

In am klassischen langlaufenden Server lebt oa Pool für die ganze Lebensdauer vom Prozess, was genau dem entspricht, wofür Pooling gedacht is. In Serverless (Lambda, Cloud Functions) kann jede Invocation an frischen Prozess hochfahrn, und pro Invocation an vollen Pool anzulegen macht den Sinn zunichte - du zoilst wieda ständig den Verbindungs-Overhead, und a Schwall gleichzeitiger Invocations kann jede ihren eigenen Pool öffnen und gemeinsam das Verbindungslimit der Datenbank sprengen. Die übliche Lösung is a externer Pooler (PgBouncer, RDS Proxy), der zwischn deine Funktionen und die Datenbank sitzt und vüle kurzlebige Clients auf a klane Zoi echter Datenbankverbindungen multiplext.

Fazit

A Connection Pool tauscht die Kosten, pro Request a Verbindung zu öffnen, gegn a feste, wiederverwendete Menge bereits offener Verbindungen - aber er is nur so gut wia seine Dimensionierung und seine Disziplin beim Freigebn. Dimensionier ihn nach dem, wos die Datenbank wirklich bedienen kann, gib imma in ana finally frei, und hoit Transaktionen an oaner einzigen expliziten Verbindung fest.

NeicherA praktischer Leitfodn zur API-PaginierungElterDeine erste GitHub Action schreibn