Django mặc định mở connection mới cho mỗi request rồi đóng (CONN_MAX_AGE = 0).
Mỗi connection Postgres tốn handshake TLS + auth + allocate session — vài chục ms cho mỗi request, và khi worker nhiều thì DB nhanh chóng ngộp.
# settings.py — giữ connection sống giữa các request
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
# ...
'CONN_MAX_AGE': 60, # giây; None = persistent
'CONN_HEALTH_CHECKS': True, # 4.1+: ping trước khi reuse
}
}Nhưng CONN_MAX_AGE không giải được 2 vấn đề lớn hơn. Một là quá nhiều worker: 10 server × 8 Gunicorn worker × 4 thread = 320 connection liên tục, vượt Postgres max_connections mặc định (100). Hai là connection idle giữa các spike traffic, Postgres giữ memory cho chúng dù không dùng tới.
Giải pháp là đặt PgBouncer giữa app và Postgres (transaction pooling mode):
Django (320 conn logical) → PgBouncer (pool 20 conn thật) → PostgresPgBouncer ghép nhiều connection logic sang ít connection thực và reuse cực nhanh. Đây là production essential cho mọi Django app từ 1 server trở lên — và managed Postgres như Neon hay Supabase thực ra đã có sẵn pooler tương đương PgBouncer.
PgBouncer ở transaction mode không hỗ trợ session feature (LISTEN/NOTIFY, prepared statement, advisory lock). Với psycopg3, set DISABLE_SERVER_SIDE_CURSORS = True để tránh lỗi "cursor does not exist". Và khi đã có pooler bên ngoài, thường set CONN_MAX_AGE = 0 ở Django để mỗi request mượn từ pool rồi trả lại ngay — không cần giữ connection.