Connection pool — angka yang menentukan skala
Default database/sql adalah koneksi tak terbatas. Di laptop itu tidak terasa; di produksi dengan sepuluh container, itu cara tercepat menghabiskan slot koneksi Postgres dan menjatuhkan semuanya sekaligus.
Intisari
MaxOpenConns× jumlah task harus lebih kecil darimax_connectionsdatabase — sisakan ruang untuk migrasi dan admin.- Default
MaxIdleConnsadalah 2. Terlalu kecil: koneksi terus dibuka-tutup, dan TLS handshake-nya mahal. - Setel
MaxIdleConnssama denganMaxOpenConnsuntuk beban stabil. ConnMaxLifetimewajib di AWS — tanpa itu, failover RDS meninggalkan koneksi mati di pool.- Pool yang habis muncul sebagai semua endpoint melambat bersamaan, bukan sebagai error database.
Empat setelan
db.SetMaxOpenConns(25) // batas keras koneksi ke database
db.SetMaxIdleConns(25) // yang dipertahankan saat menganggur
db.SetConnMaxLifetime(30 * time.Minute) // umur maksimum satu koneksi
db.SetConnMaxIdleTime(5 * time.Minute) // dibuang kalau menganggur selama ini
| Setelan | Default | Kalau salah |
|---|---|---|
MaxOpenConns | tak terbatas | Database menolak koneksi; semua aplikasi yang memakainya ikut mati |
MaxIdleConns | 2 | Koneksi dibuka-tutup terus; latensi naik karena handshake TLS berulang |
ConnMaxLifetime | tak terbatas | Setelah failover RDS, koneksi lama tetap dipakai dan gagal |
ConnMaxIdleTime | tak terbatas | Koneksi menganggur menahan memori di sisi database |
Aritmetikanya
Batas database (RDS Postgres db.t4g.medium) ≈ 340 koneksi
Dikurangi cadangan:
superuser + monitoring RDS -10
task migrasi saat deploy -10
koneksi admin / psql darurat -5
------
tersedia untuk aplikasi 315
Jumlah task saat autoscaling puncak:
service web : 10 task
service worker : 4 task
service cron : 1 task
-------
15 task
MaxOpenConns maksimum = 315 / 15 = 21 → pakai 20
Yang paling sering dilupakan: max_connections di RDS ditentukan ukuran instans.
Menaikkan jumlah task ECS dari 4 jadi 20 saat trafik melonjak juga melipatgandakan kebutuhan
koneksi — dan yang terjadi bukan aplikasi melambat, tapi database menolak koneksi baru sehingga
seluruh layanan (termasuk yang tadinya sehat) ikut gagal. Hitung untuk jumlah task
maksimum, bukan yang sekarang.
Berapa nilai yang tepat
| Beban | MaxOpenConns per task |
|---|---|
| API baca-berat, kueri cepat (< 10 ms) | 10–25 |
| Aplikasi campuran | 20–30 |
| Worker batch, kueri panjang | 5–10 (kueri lama menahan koneksi) |
| Lambda | 1–2, atau pakai RDS Proxy |
Lebih banyak koneksi tidak berarti lebih cepat. PostgreSQL memakai satu proses per koneksi;
di luar titik tertentu, menambah koneksi justru menurunkan throughput karena penjadwalan dan penguncian
internal. Rumus yang sering dipakai sebagai titik awal adalah sekitar (2 × core) + jumlah
disk untuk seluruh aplikasi digabung — angka yang jauh lebih kecil daripada dugaan
kebanyakan orang.
Kenapa ConnMaxLifetime wajib di AWS
t=0 Aplikasi punya 25 koneksi ke writer Aurora
t=100 Aurora failover: reader lama dipromosikan jadi writer
t=101 Endpoint DNS menunjuk instans baru
t=102 Aplikasi masih memakai 25 koneksi ke instans LAMA
→ semua tulisan gagal "read-only transaction"
→ dan tidak akan pulih sendiri, karena koneksinya tidak pernah kedaluwarsa
SetConnMaxLifetime(30 * time.Minute) memaksa setiap koneksi dibuang dan dibangun ulang secara
berkala, sehingga DNS di-resolve lagi dan pool berpindah sendiri ke instans yang benar. Nilainya harus
lebih pendek daripada batas apa pun yang memutus koneksi dari sisi lain: idle_timeout
di RDS Proxy, timeout NLB, atau tcp_keepalive jaringan.
Untuk pemulihan lebih cepat, pakai endpoint yang tepat. Aurora punya cluster endpoint
(selalu writer) dan reader endpoint. Arahkan pool tulis ke yang pertama dan pool baca ke yang
kedua — dua *sql.DB terpisah — supaya beban baca pindah ke replica tanpa mengubah kode
domain.
Memantau pool
s := db.Stats()
s.OpenConnections // koneksi terbuka saat ini
s.InUse // sedang dipakai kueri
s.Idle // menganggur di pool
s.WaitCount // BERAPA KALI ada yang antre menunggu koneksi
s.WaitDuration // TOTAL waktu yang dihabiskan menunggu
s.MaxIdleClosed // ditutup karena MaxIdleConns
s.MaxLifetimeClosed // ditutup karena ConnMaxLifetime
WaitCount dan WaitDuration adalah dua metrik terpenting di seluruh
lapisan database. Selama keduanya nol, poolmu cukup. Begitu naik, setiap permintaan mulai
menghabiskan waktunya menunggu giliran — dan itu muncul sebagai p95 yang meledak sementara p50
masih terlihat sehat. Ekspor keduanya ke CloudWatch dan pasang alarm (Fase 9).
// Ekspor berkala sebagai metrik
go func() {
for range time.Tick(15 * time.Second) {
s := db.Stats()
metrik.Gauge("db.pool.in_use", float64(s.InUse))
metrik.Gauge("db.pool.idle", float64(s.Idle))
metrik.Gauge("db.pool.wait_count", float64(s.WaitCount))
}
}()
Gejala pool habis
| Yang terlihat | Yang sebenarnya terjadi |
|---|---|
| Semua endpoint melambat bersamaan | Semua antre di pool yang sama |
| Endpoint tanpa database ikut lambat | Bukan pool — cek CPU atau GC |
Error context deadline exceeded pada kueri sepele | Waktu habis saat menunggu koneksi, kuerinya sendiri belum jalan |
| Membaik setelah restart, memburuk lagi setelah beberapa jam | Koneksi bocor — rows.Close() yang hilang |
| Database bilang "too many connections" | MaxOpenConns × jumlah task melebihi kapasitas |
RDS Proxy: kapan perlu
| Situasi | RDS Proxy? |
|---|---|
| ECS/EC2 dengan jumlah task stabil | Tidak perlu — pool aplikasi sudah cukup |
| Lambda | Ya — tiap eksekusi punya poolnya sendiri, jumlahnya meledak |
| Autoscaling agresif (2 → 50 task) | Ya — meratakan lonjakan koneksi |
| Butuh failover lebih cepat | Ya — proxy menahan koneksi klien selama failover |
Latihan: setel SetMaxOpenConns(2) lalu jalankan 20 permintaan bersamaan ke
endpoint yang menjalankan SELECT pg_sleep(1). Cetak db.Stats() setiap 500 ms
dan amati WaitCount naik. Naikkan ke 20 dan ulangi. Terakhir, hitung
MaxOpenConns yang benar untuk aplikasimu memakai aritmetika di atas.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.