Connection Pooling
Connection Pooling
Section titled “Connection Pooling”Optimize database connections for performance and scalability.
Overview
Section titled “Overview”Why Connection Pooling?
Section titled “Why Connection Pooling?”Database connections are expensive resources:
| Problem | Impact |
|---|---|
| Connection overhead | ~100-300ms per new connection |
| Memory usage | ~10MB per PostgreSQL connection |
| Connection limits | PostgreSQL default: 100 connections |
Connection pooling solves these by maintaining reusable connections.
Pooling Architecture
Section titled “Pooling Architecture”Application-Level Pooling
Section titled “Application-Level Pooling”Mantis components include built-in connection pooling.
Mandible Pool Configuration
Section titled “Mandible Pool Configuration”Mandible is the only component with a database pool. The [database] fields are
url, max_connections, min_idle, and connection_timeout_secs:
[database]url = "postgres://mantis:password@localhost:5432/mantis"max_connections = 20 # Maximum pool sizemin_idle = 5 # Minimum idle connectionsconnection_timeout_secs = 30 # Connection acquisition timeoutEnvironment Variables
Section titled “Environment Variables”# Mandible (the connection URL; layered overrides use the MANDIBLE__DATABASE__ prefix)DATABASE_URL=postgres://mantis:password@localhost:5432/mantisMANDIBLE__DATABASE__MAX_CONNECTIONS=20MANDIBLE__DATABASE__MIN_IDLE=5MANDIBLE__DATABASE__CONNECTION_TIMEOUT_SECS=30Sizing Guidelines
Section titled “Sizing Guidelines”Pool Size Calculation
Section titled “Pool Size Calculation”Formula:
pool_size = (core_count * 2) + spinning_disk_countFor SSD storage:
pool_size = core_count * 2Example for 4-core server with SSD:
pool_size = 4 * 2 = 8Recommended Settings
Section titled “Recommended Settings”| Deployment | Mandible pool |
|---|---|
| Development | 5 |
| Small (< 100 targets) | 10 |
| Medium (< 1000 targets) | 20 |
| Large (1000+ targets) | 50 |
(Only Mandible connects to PostgreSQL, so it is the only pool to size.)
PostgreSQL max_connections
Section titled “PostgreSQL max_connections”Ensure PostgreSQL can handle every Mandible instance’s pool:
max_connections >= (mandible_pool * mandible_instances) + overhead
Example:max_connections >= (20 * 2) + 20 = 60# postgresql.confmax_connections = 200PgBouncer Setup
Section titled “PgBouncer Setup”For large deployments, use PgBouncer as an external connection pooler.
Installation
Section titled “Installation”# Ubuntu/Debiansudo apt install pgbouncer
# RHEL/Rocky/Almasudo dnf install pgbouncerConfiguration
Section titled “Configuration”[databases]mantis = host=localhost port=5432 dbname=mantis
[pgbouncer]listen_addr = 0.0.0.0listen_port = 6432auth_type = scram-sha-256auth_file = /etc/pgbouncer/userlist.txt
# Pool settingspool_mode = session # what Mandible's LISTEN needs; see Pool Modesdefault_pool_size = 50 # Connections per database/usermax_client_conn = 1000 # Max client connectionsmin_pool_size = 10 # Minimum pool sizereserve_pool_size = 5 # Emergency connections
# Connection limitsmax_db_connections = 100 # Max connections to database
# Timeoutsserver_connect_timeout = 15server_idle_timeout = 600server_lifetime = 3600client_idle_timeout = 0 # 0 = disabled
# Logginglog_connections = 1log_disconnections = 1stats_period = 60User Authentication
Section titled “User Authentication”Create userlist:
# Generate password hashpsql -U postgres -c "SELECT concat('\"mantis\" \"', passwd, '\"') FROM pg_shadow WHERE usename='mantis'"
# Add to userlist.txtecho '"mantis" "SCRAM-SHA-256$..."' >> /etc/pgbouncer/userlist.txtPool Modes
Section titled “Pool Modes”| Mode | Description | Use Case |
|---|---|---|
| session | Connection per client session | Legacy apps |
| transaction | Connection per transaction | Most applications |
| statement | Connection per statement | Simple queries |
Start PgBouncer
Section titled “Start PgBouncer”sudo systemctl enable pgbouncersudo systemctl start pgbouncerConnect Through PgBouncer
Section titled “Connect Through PgBouncer”Update application configuration:
[database]url = "postgres://mantis:password@localhost:6432/mantis"max_connections = 50 # Can be higher with PgBouncerMonitoring Connections
Section titled “Monitoring Connections”Application Pool Metrics
Section titled “Application Pool Metrics”The /api/v1/health endpoint performs a database connectivity check and
returns {"status":"ok"} (200) when healthy or {"status":"unavailable"}
(503) when the database is unreachable. It does not expose pool internals.
For a pure process-liveness check that never gates on external dependencies,
use /api/v1/health/live instead (always 200 while the process is running).
Monitor pool saturation from the PostgreSQL side:
-- Active vs idle connections for the Mandible DB userSELECT state, count(*) FROM pg_stat_activityWHERE usename = 'mantis' GROUP BY state;PostgreSQL Connections
Section titled “PostgreSQL Connections”-- Current connectionsSELECT count(*), stateFROM pg_stat_activityWHERE datname = 'mantis'GROUP BY state;
-- Connections by applicationSELECT application_name, count(*)FROM pg_stat_activityWHERE datname = 'mantis'GROUP BY application_name;
-- Long-running connectionsSELECT pid, usename, application_name, now() - backend_start as connection_age, state, queryFROM pg_stat_activityWHERE datname = 'mantis'ORDER BY backend_start;PgBouncer Statistics
Section titled “PgBouncer Statistics”# Connect to PgBouncer admin consolepsql -p 6432 -U pgbouncer pgbouncer
# Show poolsSHOW POOLS;
# Show clientsSHOW CLIENTS;
# Show servers (actual PostgreSQL connections)SHOW SERVERS;
# Show statisticsSHOW STATS;Troubleshooting
Section titled “Troubleshooting”Connection Exhaustion
Section titled “Connection Exhaustion”Symptoms:
- “too many connections” errors
- Slow response times
- Connection timeouts
Diagnosis:
SELECT count(*) FROM pg_stat_activity;SELECT * FROM pg_stat_activity WHERE state = 'idle' ORDER BY backend_start;Solutions:
- Increase
max_connectionsin PostgreSQL - Reduce pool sizes
- Use PgBouncer
- Identify connection leaks
Connection Leaks
Section titled “Connection Leaks”Symptoms:
- Pool exhaustion over time
- Idle connections accumulating
Diagnosis:
-- Find idle connectionsSELECT pid, usename, application_name, now() - state_change as idle_timeFROM pg_stat_activityWHERE state = 'idle'ORDER BY idle_time DESC;Solutions:
- Lower
min_idleso fewer idle connections are held open, or use PgBouncer’sserver_idle_timeoutto recycle idle server connections - Check application code for unclosed connections
- Enable connection lifecycle logging
Slow Connection Acquisition
Section titled “Slow Connection Acquisition”Symptoms:
- High latency on first requests
- Timeout during connection
Diagnosis:
# Test connection timetime psql -h localhost -U mantis -c "SELECT 1"Solutions:
- Increase
min_idleto pre-warm the pool - Check network latency to database
- Reduce SSL negotiation overhead (connection reuse)
PgBouncer Issues
Section titled “PgBouncer Issues”Client authentication failed:
# Check userlist.txt formatcat /etc/pgbouncer/userlist.txt
# Verify auth type matches PostgreSQLgrep auth_type /etc/pgbouncer/pgbouncer.iniNo free connections:
# Check pool statuspsql -p 6432 pgbouncer -c "SHOW POOLS"
# Increase pool size or max_db_connectionsBest Practices
Section titled “Best Practices”1. Size Pools Appropriately
Section titled “1. Size Pools Appropriately”- Don’t over-provision (wastes memory)
- Don’t under-provision (causes contention)
- Monitor and adjust based on metrics
2. Use Connection Timeouts
Section titled “2. Use Connection Timeouts”The [database] section accepts only url, max_connections, min_idle, and
connection_timeout_secs. Set the acquisition timeout here; for idle-recycling and
max-lifetime behavior use an external pooler (PgBouncer).
[database]connection_timeout_secs = 30 # Connection acquisition timeout3. Enable Health Checks
Section titled “3. Enable Health Checks”Built-in health checks verify database connectivity:
curl https://localhost:3000/api/v1/health/ready4. Monitor Pool Utilization
Section titled “4. Monitor Pool Utilization”Mantis does not currently export pool-utilization gauges to Prometheus.
Monitor pool saturation from the PostgreSQL side using pg_stat_activity:
-- Count active connections for the Mantis DB userSELECT state, count(*) FROM pg_stat_activityWHERE usename = 'mantis' GROUP BY state;Alert when the active connection count approaches the configured
max_connections value in your PostgreSQL instance.
5. Use Session Mode for Mandible
Section titled “5. Use Session Mode for Mandible”Transaction mode is the more efficient one, but Mandible’s LISTEN registrations do
not survive it (see Pool Modes):
pool_mode = sessionNext Steps
Section titled “Next Steps”- Maintenance - Database maintenance
- PostgreSQL Setup - PostgreSQL configuration
- Monitoring - Metrics and alerts
