Server lu bisa punya 16 core, tapi backend tetap mentok kalau bottleneck-nya hidup di satu proses kecil yang jarang lu lihat: connection pooler.

Artikel ini bahas kenapa PgBouncer bisa jadi leher botol di stack PostgreSQL, terutama saat traffic sudah cukup besar dan proses pooler cuma efektif memakai satu CPU core. Intinya bukan “semua orang harus ganti arsitektur PgBouncer”, tapi cara membaca bottleneck dengan lebih waras: cek proses mana yang benar-benar penuh, lihat limit koneksi, pahami mode pooling, dan jangan cuma menyalahkan database utama. Pelajaran dari ClickHouse Managed Postgres menarik karena mereka menunjukkan pattern multi-process dengan SO_REUSEPORT, tapi tetap ada edge case yang harus diselesaikan seperti query cancellation.

Kenapa connection pooler bisa jadi bottleneck?

Connection pooler adalah lapisan di depan PostgreSQL yang tugasnya mengelola koneksi dari aplikasi ke database. Buat banyak aplikasi web, ini penting karena koneksi database tidak gratis: tiap koneksi punya biaya memory, scheduling, dan overhead di server.

PgBouncer populer karena ringan dan praktis. Banyak tim memakainya sebagai “rem” supaya aplikasi tidak membuka koneksi PostgreSQL terlalu banyak. Masalahnya, karena PgBouncer didesain ringan, banyak setup menjalankannya sebagai satu proses utama. Saat traffic naik, satu proses itu bisa mentok duluan sebelum PostgreSQL-nya benar-benar kehabisan napas.

Ini bagian yang sering bikin debugging nyasar: CPU database kelihatan belum penuh, app server sudah ditambah, tapi latency tetap naik. Kalau lu cuma lihat PostgreSQL, lu bisa salah menyimpulkan bahwa query-nya jelek atau database perlu mesin lebih besar. Padahal bisa jadi request lu sedang antre di middleware yang CPU-nya sudah 100% di satu core.

Pelajaran dari ClickHouse Managed Postgres

ClickHouse menulis engineering post tentang cara mereka menskalakan PgBouncer untuk ClickHouse Managed Postgres. Poin besarnya sederhana: satu proses PgBouncer bisa menjadi batas throughput karena single-threaded, jadi mereka menjalankan beberapa proses PgBouncer dalam satu fleet dan membiarkan kernel membagi koneksi masuk dengan SO_REUSEPORT.

Dengan cara ini, client tetap melihat satu endpoint. Di belakangnya, beberapa proses PgBouncer bisa bind ke port yang sama, lalu kernel mendistribusikan koneksi baru ke proses-proses tersebut. Dalam benchmark yang mereka tulis, setup 16 vCPU naik dari sekitar 87 ribu transactions per second di single process menjadi sekitar 336 ribu transactions per second dengan 16 proses. Angka ini bukan janji universal, tapi sinyal kuat bahwa bottleneck pooler bisa nyata.

Yang gua suka dari cerita ini: mereka tidak berhenti di “naik 4x, selesai”. Mereka juga membahas edge case. Salah satunya PostgreSQL cancel request. Kalau query berjalan di satu proses PgBouncer, lalu request cancel masuk ke proses lain, cancel itu bisa gagal kalau antarproses tidak saling tahu state query. ClickHouse menyelesaikannya dengan peering antarproses.

Di situ pelajarannya kerasa: performance engineering yang sehat bukan cuma ngejar angka benchmark. Lu juga harus cek semantik sistemnya tetap benar saat ada cancel, timeout, restart, deploy, dan failure mode lain.

Jangan langsung copy pattern multi-process

Kalau traffic aplikasi lu masih kecil, single PgBouncer bisa sangat cukup. Jangan jadikan artikel ini alasan buat bikin arsitektur rumit cuma karena terlihat keren. Multi-process pooler punya biaya operasional: konfigurasi lebih kompleks, observability harus lebih rapi, dan total koneksi ke PostgreSQL tetap harus dihitung.

Yang penting adalah mental model-nya: scaling CPU tidak otomatis berguna kalau proses yang menjadi bottleneck tidak bisa memakai semua CPU itu. Lu bisa bayar mesin 16 core, tapi kalau hot path lu cuma jalan efektif di satu core, sisa core-nya cuma jadi dekorasi mahal.

Ini berlaku bukan cuma buat PgBouncer. Banyak komponen kecil di stack backend punya limit serupa: reverse proxy, queue worker, log shipper, auth middleware, rate limiter, atau sidecar yang kelihatan “bukan database”, tapi ternyata dilewati semua request.

Cara ngecek apakah bottleneck lu ada di pooler

Sebelum menyalahkan PostgreSQL atau buru-buru upgrade instance, gua bakal cek hal-hal ini dulu:

  • CPU per proses, bukan cuma CPU mesin. Lihat apakah satu proses PgBouncer mentok 100% di satu core sementara total CPU masih terlihat rendah.
  • Queueing dan wait time. Cek apakah client menunggu koneksi pool terlalu lama, bukan menunggu query database selesai.
  • Mode pooling. PgBouncer punya session pooling, transaction pooling, dan statement pooling. Tiap mode punya tradeoff kompatibilitas dan performa.
  • Jumlah koneksi aktual ke PostgreSQL. Jangan cuma menaikkan pool size. Kalau total koneksi ke database kebanyakan, lu memindahkan bottleneck ke PostgreSQL.
  • Timeout dan cancel behavior. Pastikan query yang dibatalkan benar-benar berhenti, terutama kalau ada proxy/pooler di tengah.
  • Metrics pooler. Simpan dashboard untuk active clients, waiting clients, server connections, average wait, error, dan restart.

Checklist ini lebih berguna daripada pertanyaan “PgBouncer bagus atau jelek?”. Tool jarang bermasalah secara absolut. Yang sering bermasalah adalah posisi tool itu di traffic path dan asumsi lu tentang kapasitasnya.

Definisi singkat: PgBouncer, pooling, dan SO_REUSEPORT

PgBouncer adalah connection pooler ringan untuk PostgreSQL. Ia menerima koneksi dari aplikasi, lalu mengatur pemakaian koneksi ke PostgreSQL supaya database tidak dibanjiri koneksi mentah.

Connection pooling adalah teknik memakai ulang koneksi yang sudah ada, alih-alih membuka koneksi baru terus-menerus. Ini biasanya menurunkan overhead dan membuat database lebih stabil saat traffic naik.

SO_REUSEPORT adalah opsi socket di Linux yang memungkinkan beberapa socket bind ke alamat dan port yang sama. Dalam konteks server multi-process, ini bisa dipakai supaya beberapa proses menerima koneksi dari port yang sama, dengan distribusi yang dibantu kernel.

Gabungan tiga konsep ini kelihatan sederhana, tapi detailnya tidak boleh diremehkan. Begitu lu menaruh pooler di jalur semua request, pooler itu bukan “komponen kecil” lagi. Dia jadi bagian dari reliability story aplikasi lu.

Takeaway buat backend developer

Kalau lu sedang debugging performa backend, jangan cuma tanya “query mana yang lambat?”. Tanya juga “request ini lewat komponen apa saja sebelum sampai database?”. Kadang jawaban paling mahal justru ada di komponen yang selama ini lu anggap transparan.

Gua akan merangkum pelajaran utamanya begini:

  • Bottleneck bisa ada di middleware. Database utama belum tentu sumber masalah.
  • CPU total bisa menipu. Lihat penggunaan CPU per proses dan per core.
  • Pooling bukan magic. Pooler mengurangi overhead koneksi, tapi juga bisa menjadi antrean baru.
  • Benchmark perlu konteks. Angka 4x dari ClickHouse menarik, tapi workload lu bisa beda.
  • Reliability lebih penting dari trik performa. Cancel request, timeout, observability, dan failure mode harus tetap aman.

FAQ singkat

Apakah semua aplikasi PostgreSQL butuh PgBouncer?

Tidak selalu. Kalau aplikasi lu kecil, koneksi terkendali, dan PostgreSQL masih sehat, lu mungkin belum butuh PgBouncer. PgBouncer mulai menarik saat jumlah koneksi aplikasi sulit diprediksi atau database mulai terbebani overhead koneksi.

Apakah multi-process PgBouncer selalu lebih baik?

Tidak. Multi-process bisa membantu throughput di workload tertentu, tapi juga menambah kompleksitas. Lu perlu observability, hitungan total koneksi, dan pemahaman edge case seperti query cancellation.

Kalau backend lambat, apa langkah pertama?

Mulai dari tracing jalur request: app server, network, pooler, database, cache, dan service lain. Jangan langsung upgrade database sebelum tahu komponen mana yang benar-benar penuh.

Kenapa ini relevan buat developer yang belum scale besar?

Karena mental model-nya kepakai dari awal. Lu belajar melihat sistem sebagai rangkaian komponen, bukan cuma “app + database”. Ini bikin debugging lu lebih tajam saat traffic mulai naik.

Penutup

Menurut gua, cerita PgBouncer dari ClickHouse ini menarik bukan karena semua orang harus meniru implementasinya, tapi karena dia mengingatkan hal yang sering dilupakan: backend performance itu soal jalur request penuh, bukan satu komponen favorit.

Kalau lu lagi membangun produk, side project, atau sistem internal, biasakan bikin peta traffic sederhana. Dari browser ke API, dari API ke pooler, dari pooler ke database, dari database balik lagi ke user. Setelah itu baru tanya: komponen mana yang bisa antre, komponen mana yang single-process, dan komponen mana yang belum punya dashboard.

Itu bukan kerja “enterprise doang”. Itu kebiasaan builder yang pengen sistemnya bisa dipercaya.

Referensi / Bacaan Lanjut