fix: shrink MySQL connection pool (50->8) so a multi-op event doesn't exhaust the shared server's max_connections (Error 1040: Too many connections), which was silently failing the on-air widget/award_refs/grid reads and making the UI look frozen
This commit is contained in:
+18
-16
@@ -129,6 +129,22 @@ func translateTextColumns(s string) string {
|
|||||||
// OpenMySQL opens the shared MySQL logbook, creating the database if needed,
|
// OpenMySQL opens the shared MySQL logbook, creating the database if needed,
|
||||||
// then applies the (translated) embedded migrations. multiStatements is enabled
|
// then applies the (translated) embedded migrations. multiStatements is enabled
|
||||||
// so multi-statement migration files run in a single Exec.
|
// so multi-statement migration files run in a single Exec.
|
||||||
|
// tuneMySQLPool sizes the connection pool for a SHARED server. A multi-operator
|
||||||
|
// special event runs many OpsLog instances against ONE MySQL, whose max_connections
|
||||||
|
// is a hard global limit (often 100–151). A big per-instance pool (we used 50) let
|
||||||
|
// a handful of ops exhaust the server — "Error 1040: Too many connections" — after
|
||||||
|
// which queries (the on-air widget, award_refs, the grid) fail silently and the UI
|
||||||
|
// looks frozen. A modest cap with brisk idle reaping keeps each instance's server
|
||||||
|
// footprint small so ~10+ ops fit, while still absorbing the UI's concurrent
|
||||||
|
// logbook queries. No max lifetime: a slow first migration can run for minutes on a
|
||||||
|
// single connection and reaping it mid-migration drops the selected database.
|
||||||
|
func tuneMySQLPool(conn *sql.DB) {
|
||||||
|
conn.SetMaxOpenConns(8)
|
||||||
|
conn.SetMaxIdleConns(3)
|
||||||
|
conn.SetConnMaxIdleTime(30 * time.Second) // return idle connections to the server promptly
|
||||||
|
conn.SetConnMaxLifetime(0)
|
||||||
|
}
|
||||||
|
|
||||||
func OpenMySQL(c MySQLConfig) (*sql.DB, error) {
|
func OpenMySQL(c MySQLConfig) (*sql.DB, error) {
|
||||||
if strings.TrimSpace(c.Host) == "" {
|
if strings.TrimSpace(c.Host) == "" {
|
||||||
return nil, fmt.Errorf("host is required")
|
return nil, fmt.Errorf("host is required")
|
||||||
@@ -141,18 +157,7 @@ func OpenMySQL(c MySQLConfig) (*sql.DB, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("open mysql: %w", err)
|
return nil, fmt.Errorf("open mysql: %w", err)
|
||||||
}
|
}
|
||||||
// The UI fires bursts of concurrent queries (the Preferences dialog alone
|
tuneMySQLPool(conn)
|
||||||
// loads ~8 settings in parallel, plus the grid and live cluster status). A
|
|
||||||
// low cap turns such a burst into a deadlock-like stall against a remote
|
|
||||||
// server, so keep a generous pool with idle reaping. SQLite ran uncapped.
|
|
||||||
conn.SetMaxOpenConns(50)
|
|
||||||
conn.SetMaxIdleConns(10)
|
|
||||||
conn.SetConnMaxIdleTime(90 * time.Second)
|
|
||||||
// No max lifetime: a slow server's first migration can run for minutes on a
|
|
||||||
// single connection, and reaping it mid-migration drops the selected database
|
|
||||||
// (surfacing as "Unknown database"). Idle connections are still recycled
|
|
||||||
// after 90s, and the driver retries stale pooled connections.
|
|
||||||
conn.SetConnMaxLifetime(0)
|
|
||||||
// Connect DIRECTLY to the database first. Only if that fails — the DB doesn't
|
// Connect DIRECTLY to the database first. Only if that fails — the DB doesn't
|
||||||
// exist yet (first-time setup) or the server is unreachable — do we pay for the
|
// exist yet (first-time setup) or the server is unreachable — do we pay for the
|
||||||
// server-level connect + CREATE DATABASE (two more handshakes). On a normal
|
// server-level connect + CREATE DATABASE (two more handshakes). On a normal
|
||||||
@@ -167,10 +172,7 @@ func OpenMySQL(c MySQLConfig) (*sql.DB, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("open mysql: %w", err)
|
return nil, fmt.Errorf("open mysql: %w", err)
|
||||||
}
|
}
|
||||||
conn.SetMaxOpenConns(50)
|
tuneMySQLPool(conn)
|
||||||
conn.SetMaxIdleConns(10)
|
|
||||||
conn.SetConnMaxIdleTime(90 * time.Second)
|
|
||||||
conn.SetConnMaxLifetime(0)
|
|
||||||
if err := conn.Ping(); err != nil {
|
if err := conn.Ping(); err != nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("connect to %s: %w", name, err)
|
return nil, fmt.Errorf("connect to %s: %w", name, err)
|
||||||
|
|||||||
Reference in New Issue
Block a user