Satu koneksi MongoDB berarti satu socket TCP, satu sesi handshake, dan satu slot di maxIncomingConnections server. Membukanya butuh belasan milidetik — murah untuk sekali, mahal bila dilakukan untuk setiap request. Solusinya adalah connection pool: deretan koneksi yang dibuka sekali, disimpan di dalam client, dan dipakai bergantian oleh operasi. Pool adalah alasan pola “satu client per proses” dari Chapter Connection Lifecycle bekerja; chapter ini membuka mesinnya, mengamatinya dari sisi driver maupun server, lalu menyusun anggaran kapasitasnya.

Pool di Balik Setiap Client #

Mekanismenya rapat dan bisa diprediksi: setiap operasi menyewa satu koneksi dari pool (checkout), menjalankan command, lalu mengembalikannya (checkin). Bila tidak ada koneksi idle, pool menumbuhkan yang baru sampai batas maxPoolSize; bila batas itu tercapai, operasi berikutnya mengantre.

Opsi pool di driver Node.js 7.x beserta nilai bawaannya:

OpsiBawaanArti
maxPoolSize100jumlah koneksi maksimum per client
minPoolSize0jumlah koneksi minimal yang dijaga tetap terbuka
maxConnecting2berapa koneksi boleh dibuka bersamaan
maxIdleTimeMS0batas umur koneksi menganggur (0 = tanpa batas)
waitQueueTimeoutMS0lama operasi boleh mengantri (0 = tanpa batas)

Nilai bawaan itu bersahabat untuk satu aplikasi kecil, tetapi bukan angka baku — maxPoolSize justru opsi yang paling sering perlu disetel turun, dan alasannya ada di aritmetika bagian akhir chapter ini.

Dua opsi lain layak diceritakan. minPoolSize menjaga sejumlah koneksi tetap terbuka sehingga lonjakan pertama tidak menunggu pembukaan socket — berguna untuk service yang trafiknya naik-turun tajam. waitQueueTimeoutMS yang dibiarkan 0 berarti operasi mengantre tanpa batas waktu; aplikasi yang lebih memilih gagal cepat (lalu dicoba ulang di level yang lebih tinggi) kadang menyetelnya beberapa ratus milidetik. Keduanya adalah setelan halus — mulailah dari maxPoolSize dan biarkan sisanya bawaan sampai ada pengukuran yang menyarankan sebaliknya.

Mengamati Pool dari Dua Sisi #

Driver Node.js memancarkan event untuk setiap kejadian pool. File berikut membatasi pool menjadi 2 koneksi, lalu menjalankan 3 command bersamaan supaya antreannya terlihat:

js
// pantau-pool.js — mengamati siklus hidup connection pool lewat event driver
// Jalankan: node pantau-pool.js  (butuh paket: npm install mongodb)
const { MongoClient } = require("mongodb")

const uri = "mongodb://127.0.0.1:27017/?directConnection=true"
const client = new MongoClient(uri, { maxPoolSize: 2, minPoolSize: 0 })

const jenis = [
  "connectionPoolCreated", "connectionCreated", "connectionReady",
  "connectionCheckedOut", "connectionCheckedIn", "connectionClosed",
]
for (const j of jenis) {
  client.on(j, e => console.log(`[event] ${j} address=${e.address}`))
}

async function main() {
  const db = client.db("latihan_pool")
  // tiga command berjalan bersamaan, sementara pool hanya boleh punya 2 koneksi
  await Promise.all([
    db.command({ ping: 1 }),
    db.command({ ping: 1 }),
    db.command({ ping: 1 }),
  ])
  await client.close()
  console.log("client.close() selesai")
}

main().catch(err => {
  console.error("GAGAL:", err.message)
  process.exit(1)
})
bash
node pantau-pool.js

Output yang Dihasilkan:

text
[event] connectionPoolCreated address=127.0.0.1:27017
[event] connectionCreated address=127.0.0.1:27017
[event] connectionCreated address=127.0.0.1:27017
[event] connectionReady address=127.0.0.1:27017
[event] connectionCheckedOut address=127.0.0.1:27017
[event] connectionReady address=127.0.0.1:27017
[event] connectionCheckedOut address=127.0.0.1:27017
[event] connectionCheckedIn address=127.0.0.1:27017
[event] connectionCheckedOut address=127.0.0.1:27017
[event] connectionCheckedIn address=127.0.0.1:27017
[event] connectionCheckedIn address=127.0.0.1:27017
[event] connectionCheckedOut address=127.0.0.1:27017
[event] connectionCheckedIn address=127.0.0.1:27017
[event] connectionClosed address=127.0.0.1:27017
[event] connectionClosed address=127.0.0.1:27017
client.close() selesai

Bacalah urutannya sebagai tiga babak. Babak pertama: connectionCreated muncul tepat dua kali — maxPoolSize: 2 dihormati meski ada 3 command, karena dua koneksi pertama cukup untuk menampung dua operasi dan yang ketiga mengantri. Babak kedua: connectionCheckedOut muncul empat kali untuk checkin yang juga empat — tiga dari ping kita, satu lagi dari endSessions yang dikirim close(); checkout ketiga baru terjadi setelah ada connectionCheckedIn, itulah antrean bekerja. Babak penutup: connectionClosed dua kali saat client.close() menutup seluruh pool.

Dari sisi server, jejak yang sama terbaca di serverStatus:

js
const c = db.serverStatus().connections
printjson({
  current: c.current,
  available: c.available,
  totalCreated: c.totalCreated,
  rejected: c.rejected,
})

Output yang Dihasilkan:

text
{
  current: 6,
  available: 403,
  totalCreated: 10331,
  rejected: 0
}

current adalah koneksi yang sedang terbuka, available sisa slot, totalCreated akumulasi sejak server menyala, dan rejected menghitung koneksi yang pernah ditolak karena slot habis. Angka rejected: 0 adalah kondisi sehat; nilainya yang naik berarti server pernah menolak aplikasi — kejadian yang seharusnya tidak pernah terjadi bila anggaran dihitung dengan benar.

Anggaran Koneksi dan Banjiran Koneksi #

Aturan aritmetikanya satu baris:

jumlah proses×maxPoolSize≤anggaran koneksi server \text{jumlah proses} \times \text{maxPoolSize} \le \text{anggaran koneksi server}

Anggaran itu adalah current + available yang kita lihat di output di atas, dikurangi jatah untuk mongosh, monitoring, dan komponen internal. Contoh penerapannya:

TopologiHitunganCatatan
1 proses aplikasi, maxPoolSize bawaan1 × 100 = 100nyaman di anggaran ±409
12 pod replika, bawaan tak disentuh12 × 100 = 1200hampir separuh anggaran habis sebelum ada beban
12 pod, maxPoolSize: 2012 × 20 = 240menyisakan ruang untuk pertumbuhan

Skenario tengah itulah yang dikenal sebagai connection storm: autoscaler menambah pod malam hari, ratusan koneksi baru menyerbu server serentak, dan server menghabiskan tenaga menerima handshake alih-alih melayani query. Pencegahannya bukan menaikkan anggaran server, melainkan menyetel maxPoolSize per proses secara sadar — pool yang lebih kecil di setiap proses hampir selalu lebih sehat daripada pool besar yang dikalikan.

Berapa angka yang tepat? Mulai dari pengukuran, bukan tebakan: pantau durasi operasi p99 dan jumlah operasi bersamaan di aplikasi. Bila antrean wait queue sering panjang dan CPU server masih longgar, pool memang perlu dilebarkan; bila current di server tinggi tetapi throughput tidak ikut naik, koneksi hanya menumpuk tanpa bekerja. Angka yang sehat untuk sebagian besar aplikasi web berada di kisaran 10–50 per proses — jauh di bawah bawaan 100.

LakukanHindari
Satu client (satu pool) per proses, dibagikan lintas requestMembuat client baru per request atau per job
Setel maxPoolSize dari hitungan anggaranMembiarkan bawaan 100 dikalikan jumlah pod
Pantau current, available, rejected secara berkalaMenyimpulkan kesehatan koneksi hanya dari sisi aplikasi
Turunkan maxIdleTimeMS bila server membatasi umur koneksiMenaikkan pool sebagai respons pertama untuk keluhan lambat

Rangkuman #

  • Setiap operasi melakukan checkout–operasi–checkin terhadap pool; pool tumbuh sampai maxPoolSize, sisanya mengantri di wait queue.
  • Opsi bawaan driver Node.js: maxPoolSize 100, minPoolSize 0, maxConnecting 2, waitQueueTimeoutMS tanpa batas.
  • Event pool driver dan serverStatus().connections (termasuk rejected) memberi pandangan dari dua sisi — aplikasi dan server.
  • Anggaran koneksi = jumlah proses × maxPoolSize; connection storm dicegah dengan menyetel pool per proses, bukan menaikkan kapasitas server.