78 lines
2.8 KiB
Go
78 lines
2.8 KiB
Go
// Package db — подключение к PostgreSQL и рантайм-раннер миграций
|
||
// (embed SQL-файлов + checksum применённых версий).
|
||
package db
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"strconv"
|
||
"time"
|
||
|
||
"github.com/jackc/pgx/v5/pgxpool"
|
||
)
|
||
|
||
// Connect создаёт пул и проверяет соединение пингом: ошибку лучше
|
||
// поймать здесь, чем на первом запросе хендлера.
|
||
//
|
||
// Пул настраивается по env (DB_*), и только если переменная задана:
|
||
// без них работают дефолты pgxpool — dev и CI не меняются, а в проде
|
||
// потолок соединений задаётся явно (см. .env.prod.example). Без
|
||
// потолка MaxConns = max(4, 4*CPU) — на большой машине это лишние
|
||
// соединения к PostgreSQL.
|
||
func Connect(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
||
cfg, err := pgxpool.ParseConfig(dsn)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := applyPoolEnv(cfg); err != nil {
|
||
return nil, err
|
||
}
|
||
pool, err := pgxpool.NewWithConfig(ctx, cfg)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
if err := pool.Ping(ctx); err != nil {
|
||
// Пинг упал — пул уже создал соединения, закрываем их:
|
||
// утечка на старте незаметна, но копится с каждым рестартом.
|
||
pool.Close()
|
||
return nil, err
|
||
}
|
||
return pool, nil
|
||
}
|
||
|
||
// applyPoolEnv читает DB_* настройки пула. Пустая строка = «не задано»,
|
||
// дефолты остаются от pgxpool. Ошибка здесь — фейл-фаст на старте:
|
||
// молча работать с чужим потолком нельзя.
|
||
func applyPoolEnv(cfg *pgxpool.Config) error {
|
||
if v := os.Getenv("DB_MAX_CONNS"); v != "" {
|
||
n, err := strconv.Atoi(v)
|
||
if err != nil || n <= 0 {
|
||
return fmt.Errorf("DB_MAX_CONNS must be a positive number, got %q", v)
|
||
}
|
||
cfg.MaxConns = int32(n)
|
||
}
|
||
if v := os.Getenv("DB_MIN_CONNS"); v != "" {
|
||
n, err := strconv.Atoi(v)
|
||
if err != nil || n < 0 {
|
||
return fmt.Errorf("DB_MIN_CONNS must be a non-negative number, got %q", v)
|
||
}
|
||
cfg.MinConns = int32(n)
|
||
}
|
||
if v := os.Getenv("DB_MAX_CONN_LIFETIME"); v != "" {
|
||
d, err := time.ParseDuration(v)
|
||
if err != nil || d <= 0 {
|
||
return fmt.Errorf("DB_MAX_CONN_LIFETIME must be a positive duration, e.g. 1h, got %q", v)
|
||
}
|
||
cfg.MaxConnLifetime = d
|
||
}
|
||
if v := os.Getenv("DB_MAX_CONN_IDLE_TIME"); v != "" {
|
||
d, err := time.ParseDuration(v)
|
||
if err != nil || d <= 0 {
|
||
return fmt.Errorf("DB_MAX_CONN_IDLE_TIME must be a positive duration, e.g. 30m, got %q", v)
|
||
}
|
||
cfg.MaxConnIdleTime = d
|
||
}
|
||
return nil
|
||
}
|