Lonjakan trafik mendadak pada aplikasi Nuxt 3 sering mengungkap celah kritis pada manajemen koneksi database di layer server (Nitro). Gejala yang paling umum adalah lonjakan status HTTP 500 dengan pesan error seperti FATAL: remaining connection slots are reserved for non-replication superuser connections atau Connection terminated unexpectedly.
Root Cause: Instansiasi Client di Dalam Event Handler
Penyebab utama pool exhaustion pada runtime Nitro adalah pembuatan instance koneksi baru di dalam fungsi defineEventHandler. Berbeda dengan server Node.js monolitik tradisional yang menginisialisasi koneksi satu kali saat proses *bootstrapping*, handler Nitro dieksekusi secara independen setiap kali menerima request masuk.
Perhatikan pola anti-pattern berikut:
// server/api/users.get.ts - ANTI-PATTERN
import { Pool } from 'pg'
export default defineEventHandler(async (event) => {
// Kesalahan: Pool baru dibuat pada setiap HTTP request
const pool = new Pool({
connectionString: process.env.DATABASE_URL
})
try {
const { rows } = await pool.query('SELECT id, name FROM users')
return rows
} finally {
// Menutup pool per request menambah latency tinggi (TCP handshake berulang)
// Jika tidak ditutup, koneksi akan menggantung hingga DB kehabisan slot
}
})Jika sebuah pool dikonfigurasi dengan batas default 10 koneksi, maka 20 request konkuren akan memicu Nitro untuk membuka hingga 200 koneksi ke PostgreSQL. Hal ini langsung melampaui limit default PostgreSQL (biasanya 100 koneksi).
Alur Investigasi: Identifikasi Masalah via Database dan Log
Sebelum melakukan refactoring, validasi apakah koneksi memang terkuras akibat request HTTP yang menggantung.
1. Monitoring Koneksi Aktif di PostgreSQL
Jalankan query berikut di PostgreSQL untuk melihat jumlah dan status koneksi yang sedang berjalan:
SELECT state, count(*)
FROM pg_stat_activity
WHERE datname = current_database()
GROUP BY state;Jika koneksi berstatus idle terus meningkat seiring bertambahnya request tanpa pernah turun kembali ke angka dasar, ini mengonfirmasi adanya kebocoran koneksi (*connection leak*) dari aplikasi.
2. Memeriksa Nilai Limit Koneksi
SHOW max_connections;Bandingkan output query tersebut dengan total koneksi yang tercatat di pg_stat_activity saat pengujian lokal atau staging.
Solusi: Inisialisasi Singleton via Nitro Plugin
Pendekatan yang benar adalah membuat satu instance *connection pool* saat server Nitro mulai berjalan (*lifecycle start*), lalu menyuntikkan (*inject*) pool tersebut ke dalam context request (event.context).
1. Buat Nitro Plugin
File di dalam direktori server/plugins/ akan otomatis dieksekusi sekali saat runtime server aktif.
// server/plugins/db.ts
import pg from 'pg'
const { Pool } = pg
export default defineNitroPlugin((nitroApp) => {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Batasi jumlah koneksi maksimal dalam pool
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000
})
// Pasang pool ke context setiap request masuk
nitroApp.hooks.hook('request', (event) => {
event.context.db = pool
})
// Tutup pool secara bersih saat server shutdown
nitroApp.hooks.hook('close', async () => {
await pool.end()
})
})2. Konfigurasi Type Definition (TypeScript)
Agar event.context.db memiliki autocompletion dan tipe yang valid, tambahkan deklarasi modul berikut:
// server/types/nitro.d.ts
import type { Pool } from 'pg'
declare module 'h3' {
interface H3EventContext {
db: Pool
}
}3. Konsumsi Database Client di Handler
Gunakan pool yang sudah disediakan pada event.context tanpa menginisialisasi ulang driver:
// server/api/users.get.ts - PATTERN BENAR
export default defineEventHandler(async (event) => {
const pool = event.context.db
const { rows } = await pool.query(
'SELECT id, name, created_at FROM users LIMIT 50'
)
return rows
})Verifikasi Stabilitas Menggunakan Autocannon
Lakukan uji beban (*load test*) lokal menggunakan autocannon untuk membuktikan bahwa pool tetap stabil di bawah tekanan konkurensi tinggi.
Jalankan perintah berikut pada endpoint yang telah diperbaiki:
npx autocannon -c 50 -d 15 http://localhost:3000/api/usersPerintah ini menjalankan 50 concurrent connection selama 15 detik. Periksa kembali monitoring PostgreSQL selama tes berjalan:
SELECT count(*) FROM pg_stat_activity WHERE datname = current_database();Hasil yang diharapkan:
- Jumlah koneksi tidak akan melebihi parameter
max: 20yang telah dikonfigurasi di plugin. - Persentase error (HTTP 5xx) adalah 0%.
- Latency tetap konsisten karena koneksi TCP digunakan kembali (*reused*) via pool.
Catatan Arsitektur: Node.js vs Serverless
Pola singleton plugin ini dirancang khusus untuk deployment berbasis Node.js standar (Docker container, VPS, PM2, atau Kubernetes). Jika aplikasi di-deploy ke platform Serverless / Edge (seperti Vercel Functions atau AWS Lambda), setiap fungsi berjalan di environment terisolasi dengan siklus hidup pendek.
Pada arsitektur serverless murni, gunakan connection proxy eksternal seperti PgBouncer, Supabase Connection Pooler (Supavisor), atau driver HTTP/WebSocket database (seperti Neon serverless driver) untuk mencegah kehabisan koneksi pada database engine.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!