PostgreSQL memiliki max_connections default 100. Aplikasi NestJS tipikal dengan Prisma atau TypeORM membuka pool koneksi dengan default maksimum 10 koneksi per proses. Itu terdengar baik — sampai Anda menjalankan 20 proses Node.js (load balanced), masing-masing membuka 10 koneksi, dan Anda berada di 200 koneksi. PostgreSQL mulai menolak koneksi. Ini adalah salah satu kegagalan produksi yang paling umum saya lihat di stack Node.js/PostgreSQL.
Setiap koneksi PostgreSQL memerlukan proses backend khusus (bukan thread — proses) di server. Membuat koneksi baru melibatkan forking proses OS baru, yang membutuhkan 5-30ms dan mengalokasikan memori untuk backend. Dengan 100 koneksi, PostgreSQL mengelola 100 proses OS, masing-masing mengonsumsi sekitar 5-10MB memori.
Library seperti `pg` (node-postgres), Prisma, dan TypeORM memelihara pool koneksi tingkat aplikasi. Pool menjaga serangkaian koneksi yang telah dibuat tetap terbuka dan menyerahkannya ke kueri sesuai kebutuhan. Ini menghilangkan overhead pembuatan koneksi per-permintaan. Pool memiliki ukuran minimum dan maksimum. Ini bekerja dengan baik untuk satu proses, tetapi rusak ketika Anda skala secara horizontal.
Dalam produksi, Anda biasanya menjalankan beberapa proses Node.js — minimal satu per inti CPU. Setiap proses memiliki pool koneksinya sendiri. Jika Anda memiliki 8 inti CPU menjalankan NestJS dan masing-masing memiliki ukuran pool maksimum 20, itu 160 koneksi ke PostgreSQL hanya dari aplikasi Anda. Solusinya adalah menempatkan connection pooler antara aplikasi Anda dan PostgreSQL yang mengelola koneksi database aktual secara terpusat.
WITHOUT PgBouncer (dangerous at scale):
─────────────────────────────────────────────
NestJS Process 1 ──── 20 connections ────┐
NestJS Process 2 ──── 20 connections ────┼──► PostgreSQL (max 100)
NestJS Process 3 ──── 20 connections ────┤ OVERLOADED at 60+ procs
...8 processes ──── 20 x 8 = 160 ─────┘
WITH PgBouncer (production-ready):
─────────────────────────────────────────────
NestJS Process 1 ── 2 conns ──┐
NestJS Process 2 ── 2 conns ──┤
NestJS Process 3 ── 2 conns ──┼──► PgBouncer ──► PostgreSQL (10 conns)
...8 processes ── 2 x 8 = 16┘ Pool: 10 max_connections: 100
(80% headroom for other services)
PgBouncer pool_size formula:
optimal = (CPU_cores * 2) + number_of_disks
For 4-core server: (4 * 2) + 1 = 9 → round to 10Dari pengalaman saya menjalankan PostgreSQL di produksi: atur ukuran pool koneksi Prisma ke `DATABASE_URL` dengan `?connection_limit=2&pool_timeout=10` per proses ketika Anda menggunakan PgBouncer dalam mode transaksi. Dengan PgBouncer menangani koneksi PostgreSQL aktual, setiap proses aplikasi hanya membutuhkan 1-2 koneksi bersamaan ke PgBouncer. Formula: PgBouncer pool_size = (PostgreSQL max_connections * 0,8) / jumlah_database.
PgBouncer adalah connection pooler ringan untuk PostgreSQL, ditulis dalam C. Ini duduk antara aplikasi Anda dan PostgreSQL, menerima koneksi aplikasi dan memultipleks mereka atas sejumlah koneksi server PostgreSQL yang lebih kecil. Kunci konfigurasi: pool_mode (sesi, transaksi, atau pernyataan), max_client_conn, default_pool_size, dan min_pool_size.
PgBouncer menawarkan tiga mode pooling. Session pooling menetapkan koneksi PostgreSQL untuk durasi sesi klien — hampir setara dengan tanpa pooling. Transaction pooling menetapkan koneksi hanya untuk durasi transaksi, lalu mengembalikannya ke pool — ini adalah mode yang memberikan efisiensi koneksi 10-50x. Gunakan transaction pooling kecuali aplikasi Anda menggunakan fitur tingkat sesi PostgreSQL seperti advisory locks atau prepared statements lintas transaksi.
# docker-compose.yml — PgBouncer sidecar
services:
app:
image: my-nestjs-app
environment:
# Connect to PgBouncer, not PostgreSQL directly
DATABASE_URL: postgresql://user:pass@pgbouncer:5432/mydb?pgbouncer=true&connection_limit=2
depends_on:
- pgbouncer
pgbouncer:
image: edoburu/pgbouncer:latest
environment:
POSTGRES_HOST: db
POSTGRES_PORT: 5432
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
POOL_MODE: transaction # key: transaction pooling
MAX_CLIENT_CONN: 100 # app connections to PgBouncer
DEFAULT_POOL_SIZE: 10 # PgBouncer → PostgreSQL connections
MIN_POOL_SIZE: 2 # keep warm
RESERVE_POOL_SIZE: 5 # burst capacity
SERVER_IDLE_TIMEOUT: 600
ports:
- "5432:5432"
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
command: postgres -c max_connections=100
# Monitor PgBouncer:
# psql -h localhost -p 5432 -U pgbouncer pgbouncer
# SHOW POOLS; -- check cl_waiting (should be 0)
# SHOW STATS; -- check avg_query_timeUntuk aplikasi NestJS yang dikontainerisasi, pengaturan PgBouncer paling sederhana adalah container sidecar di Docker Compose service atau Kubernetes pod yang sama. Gunakan image `edoburu/pgbouncer` yang menerima konfigurasi melalui variabel environment — tidak perlu file INI. Tidak ada perubahan kode aplikasi yang diperlukan.
Jika ORM Anda menggunakan prepared statements (TypeORM melakukan ini secara default; Prisma menggunakan named queries yang berperilaku serupa), Anda akan mengalami error dalam mode transaction pooling PgBouncer. Perbaikan untuk Prisma adalah menambahkan `?pgbouncer=true` ke DATABASE_URL. Selalu uji kompatibilitas PgBouncer ORM Anda sebelum deploy — error prepared statement di produksi sangat halus dan sulit didebug.
PgBouncer mengekspos database virtual bernama `pgbouncer` yang dapat Anda hubungkan dengan psql. Perintah SHOW POOLS menampilkan koneksi klien saat ini, koneksi server, dan antrean tunggu untuk setiap pool. Pantau `cl_waiting` > 0 — ini berarti koneksi aplikasi menunggu koneksi server PgBouncer, menunjukkan pool Anda habis.
PgBouncer bukan satu-satunya pilihan. Supavisor (connection pooler Supabase, ditulis dalam Elixir) mendukung transaction dan session pooling dengan UI web. Neon (PostgreSQL serverless) memiliki connection pooling bawaan. RDS Proxy (AWS) adalah pooler terkelola untuk Aurora dan RDS PostgreSQL. Untuk pengaturan sederhana di VPS tunggal, PgBouncer sebagai sidecar adalah opsi overhead terendah.