Connection Pooling #

Ketika developer pertama kali merasakan database yang mulai lambat di produksi, reaksi yang paling umum adalah melihat ke query: apakah ada yang tidak pakai index, apakah ada N+1, apakah ada yang full table scan. Semua itu valid — tapi ada masalah lain yang lebih sering menjadi penyebab, lebih jarang didiagnosis, dan dampaknya lebih parah: manajemen koneksi yang buruk.

Setiap koneksi ke database bukan sekadar “sambungan” yang terbentuk instan. Di baliknya ada TCP handshake, TLS negotiation, autentikasi, alokasi thread dan memory di server database. Semua ini butuh waktu — biasanya antara 20 sampai 100 milidetik per koneksi. Kalikan dengan ratusan request per detik, dan kamu punya overhead yang sangat nyata.

Connection pooling memecahkan masalah ini dengan cara yang elegan: buat sejumlah koneksi di awal, simpan, dan gunakan ulang berulang kali. Request tidak perlu membuka koneksi baru — ia meminjam yang sudah ada, pakai, lalu kembalikan. Tapi pool yang tidak dikonfigurasi dengan benar bisa menjadi masalah baru yang lebih serius dari masalah yang ia selesaikan: database diserbu ribuan koneksi sekaligus, memory habis, dan sistem kolaps.

Mengapa Membuka Koneksi Itu Mahal #

Untuk memahami nilai connection pooling, perlu dipahami dulu berapa banyak pekerjaan yang sebenarnya terjadi setiap kali sebuah koneksi database dibuka.

Proses membuka satu koneksi database:

  Aplikasi                              Database Server
  ─────────                             ───────────────
  1. Resolve hostname (DNS)
  2. Buka socket TCP
  3. TCP 3-way handshake ────────────→  terima handshake
  4. TLS ClientHello ────────────────→  TLS negotiation
     ←─────────────────────────────── TLS ServerHello + Certificate
  5. TLS Finished ───────────────────→  ←─ TLS Finished
  6. Kirim credentials ──────────────→  verifikasi user + password
     ←─────────────────────────────── auth ok, kirim server capabilities
  7. Pilih database ─────────────────→  alokasi thread
     ←─────────────────────────────── siap
  8. [koneksi siap digunakan]

  Estimasi waktu total: 20–100ms
  Estimasi memory di DB server: 1–8 MB per koneksi (tergantung konfigurasi)

Untuk satu koneksi, 50ms masih terasa wajar. Tapi di sistem dengan 500 request per detik yang masing-masing membuka koneksi baru, ini berarti 25 detik CPU time per detik hanya untuk handshake — belum satu pun query dijalankan.

Tanpa connection pooling, pola yang terjadi adalah ini:

Tanpa pooling — setiap request membuka koneksi baru:

  Request 1 ─→ open conn (50ms) → query (5ms) → close conn → total: 55ms
  Request 2 ─→ open conn (50ms) → query (5ms) → close conn → total: 55ms
  Request 3 ─→ open conn (50ms) → query (5ms) → close conn → total: 55ms

  Overhead koneksi: 50ms dari 55ms = 91% waktu dihabiskan bukan untuk query

Dengan pooling — koneksi digunakan ulang:

  Request 1 ─→ borrow conn (0.1ms) → query (5ms) → return → total: 5.1ms
  Request 2 ─→ borrow conn (0.1ms) → query (5ms) → return → total: 5.1ms
  Request 3 ─→ borrow conn (0.1ms) → query (5ms) → return → total: 5.1ms

  Overhead koneksi: 0.1ms dari 5.1ms = 2% — hampir tidak terasa

Cara Kerja Pool: Lifecycle Sebuah Koneksi #

Connection pool memiliki siklus hidup yang perlu dipahami untuk bisa mengkonfigurasinya dengan benar.

Lifecycle koneksi di dalam pool:

  Startup aplikasi:
  ┌────────────────────────────────────────────────────────┐
  │  Pool dibuat dengan min_size koneksi (misalnya: 5)     │
  │  conn-1 [idle]  conn-2 [idle]  conn-3 [idle]           │
  │  conn-4 [idle]  conn-5 [idle]                          │
  └────────────────────────────────────────────────────────┘

  Request datang:
  ┌─────────────────────────────────────────────────────────┐
  │  Request A meminjam conn-1 → status: [in use]           │
  │  Request B meminjam conn-2 → status: [in use]           │
  │  conn-3 [idle]  conn-4 [idle]  conn-5 [idle]            │
  └─────────────────────────────────────────────────────────┘

  Request selesai:
  ┌─────────────────────────────────────────────────────────┐
  │  Request A selesai → conn-1 dikembalikan → [idle]       │
  │  Pool: conn-1 [idle]  conn-2 [in use]  conn-3 [idle]... │
  └─────────────────────────────────────────────────────────┘

  Pool penuh (semua in use) dan request baru datang:
  ┌─────────────────────────────────────────────────────────┐
  │  Request baru menunggu                                  │
  │  ├─ Jika ada yang selesai sebelum timeout → dapat conn  │
  │  └─ Jika timeout terlewat → error: pool exhausted       │
  └─────────────────────────────────────────────────────────┘

Ada lima parameter konfigurasi yang paling berpengaruh di hampir semua library pooling:

Parameter pool yang penting:

  min_connections (atau initial_size)
    → Jumlah koneksi yang dibuat saat aplikasi start
    → Pastikan DB tidak kehabisan resource saat banyak instance startup bersamaan

  max_connections (atau pool_size)
    → Batas maksimum koneksi yang boleh ada di pool
    → Ini angka yang paling krusial — terlalu besar bisa membunuh DB

  max_idle_connections
    → Berapa koneksi idle yang dibiarkan tetap terbuka
    → Sisanya ditutup jika tidak digunakan dalam waktu tertentu

  connection_timeout
    → Berapa lama request menunggu koneksi tersedia sebelum error
    → Jangan biarkan terlalu lama di endpoint user-facing

  max_lifetime (atau max_age)
    → Berapa lama sebuah koneksi boleh hidup sebelum diganti baru
    → Penting untuk menghindari koneksi stale akibat network timeout atau
      perubahan konfigurasi DB

Pooling di Aplikasi Non-Distributed #

Untuk aplikasi dengan satu instance — monolith, internal tool, admin panel — connection pooling relatif sederhana. Ada satu pool di satu proses, dan satu-satunya hal yang perlu dipikirkan adalah ukuran pool yang tepat.

# Contoh konfigurasi pool di SQLAlchemy (Python)
from sqlalchemy import create_engine

engine = create_engine(
    "mysql+pymysql://user:password@localhost/dbname",
    pool_size=10,          # koneksi yang selalu dipertahankan
    max_overflow=5,        # koneksi tambahan yang boleh dibuat saat pool penuh
    pool_timeout=30,       # detik menunggu koneksi tersedia sebelum error
    pool_recycle=1800,     # recycle koneksi setelah 30 menit (cegah stale conn)
    pool_pre_ping=True,    # test koneksi sebelum dipakai (cegah conn mati)
)

# Total koneksi maksimum: pool_size + max_overflow = 15
// Contoh konfigurasi pool di Go (database/sql)
import "database/sql"

db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
    log.Fatal(err)
}

db.SetMaxOpenConns(15)           // max koneksi aktif (= max_connections)
db.SetMaxIdleConns(10)           // max koneksi idle yang dipertahankan
db.SetConnMaxLifetime(30 * time.Minute) // max umur koneksi sebelum diganti
db.SetConnMaxIdleTime(10 * time.Minute) // max idle time sebelum koneksi ditutup

Menentukan Ukuran Pool yang Tepat #

Tidak ada angka ajaib yang berlaku untuk semua sistem. Tapi ada formula sederhana sebagai titik awal:

Formula awal pool size (non-distributed):

  Untuk aplikasi CPU-bound (banyak komputasi, sedikit query):
    max_pool = jumlah_cpu_core * 2

  Untuk aplikasi IO-bound (banyak query, sedikit komputasi):
    max_pool = jumlah_cpu_core * 2 + jumlah_disk_spindle

  Rule of thumb yang lebih praktis:
    Mulai dari pool_size = 10
    Monitor utilization pool di produksi
    Naikan jika sering penuh, turunkan jika banyak idle

  Yang harus dihindari:
    ✗ Pool size = max_connection database (membuat satu instance monopoli DB)
    ✗ Pool size terlalu besar (semua instance dijumlah melebihi kapasitas DB)
    ✗ Pool size = 1 (semua request antre satu per satu)
Penelitian dari HikariCP (connection pool Java yang populer) menunjukkan bahwa untuk sebagian besar aplikasi web, pool size yang relatif kecil — bahkan antara 5 sampai 10 — sudah cukup dan sering memberikan performa yang lebih baik dari pool yang besar. Database lebih efisien melayani sedikit koneksi yang aktif daripada banyak koneksi yang bersaing untuk resource yang sama.

Pooling di Aplikasi Distributed: Sumber Masalah yang Paling Sering Diabaikan #

Di sistem monolith, pool size yang terlalu besar paling buruk hanya membebani satu database dengan lebih banyak koneksi dari yang diperlukan. Di sistem distributed — microservices, Kubernetes, auto-scaling — kesalahan konfigurasi pool bisa menyebabkan connection explosion: database diserbu ribuan koneksi sekaligus, memory habis, dan seluruh sistem kolaps.

Skenario connection explosion:

  Konfigurasi awal (terlihat aman):
    max_pool per instance = 20
    Jumlah pod = 10
    Total koneksi ke DB = 200 ← masih dalam batas max_connections DB (250)

  Auto-scaling saat traffic naik:
    max_pool per instance = 20
    Jumlah pod = 20 (scale up karena traffic)
    Total koneksi ke DB = 400 ← MELEBIHI kapasitas DB!

  Hasil:
  ┌─────────────────────────────────────────────────────────────┐
  │  DB menolak koneksi baru: "Too many connections"            │
  │  Semua pod mendapat error koneksi                           │
  │  Error cascade: setiap retry membuat situasi makin buruk    │
  │  Sistem down — padahal query-nya tidak ada masalah sama sekali│
  └─────────────────────────────────────────────────────────────┘

Ironisnya, ini terjadi tepat di saat sistem paling butuh kapasitas — ketika traffic sedang naik. Auto-scaling yang seharusnya menyelamatkan sistem justru menjadi trigger yang menghancurkannya.

Formula Sizing untuk Distributed System #

Di distributed system, kamu harus berpikir dari sisi database, bukan dari sisi instance:

Formula sizing pool untuk distributed system:

  Langkah 1: tentukan max_connections database
    Biasanya bisa dilihat di: SHOW VARIABLES LIKE 'max_connections';
    Sisakan ~20% untuk operasi admin dan monitoring
    Usable connections = max_connections * 0.8

  Langkah 2: tentukan max pod yang mungkin terjadi
    Bukan pod saat ini — tapi pod maksimum saat auto-scaling penuh

  Langkah 3: hitung pool per instance
    max_pool_per_instance = usable_connections / max_pods

  Contoh nyata:
    max_connections DB      = 500
    Usable connections      = 500 * 0.8 = 400
    Max pods (saat scale-up)= 50
    max_pool_per_instance   = 400 / 50 = 8

  → Setiap pod dikonfigurasi max_pool = 8
  → Saat 50 pod aktif: total koneksi = 400 ← aman
  → Saat 30 pod aktif: total koneksi = 240 ← juga aman
Visualisasi perbandingan sebelum dan sesudah sizing yang benar:

  Sebelum (pool besar per instance):
  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐
  │Pod (20)│  │Pod (20)│  │Pod (20)│  │Pod (20)│  ← 20 pod
  └───┬────┘  └───┬────┘  └───┬────┘  └───┬────┘
      │            │            │            │
      └────────────┴────────────┴────────────┘
                        │
                   ┌────▼────┐
                   │Database │  ← menerima 400 koneksi dari 20 pod
                   │max: 250 │  ← OVERLOAD
                   └─────────┘

  Sesudah (pool kecil per instance + external pooler):
  ┌───────┐  ┌───────┐  ┌───────┐  ┌───────┐
  │Pod (5)│  │Pod (5)│  │Pod (5)│  │Pod (5)│  ← 20 pod
  └───┬───┘  └───┬───┘  └───┬───┘  └───┬───┘
      │           │           │           │
      └───────────┴───────────┴───────────┘
                       │
                  ┌────▼────┐
                  │ PgBouncer│  ← external pooler: multiplexing koneksi
                  │/ ProxySQL│
                  └────┬─────┘
                       │ 50 koneksi yang dikelola efisien
                  ┌────▼────┐
                  │Database │  ← menerima koneksi yang terkontrol
                  │max: 250 │  ← AMAN
                  └─────────┘

External Pooler: Solusi untuk Distributed System #

Ketika jumlah instance banyak dan tidak bisa diprediksi — terutama di environment dengan auto-scaling — external pooler adalah solusi yang paling robust. External pooler adalah proxy yang duduk di antara aplikasi dan database, mengelola pool koneksi secara terpusat.

PgBouncer untuk PostgreSQL #

PgBouncer adalah external pooler paling populer untuk PostgreSQL. Ia mendukung tiga mode pooling:

Mode pooling PgBouncer:

  Session pooling (default):
    Satu koneksi DB dialokasikan per session client selama session aktif.
    Mirip dengan pool di aplikasi, tapi dikelola secara terpusat.
    Cocok untuk: aplikasi yang menggunakan fitur session-level (prepared statements, dll)

  Transaction pooling:
    Koneksi DB dialokasikan hanya selama transaction berlangsung.
    Setelah COMMIT/ROLLBACK, koneksi dikembalikan ke pool.
    Cocok untuk: aplikasi yang mayoritas query-nya singkat.
    ⚠ Tidak kompatibel dengan: SET, advisory locks, LISTEN/NOTIFY

  Statement pooling:
    Koneksi DB dialokasikan hanya untuk satu statement.
    Paling efisien, tapi paling banyak limitasinya.
    Jarang digunakan di aplikasi modern.
# Contoh konfigurasi PgBouncer (pgbouncer.ini)
[databases]
mydb = host=db-primary port=5432 dbname=myapp

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction        # gunakan transaction pooling untuk efisiensi maksimal
max_client_conn = 1000         # max koneksi dari aplikasi ke PgBouncer
default_pool_size = 50         # koneksi dari PgBouncer ke database
min_pool_size = 10             # koneksi minimum yang selalu dipertahankan
reserve_pool_size = 5          # koneksi cadangan untuk burst
server_idle_timeout = 600      # tutup koneksi idle ke DB setelah 10 menit

ProxySQL untuk MySQL #

ProxySQL adalah external pooler untuk MySQL yang juga mendukung fitur routing read/write secara otomatis.

Keunggulan ProxySQL dibanding konfigurasi pool di aplikasi:

  ✓ Routing otomatis: query SELECT → replica, query write → primary
  ✓ Connection multiplexing: 1000 koneksi aplikasi bisa di-multiplex ke 50 koneksi DB
  ✓ Query caching: hasil query yang sering diulang bisa di-cache
  ✓ Query rewriting: ubah query tanpa mengubah kode aplikasi
  ✓ Monitoring dan statistik terpusat
  ✓ Failover otomatis saat primary down

Dengan external pooler, konfigurasi pool di sisi aplikasi bisa dikecilkan secara signifikan:

# Dengan external pooler di depan DB, pool di aplikasi bisa jauh lebih kecil
engine = create_engine(
    "mysql+pymysql://user:password@proxysql-host:6033/dbname",
    pool_size=5,           # kecil — karena multiplexing dilakukan di ProxySQL
    max_overflow=2,        # overflow minimal
    pool_timeout=10,       # timeout lebih ketat — external pooler yang handle antrean
    pool_recycle=900,      # 15 menit — ProxySQL juga punya idle timeout
    pool_pre_ping=True,
)

Connection Leak: Bug yang Paling Diam-diam Mematikan #

Connection leak terjadi ketika koneksi yang dipinjam dari pool tidak pernah dikembalikan — karena exception yang tidak tertangani, return path yang terlewat, atau koneksi yang disimpan di variable dan di-garbage-collect tanpa ditutup.

# ANTI-PATTERN: koneksi bisa leak jika ada exception sebelum close()
def get_user(user_id):
    conn = pool.get_connection()
    result = conn.execute("SELECT * FROM users WHERE id = ?", user_id)
    # Jika baris berikutnya melempar exception, conn.close() tidak dipanggil
    data = process_result(result)   # ← exception di sini?
    conn.close()                    # ← tidak pernah dipanggil jika ada exception
    return data

# BENAR: gunakan context manager yang menjamin koneksi selalu dikembalikan
def get_user(user_id):
    with pool.get_connection() as conn:  # __exit__ dipanggil otomatis, bahkan saat exception
        result = conn.execute("SELECT * FROM users WHERE id = ?", user_id)
        return process_result(result)
    # conn otomatis dikembalikan ke pool saat keluar dari blok 'with'
// ANTI-PATTERN di Go: rows tidak di-close jika ada early return atau panic
func getUser(db *sql.DB, userID int) (*User, error) {
    rows, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
    if err != nil {
        return nil, err
    }
    // Jika ada early return atau panic di bawah, rows tidak pernah di-close
    // Koneksi tetap "in use" di pool meski fungsi sudah selesai
    if !rows.Next() {
        return nil, nil  // ← rows tidak di-close!
    }
    // ...
    rows.Close()
    return user, nil
}

// BENAR: defer rows.Close() dipanggil segera setelah rows dibuat
func getUser(db *sql.DB, userID int) (*User, error) {
    rows, err := db.Query("SELECT * FROM users WHERE id = ?", userID)
    if err != nil {
        return nil, err
    }
    defer rows.Close()  // ← selalu dipanggil, tidak peduli bagaimana fungsi selesai

    if !rows.Next() {
        return nil, nil
    }
    // ...
    return user, nil
}

Tanda-tanda connection leak yang sudah terjadi:

Gejala connection leak di produksi:

  ✗ Jumlah "active connections" di DB terus naik seiring waktu
  ✗ Pool exhaustion terjadi meski traffic tidak naik
  ✗ Restart aplikasi memperbaiki masalah sementara, lalu muncul lagi
  ✗ SHOW PROCESSLIST di MySQL menampilkan banyak koneksi dengan state "Sleep"
    yang sudah idle sangat lama

Mendiagnosis Masalah Pool di Produksi #

Ketika ada masalah yang dicurigai berhubungan dengan connection pooling, ada beberapa query dan perintah yang bisa dijalankan untuk mendiagnosis situasinya.

-- Cek total koneksi aktif ke database (MySQL)
SHOW STATUS LIKE 'Threads_connected';
-- Bandingkan dengan: SHOW VARIABLES LIKE 'max_connections';
-- Jika Threads_connected mendekati max_connections → pool sedang kritis

-- Lihat semua koneksi aktif dan statusnya
SHOW PROCESSLIST;
-- Perhatikan kolom 'Command' dan 'Time':
-- 'Sleep' dengan Time tinggi → idle connection yang mungkin tidak dikembalikan
-- 'Query' dengan Time tinggi → query lambat yang menahan koneksi

-- Statistik connection pool (untuk aplikasi yang meng-expose metrics)
-- Metrik yang penting:
-- pool_size: total koneksi di pool
-- pool_checkout_count: berapa kali koneksi dipinjam
-- pool_checkin_count: berapa kali dikembalikan (harus mendekati checkout_count)
-- pool_overflow_count: berapa kali pool harus membuat koneksi di luar pool_size
-- pool_timeout_count: berapa kali request timeout menunggu koneksi

-- Cek max_connections yang dikonfigurasi di MySQL
SHOW VARIABLES LIKE 'max_connections';

-- Cek persentase penggunaan connection
SELECT
    VARIABLE_VALUE AS threads_connected
FROM information_schema.GLOBAL_STATUS
WHERE VARIABLE_NAME = 'Threads_connected';

Anti-Pattern yang Harus Dihindari #

# ✗ Anti-pattern 1: membuka koneksi baru di setiap fungsi tanpa pool
def get_orders(user_id):
    conn = create_new_connection()   # membuka koneksi baru setiap kali
    result = conn.query("SELECT * FROM orders WHERE user_id = ?", user_id)
    conn.close()
    return result
# Overhead koneksi 50ms ditambahkan ke setiap pemanggilan fungsi ini

# ✓ Solusi: inject pool atau engine ke fungsi, gunakan ulang koneksi dari pool
def get_orders(db_pool, user_id):
    with db_pool.get_connection() as conn:
        return conn.query("SELECT * FROM orders WHERE user_id = ?", user_id)

────────────────────────────────────────────────────────────────────────────────

# ✗ Anti-pattern 2: pool size = max_connections database
# DB max_connections = 200, pool di satu aplikasi dikonfigurasi pool_size = 200
# Aplikasi ini memonopoli semua koneksi DB → aplikasi lain tidak bisa connect

# ✓ Solusi: satu instance tidak boleh memakai lebih dari 50-70% kapasitas DB

────────────────────────────────────────────────────────────────────────────────

# ✗ Anti-pattern 3: tidak ada connection timeout
engine = create_engine(url, pool_size=20)
# Jika pool penuh, request menunggu selamanya → goroutine/thread leak
# → memory terus naik → OOM

# ✓ Solusi: selalu set pool_timeout yang sesuai dengan SLA
engine = create_engine(url, pool_size=20, pool_timeout=5)
# Setelah 5 detik tidak dapat koneksi → error yang jelas, bukan tunggu selamanya

────────────────────────────────────────────────────────────────────────────────

# ✗ Anti-pattern 4: long transaction di endpoint high-traffic
# Setiap request memegang koneksi selama 3 detik (karena ada HTTP call di dalam transaction)
# Dengan pool_size=10: hanya 10 request yang bisa berjalan bersamaan
# Semua request lain menunggu → bottleneck parah

# ✓ Solusi: operasi eksternal di luar transaction, transaction sesingkat mungkin

────────────────────────────────────────────────────────────────────────────────

# ✗ Anti-pattern 5: scaling aplikasi tanpa mempertimbangkan total koneksi ke DB
# Sebelum scaling: 5 pod × pool_size=20 = 100 koneksi (aman)
# Setelah scaling: 20 pod × pool_size=20 = 400 koneksi (DB max=200 → OVERLOAD)

# ✓ Solusi: hitung total koneksi dari semua instance
# Turunkan pool_size per instance atau pasang external pooler

Checklist Review Connection Pooling #

KONFIGURASI DASAR:
  □ Pool size sudah dihitung berdasarkan kapasitas DB dan jumlah instance
  □ Pool timeout dikonfigurasi dengan nilai yang sesuai SLA (bukan default atau tidak ada)
  □ max_lifetime atau pool_recycle dikonfigurasi untuk mencegah stale connection
  □ pool_pre_ping atau validation query diaktifkan untuk mendeteksi koneksi mati

CONNECTION LEAK:
  □ Semua tempat yang menggunakan koneksi menggunakan context manager atau finally block
  □ Tidak ada koneksi yang disimpan di global state atau class field
  □ Semua rows/cursor/result set di-close setelah selesai digunakan
  □ Tidak ada early return path yang bisa membuat koneksi tidak dikembalikan

DISTRIBUTED SYSTEM:
  □ Pool size dihitung secara global: total_koneksi = pool_per_instance × max_instances
  □ Total koneksi maksimum tidak melebihi max_connections DB (sisakan 20% untuk admin)
  □ Pool size per instance sudah diperkecil untuk mengakomodasi max_instances saat scale-up
  □ External pooler (PgBouncer/ProxySQL) dipertimbangkan jika instance > 10

TRANSACTION MANAGEMENT:
  □ Transaction sesingkat mungkin — tidak ada operasi eksternal di dalamnya
  □ Tidak ada long-running query yang tidak perlu memegang koneksi lama
  □ Read pool dan write pool dipisah jika ada replication

MONITORING:
  □ Jumlah koneksi aktif ke DB dimonitor (alert jika mendekati max_connections)
  □ Pool utilization dimonitor (checkout count, timeout count, overflow count)
  □ Slow query yang menahan koneksi lama dimonitor via slow query log
  □ Jumlah idle connection di DB dimonitor (tanda potensial connection leak)

Ringkasan #

  • Membuka koneksi database mahal — 20–100ms per koneksi untuk TCP handshake, TLS, dan autentikasi. Connection pooling menghilangkan overhead ini dengan menggunakan ulang koneksi yang sudah ada.
  • Pool size bukan “semakin besar semakin baik” — terlalu besar menghabiskan resource DB dan menurunkan efisiensi. Mulai dari pool kecil (5–10) dan naikan berdasarkan monitoring nyata.
  • Di distributed system, hitung dari sisi DBmax_pool_per_instance = usable_connections / max_instances. Jangan konfigurasi pool berdasarkan kebutuhan satu instance tanpa memperhitungkan total instance.
  • Connection explosion terjadi saat auto-scaling — setiap pod baru membawa pool-nya sendiri. Pastikan total koneksi saat max scale-up tidak melebihi kapasitas DB.
  • External pooler adalah solusi paling robust untuk distributed system — PgBouncer atau ProxySQL mengelola pool secara terpusat, memungkinkan ribuan koneksi dari aplikasi di-multiplex ke puluhan koneksi ke database.
  • Connection leak membunuh pool secara perlahan — selalu gunakan context manager atau defer untuk memastikan koneksi dikembalikan, bahkan saat ada exception atau early return.
  • Transaction panjang memblokir koneksi di pool — setiap milidetik transaction terbuka berarti satu koneksi tidak tersedia. Operasi eksternal (HTTP call, file I/O) harus dilakukan di luar transaction.
  • Pool timeout wajib dikonfigurasi — tanpa timeout, request yang tidak mendapat koneksi menunggu selamanya, menyebabkan goroutine/thread leak dan OOM.
  • Monitor aktif, bukan reaktif — pantau Threads_connected vs max_connections, pool utilization, dan connection leak sebelum masalah menjadi insiden.
  • Pool untuk read dan write harus dipisah — jika ada replication, pool write ke primary harus lebih kecil dan ketat, pool read ke replica bisa lebih longgar.

← Sebelumnya: Index   Berikutnya: Backup and Restore →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact