Table of contents
Part of nurago, a collection of independent Go packages for backend services.
import "github.com/tecnickcom/nurago/pkg/sqlconn"
Opens a database/sql pool, applies limits, verifies it works, exposes a health check, and closes it on shutdown.
c, err := sqlconn.Connect(ctx, "mysql://user:pass@tcp(host:3306)/db")
defer c.Shutdown()
New takes driver and DSN separately; Connect takes a <DRIVER>://<DSN> string, with WithDefaultDriver for a bare DSN.
The context bounds connection, not the pool
This is the distinction the package documents most carefully, and the one that causes the most confusion.
The context passed to New or Connect bounds establishment only: dialling and the initial health check. It does not control the pool’s lifetime. A request-scoped or timeout-scoped context will not close the pool when it ends.
That is correct, and the alternative would be worse. sql.Open does not connect, so a service that never verified connectivity discovers a bad DSN or an unreachable host on its first query, in a request. Verifying at startup with a bounded context means a misconfigured deployment fails at rollout.
Closing the pool is wired separately:
WithLifetimeContextfor a long-lived context tied to the processWithShutdownSignalChanfor a signal channelSQLConn.Shutdowncalled directly
Shutdown closes the handle, updates the shared shutdown waitgroup, sets the internal DB pointer to nil to prevent further use, and stops the watcher goroutine. It is idempotent, so a deferred call and a signal-driven one can both fire without a double-close panic or a leaked goroutine.
Pool limits, and why the defaults are wrong
WithConnMaxOpen, WithConnMaxIdleCount, WithConnMaxIdleTime and WithConnMaxLifetime map to the sql.DB setters and are applied on successful connect.
Go’s defaults are unsuited to a service. MaxOpenConns defaults to unlimited, so a traffic spike opens connections until the database refuses them, converting a latency problem into an outage across every client. MaxIdleConns defaults to 2, so a busy service closes and reopens connections continuously, paying the handshake, and with TLS that cost is significant.
ConnMaxLifetime matters wherever something between the service and the database closes idle connections: a load balancer, a proxy, or a MySQL wait_timeout. Without it the pool hands out a connection the far end already closed, and the first query on it fails. Setting a lifetime shorter than the far end’s timeout means the pool retires connections before anything else does.
Health checks
SQLConn satisfies the healthcheck HealthChecker contract directly, so it registers with no adapter.
The check uses the same pool as real traffic, so it exercises the path production uses rather than a separate connection that might succeed while the pool is exhausted.
Injection points for tests
WithConnectFunc, WithCheckConnectionFunc and WithSQLOpenFunc replace the connect, verify and open steps.
These make the failure paths testable without a database: connect failure, verification failure, and a pool that opens but does not work are all reachable in a unit test.
Dependencies
Reaches the SQL driver you register. The package itself uses database/sql from the standard library.
Links
- Reference documentation
- Source code
- Transactions: sqltransaction
- Distributed locking: mysqllock
- The data guide: /docs/data-and-messaging/
- All nurago packages