Documentation
¶
Overview ¶
Package postgres provides an instrumented PostgreSQL client backed by a pgxpool.Pool that implements app.Component.
Two access paths are exposed after Client.Start:
Client.DB returns a standard *sql.DB derived from the pool. Use this for standard queries and full compatibility with ORMs (sqlx, GORM, Bun), query builders, and migration frameworks (golang-migrate, goose).
Client.Pool returns the underlying *pgxpool.Pool for pgx-native operations: COPY FROM/TO for bulk loads, batch queries, named prepared statements, and LISTEN/NOTIFY for pub-sub patterns.
Both paths share the same connection pool and query tracer, so OpenTelemetry spans are recorded regardless of which path is used.
Basic usage ¶
client, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@localhost:5432/mydb",
})
if err != nil { log.Fatal(err) }
a.Register(client)
if err := a.Start(ctx); err != nil { log.Fatal(err) }
defer a.Shutdown(ctx) //nolint:errcheck
// Standard database/sql path — compatible with ORMs and migrations.
rows, err := client.DB().QueryContext(ctx, "SELECT id FROM users WHERE active = $1", true)
// pgx-native path — for COPY, batching, LISTEN/NOTIFY.
batch := &pgx.Batch{}
batch.Queue("INSERT INTO events (name) VALUES ($1)", "signup")
results := client.Pool().SendBatch(ctx, batch)
With tracing ¶
client, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@localhost:5432/mydb",
Tracer: a.Tracer(),
})
Every query automatically receives an OpenTelemetry span named after the SQL verb (e.g. "db SELECT", "db INSERT"). The full statement is recorded as the db.statement attribute and errors are recorded on the span.
PgBouncer ¶
The client defaults to pgx.QueryExecModeSimpleProtocol, which avoids server-side prepared statements and is compatible with PgBouncer in transaction-pooling mode (the standard deployment at GitLab.com). No additional configuration is required for PgBouncer compatibility.
When running behind PgBouncer, also set ConnMaxLifetime and ConnMaxIdleTime to recycle stale connections:
client, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@pgbouncer:6432/mydb",
ConnMaxLifetime: 5 * time.Minute,
ConnMaxIdleTime: 60 * time.Second,
})
When connecting directly to PostgreSQL without a pooler, you can opt into cached prepared statements for better performance:
client, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@localhost:5432/mydb",
QueryExecMode: pgx.QueryExecModeCacheStatement,
})
Connection parameters for another client library ¶
New reads the PostgreSQL entry of the infrastructure contract mounted by the platform and resolves it, together with the Kubernetes Secret it names, into a DSN. The rules are not trivial: the Secret keys depend on the secret_ref format (CNPG, CRUNCHY, ZALANDO or a CUSTOM projection), a value set inline in the contract wins over the Secret, and the contract may declare a connection pooler next to the database. A service that keeps its own driver, for example pgx pools with their own tracing or a separate migration connection, would have to reimplement them. ConnectionParams applies the same rules and returns the resolved Params without opening a connection; Params.DSN and Params.DirectDSN render them as postgres:// URLs:
infra, err := infrastructure.DefaultConfig()
if err != nil {
log.Fatal(err)
}
params, err := postgres.ConnectionParams(ctx, infra)
if err != nil {
log.Fatal(err)
}
// Application traffic goes through the pooler when the contract declares
// one; migrations and long-running queries connect to the database itself.
pool, err := pgxpool.New(ctx, params.DSN())
if err != nil {
log.Fatal(err)
}
migrations, err := pgx.Connect(ctx, params.DirectDSN())
if err != nil {
log.Fatal(err)
}
Params.URL and Params.DirectURL return the same URLs as *url.URL values for a consumer that edits them first, to keep the credentials out of the DSN or to add query parameters. Params.Password is a secret.Secret and redacts itself when printed.
Example ¶
Example shows an instrumented PostgreSQL client registered as an app component. Start creates the pool and pings the server; Shutdown closes all connections cleanly.
package main
import (
"context"
"gitlab.com/gitlab-org/labkit/v2/postgres"
)
func main() {
ctx := context.Background()
db, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@localhost:5432/mydb",
// Tracer: a.Tracer(),
})
if err != nil {
// DSN parse errors are caught here before Start is called.
panic(err)
}
// Register with app.App so the lifecycle is managed automatically:
// a.Register(db)
// a.Run(ctx)
//
// Or manage manually:
if err := db.Start(ctx); err != nil {
panic(err)
}
defer db.Shutdown(ctx) //nolint:errcheck
// DB() returns *sql.DB — compatible with ORMs, query builders, migrations.
row := db.DB().QueryRowContext(ctx, "SELECT version()")
var version string
_ = row.Scan(&version)
// Pool() returns *pgxpool.Pool — for COPY, batching, LISTEN/NOTIFY.
_ = db.Pool()
}
Output:
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is an instrumented PostgreSQL client backed by a pgxpool.Pool that implements app.Component. Call [Start] to open the pool and [Shutdown] to close it.
Client.DB returns a standard *sql.DB derived from the pool, compatible with ORMs, query builders, and migration frameworks. Client.Pool returns the underlying *pgxpool.Pool for pgx-native operations such as COPY, batch queries, and LISTEN/NOTIFY.
func New ¶
New a Client configured from environment variables, reading the infrastructure configuration from the standard config path.
Returns infrastructure.ErrNotConfigured when no PostgreSQL config is present, or ErrMissingSecrets when no secret provider is configured.
func NewWithConfig ¶
NewWithConfig returns a Client configured with cfg. DSN parsing is performed eagerly so that configuration errors are caught before Start is called.
Example ¶
ExampleNewWithConfig shows connection pool tuning for services running behind PgBouncer, where long-lived connections should be recycled.
package main
import (
"time"
"gitlab.com/gitlab-org/labkit/v2/postgres"
)
func main() {
db, err := postgres.NewWithConfig(&postgres.Config{
DSN: "postgres://user:pass@pgbouncer:6432/mydb",
Name: "primary",
MaxConns: 20,
MinConns: 2, // keep 2 connections warm
ConnMaxLifetime: 5 * time.Minute,
ConnMaxIdleTime: 60 * time.Second,
})
if err != nil {
panic(err)
}
_ = db
}
Output:
func (*Client) DB ¶
DB returns the standard *sql.DB derived from the pool. Compatible with ORMs, query builders, and migration frameworks. Returns nil before [Start] has been called successfully.
func (*Client) Pool ¶
Pool returns the underlying *pgxpool.Pool for pgx-native operations. Use this for bulk loads ([COPY FROM / COPY TO]), batch queries, named prepared statements, and LISTEN/NOTIFY. Returns nil before [Start] has been called successfully.
func (*Client) QueryExecMode ¶
func (c *Client) QueryExecMode() pgx.QueryExecMode
QueryExecMode returns the pgx.QueryExecMode that will be used for queries. The default is pgx.QueryExecModeSimpleProtocol for PgBouncer compatibility.
func (*Client) QueryTracer ¶
func (c *Client) QueryTracer() pgx.QueryTracer
QueryTracer returns the pgx.QueryTracer configured on this client, or nil when no Config.Tracer was provided. This is primarily useful for testing the tracing integration without a live database.
func (*Client) Shutdown ¶
Shutdown closes the sql.DB and the underlying pool, releasing all connections. It satisfies app.Component and should be called via app.App.Shutdown.
func (*Client) Start ¶
Start creates the connection pool and verifies connectivity with a ping. It satisfies app.Component and should be called via app.App.Start.
type Config ¶
type Config struct {
// DSN is the PostgreSQL connection string.
// Accepts postgres:// and postgresql:// URL formats, as well as key=value
// connection strings (e.g. "host=localhost user=postgres dbname=mydb").
DSN string
// Name identifies this client in logs and errors. Defaults to "postgres".
// Use distinct names when a service connects to multiple databases.
Name string
// MaxConns is the maximum number of connections in the pool.
// Maps to pgxpool.Config.MaxConns. Zero uses the pgxpool default
// (max(4, runtime.NumCPU())).
MaxConns int
// MinConns is the minimum number of connections to keep open in the pool,
// even when idle. Maps to pgxpool.Config.MinConns. Zero means no minimum.
// Use this to pre-warm connections at startup and avoid cold-start latency
// on the first requests.
MinConns int
// MaxIdleConns is not used by the pgxpool backend and has no effect.
// To keep a minimum number of connections warm, use [Config.MinConns].
// To close connections that have been idle too long, use [Config.ConnMaxIdleTime].
//
// Deprecated: retained for source compatibility; has no effect.
MaxIdleConns int
// ConnMaxLifetime is the maximum duration a connection may be reused.
// Maps to pgxpool.Config.MaxConnLifetime. Zero means no age limit.
// Set this when using PgBouncer or similar connection poolers to ensure
// stale connections are recycled.
ConnMaxLifetime time.Duration
// ConnMaxIdleTime is the maximum duration a connection may sit idle
// before being closed. Maps to pgxpool.Config.MaxConnIdleTime.
// Zero means connections are not closed due to idle time.
ConnMaxIdleTime time.Duration
// QueryExecMode controls how pgx executes queries. Defaults to
// [pgx.QueryExecModeSimpleProtocol], which avoids server-side prepared
// statements and is required for PgBouncer in transaction-pooling mode
// (the standard deployment at GitLab.com).
//
// Set to [pgx.QueryExecModeCacheStatement] or another mode only when
// connecting directly to PostgreSQL without a connection pooler.
//
// See https://pkg.go.dev/github.com/jackc/pgx/v5#QueryExecMode for all
// available modes.
QueryExecMode pgx.QueryExecMode
// Tracer is used to create spans for queries. When nil, tracing is
// disabled.
Tracer *trace.Tracer
}
Config holds optional configuration for New / NewWithConfig.
type NewOption ¶ added in v2.4.1
type NewOption func(*clientOptions)
NewOption configures a Client created via New.
func WithConnectionPool ¶ added in v2.16.0
WithConnectionPool configures whether the client should use a connection pool. When false, the client will connect directly to the primary DB using a single connection. Defaults to true.
func WithInfraConfig ¶ added in v2.8.0
func WithInfraConfig(c *infrastructure.Config) NewOption
WithInfraConfig sets the infrastructure.Config used to read infrastructure configuration and secrets. When nil, the default config is used.
func WithTracer ¶ added in v2.4.1
WithTracer sets the trace.Tracer used to instrument database queries.
type Params ¶ added in v2.45.0
type Params struct {
Host string
Port string
Database string
User string
Password secret.Secret
HasPassword bool
PoolHost string
PoolPort string
Pooled bool
}
Params are the connection parameters the infrastructure contract resolves to for its PostgreSQL entry. Params may gain fields in later versions.
Host and Port are the database's own endpoint. PoolHost and PoolPort are the endpoint for pooled application traffic: the connection pooler where the contract declares one, inline or in the Secret, and Host and Port otherwise; Pooled reports whether a pooler is declared. The two pooler fields resolve independently: either one declared sets Pooled and the other keeps the database's value, so a pooler host may pair with the database's port. HasPassword reports whether the contract declares a secret_ref, so Password was read from the Secret; it may be empty. Without a secret_ref the connection is unauthenticated. Ports are kept as written in the contract or the Secret.
func ConnectionParams ¶ added in v2.45.0
func ConnectionParams(ctx context.Context, infra *infrastructure.Config, opts ...ParamsOption) (Params, error)
ConnectionParams resolves the PostgreSQL entry of the infrastructure contract to connection parameters without opening a connection.
The caller loads the contract first, with infrastructure.DefaultConfig or infrastructure.Load. ConnectionParams exists for services that keep their own client library and would otherwise reimplement the contract's rules, which are the ones New applies: the Secret keys are named by the secret_ref format preset (CNPG, CRUNCHY, ZALANDO) or by the projection of format CUSTOM, a value set inline in the contract wins over the Secret, no secret_ref means host, port, database and user must all be inline, and the pooler endpoint is optional while the database's own endpoint is required.
Errors wrap infrastructure.ErrNotConfigured for a nil infra or a contract without a PostgreSQL entry, ErrMissingSecrets for a secret_ref without a secret provider, ErrMissingConfigField for a required field that is neither inline nor in the Secret or resolves to an empty value, ErrUnknownSecretFormat, secret.ErrNotFound for a projected key the Secret does not hold, secret.ErrProjectionMissing for format CUSTOM without a projection or a projection that leaves a required key unnamed, secret.ErrProjectionInvalid, or any other error the secret provider returns. Several may be joined.
func (Params) DirectURL ¶ added in v2.45.0
DirectURL renders the parameters as URL does, pointed at Host and Port, the database itself, for migrations and long-running queries that must not go through a pooler. Every call returns a new value.
func (Params) URL ¶ added in v2.45.0
URL renders the parameters as a postgres:// URL for application traffic, pointed at PoolHost and PoolPort. The user info carries Password only when HasPassword is set. Every call returns a new value, so a consumer that keeps the credentials out of the DSN or adds query parameters edits it before rendering it with String.
type ParamsOption ¶ added in v2.45.0
type ParamsOption func(*paramsOptions)
ParamsOption configures ConnectionParams. No options exist yet; the type keeps the signature open for them.