axe/internal/db/db.go
2026-10-18 22:07:25 +03:00

78 lines
2.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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
}