fix: ограничить пул Postgres и реже писать прогресс скана
- DB_MAX_OPEN_CONNS (25), DB_MAX_IDLE_CONNS (10), DB_CONN_MAX_LIFETIME_SEC (1800) - Throttle UpdateScan прогресса в Runner (500ms), меньше одновременных запросов к БД Устраняет FATAL too many clients при больших сканах и частом опросе GetScan Made-with: Cursor
This commit is contained in:
@@ -57,6 +57,15 @@ func makeStore(cfg config.Config) (scans.Store, func()) {
|
|||||||
log.Fatalf("db ping failed: %v", err)
|
log.Fatalf("db ping failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.SetMaxOpenConns(cfg.DBMaxOpenConns)
|
||||||
|
db.SetMaxIdleConns(cfg.DBMaxIdleConns)
|
||||||
|
if cfg.DBConnMaxLifetime > 0 {
|
||||||
|
db.SetConnMaxLifetime(cfg.DBConnMaxLifetime)
|
||||||
|
log.Printf("postgres pool: max_open=%d max_idle=%d conn_max_lifetime=%v", cfg.DBMaxOpenConns, cfg.DBMaxIdleConns, cfg.DBConnMaxLifetime)
|
||||||
|
} else {
|
||||||
|
log.Printf("postgres pool: max_open=%d max_idle=%d conn_max_lifetime=off", cfg.DBMaxOpenConns, cfg.DBMaxIdleConns)
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.RunMigrations {
|
if cfg.RunMigrations {
|
||||||
if err = store.RunMigrations(db); err != nil {
|
if err = store.RunMigrations(db); err != nil {
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@@ -13,6 +15,10 @@ type Config struct {
|
|||||||
SNMPEnabled bool
|
SNMPEnabled bool
|
||||||
SNMPCommunity string
|
SNMPCommunity string
|
||||||
ResetDBSecret string // NETTOPO_RESET_DB_SECRET — секрет для POST /api/admin/purge-scans
|
ResetDBSecret string // NETTOPO_RESET_DB_SECRET — секрет для POST /api/admin/purge-scans
|
||||||
|
// Пул соединений к PostgreSQL (см. database/sql). По умолчанию ограничен, иначе при большом скане легко исчерпать max_connections на сервере.
|
||||||
|
DBMaxOpenConns int
|
||||||
|
DBMaxIdleConns int
|
||||||
|
DBConnMaxLifetime time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromEnv() Config {
|
func FromEnv() Config {
|
||||||
@@ -44,13 +50,44 @@ func FromEnv() Config {
|
|||||||
snmpCommunity = "public"
|
snmpCommunity = "public"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maxOpen := intFromEnvMin("DB_MAX_OPEN_CONNS", 25, 1)
|
||||||
|
maxIdle := intFromEnvMin("DB_MAX_IDLE_CONNS", 10, 0)
|
||||||
|
if maxIdle > maxOpen {
|
||||||
|
maxIdle = maxOpen
|
||||||
|
}
|
||||||
|
connLife := 30 * time.Minute
|
||||||
|
if s := strings.TrimSpace(os.Getenv("DB_CONN_MAX_LIFETIME_SEC")); s != "" {
|
||||||
|
if sec, err := strconv.Atoi(s); err == nil {
|
||||||
|
if sec == 0 {
|
||||||
|
connLife = 0 // без ограничения времени жизни соединения
|
||||||
|
} else if sec > 0 {
|
||||||
|
connLife = time.Duration(sec) * time.Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Config{
|
return Config{
|
||||||
HTTPAddr: addr,
|
HTTPAddr: addr,
|
||||||
DBDSN: dbDSN,
|
DBDSN: dbDSN,
|
||||||
StoreBackend: storeBackend,
|
StoreBackend: storeBackend,
|
||||||
RunMigrations: runMigrations,
|
RunMigrations: runMigrations,
|
||||||
SNMPEnabled: snmpEnabled,
|
SNMPEnabled: snmpEnabled,
|
||||||
SNMPCommunity: snmpCommunity,
|
SNMPCommunity: snmpCommunity,
|
||||||
ResetDBSecret: strings.TrimSpace(os.Getenv("NETTOPO_RESET_DB_SECRET")),
|
ResetDBSecret: strings.TrimSpace(os.Getenv("NETTOPO_RESET_DB_SECRET")),
|
||||||
|
DBMaxOpenConns: maxOpen,
|
||||||
|
DBMaxIdleConns: maxIdle,
|
||||||
|
DBConnMaxLifetime: connLife,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func intFromEnvMin(key string, def, min int) int {
|
||||||
|
s := strings.TrimSpace(os.Getenv(key))
|
||||||
|
if s == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
n, err := strconv.Atoi(s)
|
||||||
|
if err != nil || n < min {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|||||||
@@ -163,11 +163,28 @@ func (r *Runner) run(ctx context.Context, job ScanJob) {
|
|||||||
workCh := make(chan string)
|
workCh := make(chan string)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
pushProgress := func() {
|
// Каждый UpdateScan в Postgres тянет GetScan+UPDATE; при сотнях воркеров без паузы исчерпывается пул соединений к БД.
|
||||||
|
const minProgressWriteInterval = 500 * time.Millisecond
|
||||||
|
var progWriteMu sync.Mutex
|
||||||
|
var lastProgressWrite time.Time
|
||||||
|
|
||||||
|
pushProgress := func(force bool) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
q, f, u := quickDone, fullDone, up
|
q, f, u := quickDone, fullDone, up
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
prog := scanProgressBlend(q, f, total)
|
prog := scanProgressBlend(q, f, total)
|
||||||
|
|
||||||
|
if !force {
|
||||||
|
progWriteMu.Lock()
|
||||||
|
now := time.Now()
|
||||||
|
if !lastProgressWrite.IsZero() && now.Sub(lastProgressWrite) < minProgressWriteInterval {
|
||||||
|
progWriteMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lastProgressWrite = now
|
||||||
|
progWriteMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
_, _ = r.store.UpdateScan(job.ID, ScanUpdate{
|
||||||
Status: "running",
|
Status: "running",
|
||||||
Progress: prog,
|
Progress: prog,
|
||||||
@@ -207,7 +224,7 @@ func (r *Runner) run(ctx context.Context, job ScanJob) {
|
|||||||
}
|
}
|
||||||
quickDone++
|
quickDone++
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
pushProgress()
|
pushProgress(false)
|
||||||
|
|
||||||
if isUp && r.cfg.SNMPEnabled {
|
if isUp && r.cfg.SNMPEnabled {
|
||||||
snmpRes, lldpRes, ifRes, portDevRes := probeSNMPAndLLDP(ip, r.cfg.SNMPCommunity, checkedAt, job.Options.PingTimeoutMS)
|
snmpRes, lldpRes, ifRes, portDevRes := probeSNMPAndLLDP(ip, r.cfg.SNMPCommunity, checkedAt, job.Options.PingTimeoutMS)
|
||||||
@@ -232,7 +249,7 @@ func (r *Runner) run(ctx context.Context, job ScanJob) {
|
|||||||
mu.Lock()
|
mu.Lock()
|
||||||
fullDone++
|
fullDone++
|
||||||
mu.Unlock()
|
mu.Unlock()
|
||||||
pushProgress()
|
pushProgress(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user