PostgreSQL memiliki max_connections default 100, dan setiap proses Node.js memelihara pool koneksinya sendiri. Jika Anda menjalankan 20 proses NestJS yang di-load balance masing-masing dengan ukuran pool 10, Anda mengonsumsi 200 koneksi — melebihi batas default PostgreSQL dan menyebabkan error 'too many connections'. Akar masalahnya adalah application-level pooling hanya membantu dalam satu proses, bukan lintas beberapa proses.
PgBouncer adalah connection pooler PostgreSQL ringan yang ditulis dalam C, duduk di antara aplikasi dan PostgreSQL untuk memultipleks banyak koneksi aplikasi atas lebih sedikit koneksi server nyata. Transaction pooling menetapkan koneksi PostgreSQL hanya untuk durasi transaksi, lalu mengembalikannya ke pool — memberikan efisiensi koneksi 10–50x dibandingkan session pooling. Session pooling hampir setara dengan tanpa pooling sama sekali, sementara statement pooling terlalu restriktif untuk aplikasi yang menggunakan transaksi.
Dalam mode transaction pooling, PgBouncer dapat merutekan transaksi berbeda dalam sesi klien yang sama ke koneksi PostgreSQL nyata yang berbeda, sehingga named prepared statements yang dibuat di satu koneksi tidak tersedia di koneksi lain. TypeORM menggunakan prepared statements secara default, dan Prisma menggunakan named queries yang berperilaku serupa — keduanya akan mengalami error. Perbaikan untuk Prisma adalah menambahkan '?pgbouncer=true' ke DATABASE_URL, yang menonaktifkan cache named query dan beralih ke simple protocol queries.
Saat menggunakan PgBouncer dalam mode transaksi, atur connection limit Prisma ke 1–2 per proses melalui '?connection_limit=2&pool_timeout=10' yang ditambahkan ke DATABASE_URL, dan tambahkan juga '?pgbouncer=true' untuk menonaktifkan caching prepared statement. Dengan PgBouncer yang menangani koneksi PostgreSQL nyata, setiap proses aplikasi hanya membutuhkan 1–2 koneksi bersamaan ke PgBouncer. Ukuran pool PgBouncer dihitung dengan formula: pool_size = (max_connections PostgreSQL × 0,8) ÷ jumlah_database.
PgBouncer mengekspos database virtual bernama 'pgbouncer' yang dapat dihubungkan dengan psql. Perintah SHOW POOLS menampilkan koneksi klien saat ini, koneksi server, dan antrean tunggu untuk setiap pool. Pantau metrik 'cl_waiting' — nilai lebih dari 0 berarti koneksi aplikasi mengantri menunggu koneksi server yang bebas, yang menandakan ukuran pool perlu ditingkatkan.
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.