Skip to main content
BlogWeb Dev

Connection Pooling Database di NestJS: PgBouncer dan Mengapa Anda Membutuhkannya

Cara kerja connection pooling, mengapa aplikasi NestJS Anda akhirnya akan menghabiskan koneksi PostgreSQL, dan konfigurasi PgBouncer yang saya jalankan di produksi.

Matthews Wong

Software Engineer

Juni 202510 menit baca
Web Dev

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.

Mengapa Biaya Koneksi PostgreSQL Tinggi

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.

Cara Kerja Application-Level Pooling

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.

Masalah Multi-Proses

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 10

Dari 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: Connection Pooler

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.

Transaction vs Session Pooling

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_time

Deploy PgBouncer dengan Docker

Untuk 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.

Memantau Kesehatan Connection Pool

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.

Alternatif: Supavisor dan Built-In Pooling

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.

Sumber & Bacaan Lebih Lanjut

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Mengapa aplikasi NestJS saya kehabisan koneksi PostgreSQL saat di-scale secara horizontal?

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.

Apa itu PgBouncer dan mengapa transaction pooling adalah mode yang direkomendasikan?

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.

Mengapa prepared statements bermasalah dengan PgBouncer dalam mode transaction pooling, dan bagaimana cara memperbaikinya?

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.

Pengaturan koneksi Prisma apa yang sebaiknya digunakan saat menjalankan PgBouncer di produksi?

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.

Bagaimana cara memantau apakah pool PgBouncer sudah habis?

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.