Di mongosh, koneksi adalah urusan yang tak terlihat — shell membukanya saat startup dan menutupnya saat kita keluar. Di aplikasi, siklus hidup koneksi menjadi tanggung jawab kode kita: kapan dibuat, kapan dipakai, dan kapan ditutup. Sebagian besar insiden “aplikasi tidak bisa tersambung” di produksi berakar pada kesalahan di siklus ini — client dibuat berulang tiap request, close() yang lupa dipanggil, atau timeout yang tidak pernah disetel. Chapter ini menelusuri empat tahap hidup koneksi dengan eksekusi driver sungguhan, lalu membahas pola lazy vs eager dan singleton.

Empat Tahap Hidup Koneksi #

Aturan mainnya: new MongoClient(uri) hanya membuat object konfigurasi tanpa menyentuh jaringan; connect() (atau operasi pertama) yang membuka koneksi sungguhan; operasi berjalan lewat pool; dan close() menutup semuanya. Diagram berikut merangkum transisinya:

Tahap “Terhubung” mencakup server selection, pembukaan TCP/TLS, handshake hello, dan autentikasi — baru setelah itu pool siap melayani operasi. Kita jalankan keempat tahap itu dalam satu file dengan driver Node.js (pasang dulu paketnya dengan npm install mongodb):

js
// siklus-koneksi.js — empat tahap hidup koneksi dengan driver Node.js
// Jalankan: node siklus-koneksi.js  (butuh paket: npm install mongodb)
const { MongoClient } = require("mongodb")

const uri = process.env.MONGODB_URI ?? "mongodb://127.0.0.1:27017/?directConnection=true"
const client = new MongoClient(uri, { serverSelectionTimeoutMS: 5000 })

async function main() {
  console.log("1. new MongoClient(uri) — client dibuat, jaringan belum disentuh")

  await client.connect()
  console.log("2. connect() selesai — handshake dan autentikasi berhasil")

  const ping = await client.db("admin").command({ ping: 1 })
  console.log("3. ping diterima, ok =", ping.ok)

  const res = await client.db("latihan_siklus").collection("kunjungan")
    .insertOne({ pada: new Date() })
  console.log("4. insertOne acknowledged:", res.acknowledged, "| insertedId:", res.insertedId)

  await client.close()
  console.log("5. client.close() — seluruh koneksi pool ditutup")
}

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

Output yang Dihasilkan:

text
1. new MongoClient(uri) — client dibuat, jaringan belum disentuh
2. connect() selesai — handshake dan autentikasi berhasil
3. ping diterima, ok = 1
4. insertOne acknowledged: true | insertedId: new ObjectId('6ad3de9d5d99a094658a5408')
5. client.close() — seluruh koneksi pool ditutup

Mari kita bedah baris-per-baris: baris new MongoClient(uri, ...) hanya mengurai connection string dan menyimpan opsi — itulah mengapa tahap ini instan dan murah. Baris await client.connect() memaksa koneksi terbuka sekarang, sehingga galat konfigurasi atau kredensial langsung terlihat. command({ ping: 1 }) adalah pemeriksaan kesiapan paling ringan yang tersedia di semua driver. Perhatikan juga bentuk insertedId pada baris keempat: driver menampilkan BSON ObjectId sebagai new ObjectId('...') — tipe asli, bukan string biasa. Terakhir, client.close() menutup seluruh socket milik pool sekaligus.

Server merekam setiap koneksi yang masuk. Sebelum membaca catatannya, kita buktikan dulu bahwa koneksi dipakai ulang — dua command berurutan dari client yang sama berjalan lewat satu koneksi:

js
db.hello().connectionId === db.hello().connectionId

Output yang Dihasilkan:

text
true

connectionId kedua panggilan hello identik: mongosh (dan driver pada umumnya) tidak membuka koneksi baru untuk setiap operasi, melainkan memakai ulang yang sudah ada. Sekarang kita baca statistik sisi server:

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

Output yang Dihasilkan:

text
{
  current: 6,
  available: 403,
  totalCreated: 10160
}

current adalah koneksi aktif saat ini, available sisa slot sebelum maxIncomingConnections tercapai, dan totalCreated akumulasi koneksi yang pernah dibuat sejak server menyala. Setiap connect() dari aplikasi menaikkan totalCreated — pengamatan ini kita pakai lagi secara mendalam di Chapter Connection Pooling.

Lazy atau Eager #

Dua gaya membuka koneksi: lazy (biarkan operasi pertama yang memicu) dan eager (connect() eksplisit di awal). File kecil berikut membuktikan bahwa driver memang mengizinkan gaya lazy:

js
// koneksi-malas.js — operasi pertama bisa memicu koneksi tanpa connect() eksplisit
// Jalankan: node koneksi-malas.js  (butuh paket: npm install mongodb)
const { MongoClient } = require("mongodb")

const client = new MongoClient("mongodb://127.0.0.1:27017/?directConnection=true")

async function main() {
  // Tidak ada client.connect() — operasi pertama-lah yang memicu koneksi
  const jumlah = await client.db("latihan_siklus").collection("kunjungan").countDocuments()
  console.log("operasi tanpa connect() eksplisit berhasil, jumlah dokumen:", jumlah)

  await client.close()
  console.log("close() tetap wajib dipanggil")
}

main().catch(err => {
  console.error("GAGAL:", err.name, "-", err.message)
  process.exit(1)
})

Output yang Dihasilkan (latihan_siklus berisi satu document dari file sebelumnya):

text
operasi tanpa connect() eksplisit berhasil, jumlah dokumen: 1
close() tetap wajib dipanggil

Gaya lazy nyaman untuk skrip sekali jalan. Untuk server berumur panjang, pilih eager: connect() di saat startup membuat galat konfigurasi muncul saat boot — bukan pada request pengguna pertama. Prinsipnya sama dengan aplikasi apa pun: gagal lebih awal, saat masih murah.

Timeout yang Mengikat Setiap Tahap #

Setiap tahap siklus punya batas waktunya sendiri di sisi client. Nilai bawaan driver Node.js 7.x:

OpsiBawaanMengikat
serverSelectionTimeoutMS30000menunggu server yang cocok (tersambung + lolos autentikasi)
connectTimeoutMS30000pembukaan TCP/TLS per koneksi
socketTimeoutMS0lama menunggu jawaban per operasi (0 = tanpa batas)
heartbeatFrequencyMS10000jeda pemeriksaan kesehatan topologi

Gejala klasik salah setel: aplikasi terlihat menggantung 30 detik lalu melempar galat — hampir selalu serverSelectionTimeoutMS yang sedang menghitung. Turunkan nilainya untuk gagal cepat, naikkan bila jaringan ke cluster memang lambat.

Close dan Pola Singleton #

Koneksi yang tidak ditutup tetap memakan slot di server (current naik, available turun) dan file descriptor di proses aplikasi. Di Node.js ada gejala tambahan yang khas: socket yang terbuka menjaga event loop tetap hidup, sehingga skrip yang lupa close() selesai bekerja tetapi prosesnya tidak pernah keluar.

Untuk aplikasi berumur panjang, solusinya satu client per proses — dibuat saat startup, dibagikan ke seluruh request, ditutup saat proses menerima sinyal berhenti:

js Ilustrasi — singleton client yang dibagikan ke seluruh request
// mdtest: skip — pola umum pada aplikasi web Node.js
const { MongoClient } = require("mongodb")
const client = new MongoClient(process.env.MONGODB_URI)

// modul ini diekspor satu kali; seluruh handler memakai client dan pool yang sama
module.exports = client

Driver juga memancarkan event monitoring pool — connectionPoolCreated, connectionCreated, connectionCheckedOut — yang bisa disubsikripsi untuk observability; kita praktikkan lengkap di Chapter Connection Pooling. Database latihan_siklus dari latihan ini boleh dibersihkan kapan saja lewat db.getSiblingDB("latihan_siklus").dropDatabase().

Rangkuman #

  • Empat tahap: buat client (tanpa I/O) → connect() (server selection, TCP/TLS, handshake, auth) → operasi via pool → close().
  • Gaya lazy membiarkan operasi pertama memicu koneksi; gaya eager (connect() di startup) gagal cepat — pilihan yang tepat untuk server produksi.
  • Timeout bawaan: serverSelectionTimeoutMS 30 detik, connectTimeoutMS 30 detik, socketTimeoutMS tanpa batas — gejala “menggantung 30 detik” hampir selalu server selection.
  • Satu client per proses, dibagikan lintas request; close() wajib dijalankan saat proses berhenti agar slot server dan event loop terbebaskan.