Skip to content

Memahami Koneksi Database: Dari Handshake sampai Connection Pooling ​

text
FATAL: sorry, too many clients already

Error di atas adalah salah satu pesan yang paling sering membuat engineer panik di tengah malam saat jadwal on-call. Artikel ini membahas apa yang sebenarnya terjadi di balik layar sebuah koneksi database, alasan mengapa koneksi tergolong mahal, serta cara mengelolanya dengan benar agar error tersebut tidak terjadi di sistem kamu.

Apa itu koneksi database? ​

Koneksi database adalah jalur komunikasi dua arah antara aplikasi dan server database. Sebelum satu query pun bisa dieksekusi, aplikasi harus membuka koneksi terlebih dahulu. Konsepnya mirip seperti menelepon dan menunggu panggilan diangkat sebelum bisa berbicara.

Hal yang sering terlupakan di pekerjaan: membuat koneksi baru itu mahal.

Mahal di sini bukan berarti biaya finansial, melainkan mahal dalam hal waktu (latency) dan pemakaian resource hardware (CPU serta memori). Kesalahpahaman mengenai alasan koneksi itu mahal sering menjadi akar penyebab utama masalah performa di lingkungan production.

Bagaimana proses koneksi terjadi? ​

Ketika aplikasi memanggil fungsi seperti createConnection() atau sejenisnya melalui database driver, terjadi serangkaian tahapan sebelum query pertama dapat dikirimkan:

Loading diagram...

Secara detail, tahapan pembentukan koneksi meliputi:

  • Pembukaan socket TCP: aplikasi membuka koneksi network ke port database. Beberapa port default yang umum digunakan:

    DatabasePort Default
    PostgreSQL5432
    MySQL / MariaDB3306
    SQL Server1433
    Oracle1521
    MongoDB27017
  • Handshake protokol: client dan server saling bertukar versi protokol, informasi capability, dan parameter sesi.

  • Autentikasi: server memverifikasi identitas client melalui username, password, atau sertifikat SSL/TLS jika enkripsi diaktifkan.

  • Otorisasi: server memeriksa hak akses client, misalnya database mana yang boleh diakses dan operasi apa saja yang diizinkan.

  • Alokasi resource: server mengalokasikan memori untuk buffer sesi. Di PostgreSQL, server bahkan melakukan spawn satu proses backend (backend worker process) baru khusus untuk menangani sesi koneksi tersebut.

Seluruh proses di atas membutuhkan waktu puluhan hingga ratusan milidetik, terutama jika koneksi melewati network antar server atau menggunakan negosiasi TLS/SSL.

Bandingkan tahapan tersebut dengan eksekusi query sederhana yang sering kali selesai hanya dalam 1–5 milidetik. Artinya, membuat koneksi baru bisa puluhan hingga ratusan kali lebih lama dibanding eksekusi query itu sendiri.

Konsep Dasar

Biaya pembuatan koneksi yang tinggi inilah alasan utama perlunya connection pooling. Memahami biaya ini membantu kamu mendesain arsitektur aplikasi yang efisien.

Membuka dan menutup koneksi ​

Membuka koneksi ​

Berikut contoh membuka koneksi secara langsung di Node.js menggunakan library pg untuk PostgreSQL:

javascript
const { Client } = require("pg");

const client = new Client({
  host: "localhost",
  user: "app_user",
  password: "secret",
  database: "mydb",
});

// Membuka koneksi TCP, melakukan autentikasi, dan mengalokasikan resource di server
await client.connect();

// Menjalankan query pada koneksi aktif
const result = await client.query("SELECT * FROM users");

Setelah query selesai dijalankan, koneksi harus ditutup:

javascript
// Menutup sesi koneksi dan melepaskan resource di sisi server
await client.end();

Mengapa menutup koneksi sama pentingnya dengan membuka? ​

Setiap koneksi yang terbuka menahan resource di sisi database server:

  • Memori: di PostgreSQL, setiap koneksi (meskipun sedang idle) adalah sebuah proses tersendiri yang memakan memori sekitar 5–10 MB RAM.
  • File descriptor: sistem operasi pada server memiliki batas file descriptor yang harus dibagi ke seluruh proses yang berjalan.
  • Manajemen thread atau proses: semakin banyak koneksi terbuka, semakin besar overhead OS untuk melakukan penjadwalan dan context switching.

Jika aplikasi membuka koneksi tetapi tidak menutupnya setelah selesai, terjadi kondisi yang disebut connection leak. Aplikasi terus membuka koneksi baru sampai server database kehabisan kuota koneksi, lalu server mulai menolak koneksi berikutnya dengan pesan error too many clients already.

Tiga timeout yang wajib dipahami ​

Untuk menjaga stabilitas koneksi di production, ada tiga jenis timeout yang perlu kamu perhatikan:

TimeoutDefinisi
Connection timeoutBatas waktu toleransi aplikasi saat mencoba menyambung ke database sebelum menganggap proses gagal.
Idle timeoutBatas waktu sebuah koneksi dibiarkan menganggur tanpa aktivitas sebelum diputus.
Wait timeoutBatas waktu server database menunggu request baru pada koneksi yang idle sebelum menutupnya secara sepihak (di MySQL default bernilai 8 jam).

Jebakan: koneksi diputus sepihak oleh server

Jika database server menutup koneksi yang idle terlalu lama karena batas wait_timeout, tetapi aplikasi masih menganggap koneksi tersebut terbuka, query berikutnya akan menghasilkan error seperti connection terminated unexpectedly atau broken pipe.

Solusinya: pastikan aplikasi memvalidasi kesehatan koneksi sebelum digunakan, atau gunakan connection pool yang memiliki fitur pengecekan koneksi berkala otomatis.

Connection pooling ​

Masalah tanpa pooling ​

Tanpa connection pooling, siklus hidup koneksi untuk setiap request HTTP terlihat seperti ini:

text
Request 1: Buka koneksi (100 ms) -> Jalankan query (5 ms) -> Tutup koneksi (5 ms)
Request 2: Buka koneksi (100 ms) -> Jalankan query (5 ms) -> Tutup koneksi (5 ms)
Request 3: Buka koneksi (100 ms) -> Jalankan query (5 ms) -> Tutup koneksi (5 ms)

Sebagian besar waktu habis hanya untuk membuka dan menutup koneksi, bukan untuk mengeksekusi query. Pola ini sangat tidak efisien dan membuat latency aplikasi tinggi.

Cara kerja connection pool ​

Connection pool adalah penampung (pool) koneksi yang sudah terbuka dan siap dipakai berulang kali oleh berbagai request:

Loading diagram...

Mekanisme kerjanya:

  1. Saat aplikasi start, pool membuka beberapa koneksi awal ke database dan menjaganya tetap aktif.
  2. Ketika ada request yang perlu menjalankan query, aplikasi meminjam koneksi yang tersedia dari pool (acquire).
  3. Setelah query selesai dieksekusi, koneksi tidak ditutup, melainkan dikembalikan ke pool (release).
  4. Request berikutnya dapat langsung memakai kembali koneksi yang ada tanpa perlu mengulang proses handshake dan autentikasi.

Contoh implementasi connection pool ​

Berikut contoh penggunaan Pool di Node.js menggunakan library pg:

javascript
const { Pool } = require("pg");

// Inisialisasi pool satu kali saat aplikasi booting
const pool = new Pool({
  host: "localhost",
  user: "app_user",
  password: "secret",
  database: "mydb",
  max: 10, // batas maksimum 10 koneksi simultan dari instance aplikasi ini
  idleTimeoutMillis: 30000, // koneksi idle diputus setelah 30 detik
  connectionTimeoutMillis: 5000, // batas waktu menunggu koneksi tersedia (5 detik)
});

// pool.query secara otomatis meminjam koneksi, menjalankan query, dan mengembalikannya ke pool
const result = await pool.query("SELECT * FROM users WHERE id = $1", [1]);

Jika kamu perlu menjalankan transaksi multi-query (BEGIN ... COMMIT), lakukan peminjaman koneksi secara eksplisit dengan memastikan koneksi selalu dikembalikan di blok finally:

javascript
const client = await pool.connect();
try {
  await client.query("BEGIN");
  await client.query(
    "UPDATE accounts SET balance = balance - 100 WHERE id = $1",
    [1],
  );
  await client.query(
    "UPDATE accounts SET balance = balance + 100 WHERE id = $2",
    [2],
  );
  await client.query("COMMIT");
} catch (error) {
  await client.query("ROLLBACK");
  throw error;
} finally {
  // Selalu release koneksi agar kembali ke pool dan tidak terjadi leak
  client.release();
}

Parameter konfigurasi penting ​

ParameterFungsi
maxJumlah maksimum koneksi yang diizinkan untuk dibuka oleh pool ini ke server database.
idleTimeoutMillisDurasi koneksi dibiarkan idle di dalam pool sebelum ditutup untuk menghemat resource.
connectionTimeoutMillisBatas waktu toleransi aplikasi saat antre menunggu koneksi kosong di pool sebelum melempar error timeout.
maxLifetimeBatas usia maksimal koneksi sebelum digantikan dengan koneksi baru (daur ulang koneksi berkala).

Kesalahan umum: membuat pool di setiap request

Jangan membuat instance connection pool di dalam function handler request. Pool harus dibuat satu kali saat aplikasi booting (singleton) dan dipakai bersama di seluruh aplikasi. Membuat pool di setiap request justru memperparah pembuatan koneksi baru dan menghabiskan memori server.

Tiga angka dalam tiga level berbeda ​

Saat merancang ukuran koneksi, sering terjadi kebingungan antara kapasitas aplikasi, konfigurasi database, dan kemampuan hardware. Ketiganya berada pada level yang berbeda:

LevelParameterContoh NilaiPenjelasan
Level 1: Aplikasipool_size20Ditentukan di kode aplikasi: batas koneksi yang boleh dibuka oleh satu instance aplikasi ini.
Level 2: Database Servermax_connections100Dikonfigurasi di database server: batas total koneksi dari seluruh aplikasi dan client yang diterima server.
Level 3: Kapasitas Hardwarecore_count × 28 (pada 4 core)Batas kemampuan riil CPU database: perkiraan jumlah query yang dapat diproses secara bersamaan dengan efisien.

Analogi restoran ​

Untuk memahami perbedaan ketiga level di atas, bayangkan sebuah restoran:

  • max_connections (100): jumlah kapasitas kursi tamu di dalam restoran. Jika seluruh 100 kursi terisi, tamu baru yang datang terpaksa ditolak di pintu masuk.
  • pool_size aplikasi (20): jumlah jatah kursi untuk satu rombongan pemesan tertentu.
  • core_count × 2 (8): jumlah koki di dapur yang memasak makanan secara paralel.

Restoran bisa saja menampung 100 tamu duduk. Namun jika dapur hanya memiliki 4 koki, memasak 100 pesanan makanan secara bersamaan justru membuat dapur panik, koki kelelahan, dan semua pesanan selesai lebih lambat. Memiliki banyak kursi bukan berarti bisa memasak semuanya sekaligus.

Dari mana formula core × 2? ​

Salah satu formula ukuran pool yang terkenal dipopulerkan oleh tim pengembang HikariCP (connection pool berkecepatan tinggi di ekosistem Java):

pool_size ≈ core_count × 2 + effective_spindle_count

Pada server modern yang menggunakan SSD, tidak ada piringan mekanik berputar (spindle), sehingga nilai effective spindle count mendekati 0. Formula praktisnya menjadi:

pool_size ≈ core_count × 2

Poin paling penting yang perlu dicatat: core_count di sini mengacu pada jumlah core CPU di database server, bukan server aplikasi.

Pihak yang memproses eksekusi query (parsing SQL, kalkulasi query planner, join tabel, sorting data, dan scanning index) adalah CPU di server database.

Logika di baliknya:

  1. Setiap query yang sedang aktif dieksekusi membutuhkan perhatian unit kerja CPU di database server.
  2. Jika jumlah query yang dieksekusi bersamaan jauh melebihi jumlah core CPU, operating system terpaksa melakukan context switching (berganti giliran memproses thread atau proses secara berulang-ulang).
  3. Beban context switching yang tinggi justru menurunkan efisiensi CPU dan memperlambat throughput query secara keseluruhan.

Pengali × 2 digunakan sebagai faktor buffer karena pada kenyataannya, koneksi yang sedang aktif tidak selalu memakan CPU 100% sepanjang waktu.

TIP

Perlu diingat bahwa formula ini awalnya dirancang dengan asumsi satu aplikasi monolith yang terhubung ke satu database server. Jika ada beberapa aplikasi atau instance yang mengakses database yang sama, angka ini merupakan total anggaran (pool budget) yang harus dibagi bersama.

Mengapa status "aktif" tidak selalu memakan CPU? ​

Di dalam database, query yang sedang berjalan memiliki beberapa kemungkinan status:

Status QueryMemakai CPU?Contoh Kondisi
Aktif mengeksekusiYaQuery sedang melakukan sequential scan tabel besar atau perhitungan agregasi data di memori.
Menunggu I/O diskTidakQuery membaca data dari penyimpanan disk karena halaman data belum ada di cache memory (buffer pool).
Menunggu lockTidakQuery tertahan menunggu transaksi lain melepaskan lock pada baris atau tabel yang sama.
Idle in transactionTidakAplikasi membuka transaksi (BEGIN), tetapi belum mengirimkan perintah COMMIT atau ROLLBACK.

Bahaya status "idle in transaction"

Koneksi dengan status idle in transaction tidak membebani CPU, tetapi menahan row lock dan mencegah database membersihkan record lama (misalnya proses autovacuum di PostgreSQL). Kondisi ini dapat menurunkan performa tabel secara signifikan jika dibiarkan terlalu lama.

Karena sebagian query menghabiskan waktu menunggu I/O atau lock, formula core × 2 memberikan rasio yang pas antara pemanfaatan CPU dan waktu tunggu antrean. Memperbesar ukuran pool melebihi rasio tersebut tidak akan mempercepat eksekusi jika CPU database sudah mencapai batas kapasitasnya.

Mengapa pool besar tidak membuat query lebih cepat? ​

Banyak developer mengira bahwa memperbesar ukuran pool (misalnya mengatur pool_size = 20 pada server database 4 core) otomatis membuat aplikasi mampu menangani traffic lebih cepat. Anggapan ini keliru.

Pool besar tidak membuat query berjalan lebih cepat. Jika database server hanya memiliki 4 core CPU, 20 query yang masuk bersamaan secara fisik tetap hanya bisa dikerjakan sekitar 4 sekaligus oleh CPU. Sisanya akan menumpuk dan antre di dalam internal PostgreSQL.

Kondisi tersebut menimbulkan beberapa dampak negatif:

  • Context switching berat: CPU dipaksa membagi waktu dan berpindah-pindah giliran di antara puluhan proses, sehingga throughput pemrosesan data justru menurun.
  • Konsumsi memori membengkak: setiap koneksi menahan alokasi RAM tersendiri di server database (sekitar 5–10 MB per proses backend di PostgreSQL).
  • Latency query melonjak: waktu respons setiap query meningkat karena harus berebut giliran CPU dan antrean I/O disk.

Lebih baik antrean terjadi di aplikasi daripada menumpuk di database server.

Ketika ukuran pool dibatasi secara proporsional (misalnya 8 koneksi), request yang belum kebagian koneksi akan mengantre secara tertib di level connection pool aplikasi. Menahan antrean di sisi aplikasi membutuhkan resource yang jauh lebih hemat dan terkendali. Sementara itu, server database tetap bekerja pada kapasitas optimalnya tanpa kewalahan. Begitu satu query selesai dan koneksi dikembalikan (release), request berikutnya di antrean pool baru dikirimkan ke database.

Studi kasus: 3 aplikasi dan 1 database ​

Mari kita lihat contoh penerapan formula ini pada arsitektur yang sering ditemui di lingkungan kerja: beberapa service aplikasi yang terhubung ke satu database server.

Skenario arsitektur:

  • Database Server: PostgreSQL, 4 core CPU, konfigurasi max_connections = 100.
  • Aplikasi: 3 instance service backend (misalnya Service A, Service B, dan Service C).

Menghitung alokasi pool yang ideal ​

Langkah pertama adalah menghitung total kapasitas pool yang ideal untuk hardware database server:

text
Kapasitas ideal database = 4 core CPU × 2 = 8 koneksi paralel efisien

Angka 8 ini adalah total pool budget untuk seluruh database server, bukan jatah yang bisa dipakai oleh masing-masing aplikasi secara mandiri. Karena ada 3 service yang mengakses database yang sama, kapasitas ideal tersebut dibagi rata ke seluruh service:

text
pool_size per instance ≈ (core_count × 2) / total jumlah instance aplikasi
pool_size per instance ≈ 8 / 3 ≈ 2 sampai 3 koneksi per aplikasi

Pada skenario ini, kita dapat membagi alokasi pool menjadi:

  • Service A: pool_size = 3
  • Service B: pool_size = 3
  • Service C: pool_size = 2
  • Total koneksi pool: 8 koneksi (tepat sesuai kapasitas ideal 4 core CPU)

Yang terjadi di balik layar ​

Loading diagram...

Poin penting dari arsitektur ideal ini:

  1. Database server bekerja pada efisiensi puncak: dengan total 8 koneksi terbuka, CPU 4 core dapat mengeksekusi query secara paralel dengan context switching minimal. Waktu proses query tetap cepat dan stabil.
  2. Antrean tertahan di level aplikasi: jika ada lonjakan request di Service A, request ekstra tidak langsung membanjiri database, melainkan mengantre tertib di connection pool milik Service A. Begitu query sebelumnya selesai, koneksi langsung dilepas (release) dan dipakai oleh request berikutnya.
  3. Isolasi antar service: karena jatah pool dibatasi per service, Service A yang sedang sibuk tidak akan memonopoli seluruh kapasitas database atau mengganggu Service B dan Service C.

Bagaimana jika kebutuhan traffic meningkat? ​

Jika traffic aplikasi semakin tinggi dan antrean di pool aplikasi mulai menimbulkan waktu tunggu (acquire wait time) yang terlalu lama, berikut opsi penanganannya:

  • Opsi 1: Optimasi durasi query: periksa query SQL dan pastikan index bekerja optimal. Jika durasi query berkurang dari 20 ms menjadi 2 ms, satu koneksi di pool bisa melayani 10 kali lebih banyak request per detik tanpa perlu menambah ukuran pool.
  • Opsi 2: Scale up CPU database server: jika query sudah efisien tetapi throughput tetap butuh ditingkatkan, upgrade database server ke 8 atau 16 core CPU. Pada server 8 core, total pool budget naik menjadi ≈ 16 koneksi, sehingga masing-masing dari 3 service bisa dinaikkan menjadi pool_size = 5–6.
  • Opsi 3: Gunakan external connection pooler: jika jumlah instance aplikasi bertambah banyak (misalnya belasan service atau arsitektur serverless), gunakan tools seperti PgBouncer agar ratusan service dapat berbagi puluhan koneksi fisik database secara dinamis.

Prinsip: ukur, jangan menebak

Gunakan formula core × 2 sebagai titik awal konfigurasi. Setelah itu, pantau metrik riil di production: waktu tunggu peminjaman koneksi (acquire wait time), latency query, serta jumlah koneksi aktif vs idle. Sesuaikan ukuran pool berdasarkan data metrik tersebut.

Jangan lupakan headroom untuk admin ​

Salah satu insiden yang sering terjadi di production adalah situasi darurat tanpa akses:

  1. Nilai max_connections = 100.
  2. Seluruh 100 slot koneksi habis terpakai oleh aplikasi (misalnya karena terjadi traffic spike atau connection leak).
  3. Aplikasi mulai mengalami error koneksi massal.
  4. Engineer berusaha masuk ke database via terminal (psql) untuk mencari penyebab masalah.
  5. Login ditolak oleh database server karena tidak ada sisa kuota koneksi yang tersedia.

Untuk mencegah situasi tersebut, database modern menyediakan mekanisme reservasi koneksi khusus bagi administrator:

DatabasePengaturan / MekanismeHak Akses
PostgreSQLsuperuser_reserved_connections (default: 3)Khusus role dengan status superuser.
MySQLMenyediakan 1 slot koneksi ekstra di luar max_connectionsKhusus user dengan privilege SUPER atau CONNECTION_ADMIN.

Contoh perhitungan alokasi di PostgreSQL:

text
max_connections = 100
superuser_reserved_connections = 3
------------------------------------------------------
Koneksi maksimal untuk aplikasi biasa = 97
Koneksi cadangan khusus superuser     = 3

Aktivitas yang wajib kamu masukkan ke dalam perhitungan cadangan (headroom):

  • Investigasi insiden: memeriksa query yang berjalan melalui pg_stat_activity dan mematikan query yang bermasalah.
  • Migrasi skema database: menjalankan perintah ALTER TABLE atau deployment script.
  • Maintenance rutin: proses backup harian atau operasi database maintenance seperti VACUUM.
  • Monitoring agent: monitoring tools seperti Datadog, Prometheus node exporter, atau background cron worker.

Pastikan total koneksi seluruh instance aplikasi ditambah headroom admin selalu berada di bawah max_connections.

Solusi untuk skala besar: external connection pooler ​

Pada arsitektur dengan ratusan microservices atau platform serverless (seperti AWS Lambda), instance aplikasi dapat bertambah (scale out) secara dinamis hingga ratusan unit.

Jika 100 fungsi Lambda masing-masing membuka pool berisi 5 koneksi, database akan menerima 500 koneksi bersamaan. Mengingat setiap koneksi di PostgreSQL memakan memori proses tersendiri, menaikkan max_connections ke angka ribuan akan membebani RAM dan menurunkan stabilitas database.

Solusi yang tepat untuk arsitektur ini adalah menggunakan external connection pooler yang berdiri di antara aplikasi dan database:

Loading diagram...

Beberapa solusi pooler eksternal yang umum dipakai di pekerjaan:

  • PgBouncer (PostgreSQL): mendukung mode transaction pooling, di mana koneksi fisik ke database hanya dipinjam selama satu transaksi query berlangsung, lalu langsung dilepas untuk dipakai oleh client lain. Ribuan koneksi client dapat dilayani hanya dengan puluhan koneksi fisik ke database.
  • ProxySQL (MySQL): proxy performa tinggi khusus ekosistem MySQL dengan kemampuan query routing dan pooling.
  • Managed Cloud Proxy: solusi terkelola dari cloud provider seperti AWS RDS Proxy atau GCP Cloud SQL Auth Proxy yang menyederhanakan manajemen koneksi untuk aplikasi serverless.

Checklist praktis ​

Berikut ringkasan praktik terbaik pengelolaan koneksi database:

  • Buat connection pool satu kali saat aplikasi booting (singleton), bukan per request handler.
  • Selalu rilis atau tutup koneksi di blok finally setelah selesai digunakan untuk mencegah terjadinya connection leak.
  • Gunakan formula (core_count × 2) / jumlah instance dari CPU database server sebagai titik awal penentuan ukuran pool per aplikasi.
  • Pastikan total kapasitas koneksi dari seluruh instance aplikasi ditambah kebutuhan maintenance tidak melebihi max_connections.
  • Sisakan headroom koneksi khusus untuk admin agar database tetap bisa diakses saat terjadi insiden.
  • Konfigurasikan batas timeout secara tepat (connection timeout, idle timeout, dan wait timeout) untuk mencegah koneksi zombie atau error koneksi terputus.
  • Gunakan koneksi terenkripsi (SSL/TLS) untuk database di production, terutama database berbasis cloud.
  • Gunakan external connection pooler (seperti PgBouncer atau RDS Proxy) jika aplikasi menggunakan arsitektur serverless atau memiliki banyak instance.
  • Ukur performa secara rutin dengan memantau metrik waktu tunggu pool, latency query, serta jumlah koneksi aktif dibanding koneksi idle.

Ringkasan tiga angka ​

ParameterSifatPeran dan Fungsinya
core_count × 2Rekomendasi kalkulasiPanduan total kapasitas pool ideal untuk server database agar eksekusi query paralel pada CPU efisien. Angka ini dibagi ke seluruh instance aplikasi.
pool_sizeKonfigurasi aplikasiBatas maksimum koneksi yang diizinkan untuk dibuka oleh satu instance aplikasi.
max_connectionsKonfigurasi databaseBatas mutlak seluruh koneksi fisik yang dapat diterima oleh server database.

Rangkuman ​

  • Koneksi database mahal karena melibatkan socket network TCP, handshake protokol, autentikasi, dan alokasi memori proses di server.
  • Connection pooling menghemat waktu dan beban server dengan menggunakan kembali koneksi yang sudah terbuka.
  • Kapasitas eksekusi query dibatasi oleh hardware CPU database, bukan oleh besarnya angka pengaturan pool di aplikasi. Memperbesar pool secara berlebihan justru memicu degradasi performa akibat context switching.
  • Selalu sediakan headroom koneksi untuk keperluan darurat dan monitoring, serta gunakan external pooler saat skala aplikasi membutuhkan ratusan hingga ribuan koneksi.

References ​