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

  1. Construct one pool
  2. Set a deployment budget
  3. Measure wait deltas
  4. 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.

01

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.

go
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, nil
Read the code

This 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.

02

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.

03

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.

go
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.
Read the code

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.

04

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.