Around Go: SQL services · 3 MIN READ
SQL: one pool, explicit capacity and observable waiting
Configure database/sql as a shared pool and measure its actual pressure.
sql.DB is a concurrency-safe pool handle, not a single database connection. Reuse it across the application and close it after its users drain. These lessons describe native Go services using a real driver; the app’s bounded WASI programs cannot open remote database sockets.
SQL: one pool, explicit capacity and observable waiting · A MAP OF THE IDEA
- Construct one pool
- Set a deployment budget
- Measure wait deltas
- Drain before closing
Useful when
- Configure database/sql as a shared pool and measure its actual pressure.
- Own one shared pool, bound operation lifetimes and admission, and measure pool waits across all replicas before changing connection limits.
Watch out
- A larger connection cap can move waiting into an overloaded database. Admission and query efficiency still need their own policies.
Implementation & operation cost: Choose the policy from the actual contract, then verify its failure path in the target environment.
Construct once and verify readiness
sql.Open generally constructs a handle rather than proving connectivity. Use a bounded PingContext for startup evidence, then handle every operation error separately. Configure a reviewed driver version in the native service module; pgx/v5/stdlib is one PostgreSQL driver option.
db, err := sql.Open("pgx", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(12)
db.SetMaxIdleConns(6)
db.SetConnMaxIdleTime(2 * time.Minute)
db.SetConnMaxLifetime(30 * time.Minute)
probe, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
if err := db.PingContext(probe); err != nil {
_ = db.Close()
return nil, fmt.Errorf("database readiness: %w", err)
}
return db, nilThis startup-function fragment imports database/sql, context, fmt, time and the chosen PostgreSQL driver registration. The DSN comes from configuration. The numeric settings are examples, not sizing advice; a returned shared pool needs one application lifetime owner.
Budget across the deployment
A limit of twelve per replica means something different with two replicas than with fifty. Reserve database capacity for migrations, administration and other applications. SetMaxOpenConns bounds this pool’s open connections; it is not a global request limiter or a cap on goroutines waiting to acquire one.
Read counters as changes over an interval
DB.Stats exposes OpenConnections, InUse, Idle, WaitCount and WaitDuration. Wait counters accumulate, so compare their changes over the same measurement interval. A high wait rate can reflect slow transactions, leaked rows or too much admission rather than a pool that should simply be enlarged.
stats := db.Stats()
fmt.Println(stats.OpenConnections, stats.InUse, stats.Idle)
fmt.Println(stats.WaitCount, stats.WaitDuration)
// Record deltas over a defined interval alongside request latency.The fragment observes a shared sql.DB and imports fmt. Lifetime counters do not identify the present bottleneck by themselves. Correlate a pool-wait interval with query latency, queue length, errors and the database’s own evidence.
Distinguish other pool APIs
A native pgxpool.Pool is another pool abstraction with its own settings and metrics. Do not wrap one pool in another or assume database/sql option names apply unchanged. Choose the API the service uses, then test saturation and shutdown for that actual combination.
TAKE THIS WITH YOU
Own one shared pool, bound operation lifetimes and admission, and measure pool waits across all replicas before changing connection limits.
Check your understanding
Fifty replicas each allow twelve connections. Which budget must be checked before scaling further?
Go to the source
Progress stays in this browser. No account needed.