72 lines
3.3 KiB
Go
72 lines
3.3 KiB
Go
package store
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
)
|
||
|
||
// R-1346 (2026-10-30): the statements the measurement named, in one place so the plan test reads the exact SQL the
|
||
// code runs (r1346_speed_test.go TestR1346_HeavyStatementsUseTheirIndex).
|
||
|
||
// queryNewestControllerVersion — the vouch save's R-120 fleet check (configs.go handleSetArtifacts).
|
||
const queryNewestControllerVersion = `SELECT DISTINCT controller_version FROM reports WHERE controller_version IS NOT NULL AND controller_version != ''`
|
||
|
||
// queryHostAgentVersionsSeen — the hourly OS alarm, for a box with no healthy OS report.
|
||
const queryHostAgentVersionsSeen = `SELECT COALESCE(agent_version, ''), MIN(received_at) FROM host_reports WHERE host_id = ? GROUP BY agent_version`
|
||
|
||
// queryPruneReports / queryPruneHostReports — the 04:30 retention prune (Prune).
|
||
const (
|
||
queryPruneReports = `DELETE FROM reports WHERE received_at < ?`
|
||
queryPruneHostReports = `DELETE FROM host_reports WHERE received_at < ?`
|
||
)
|
||
|
||
// queryFleetAppSummary — the apps page (GetFleetAppSummary).
|
||
const queryFleetAppSummary = `
|
||
SELECT app_name,
|
||
MAX(display_name) as display_name,
|
||
COUNT(DISTINCT customer_id) as deployment_count,
|
||
AVG(memory_avg_mb) as avg_memory_mb,
|
||
MAX(memory_peak_mb) as peak_memory_mb,
|
||
AVG(cpu_avg_percent) as avg_cpu,
|
||
SUM(log_errors) as total_errors,
|
||
SUM(log_warnings) as total_warnings,
|
||
MAX(catalog_estimate) as catalog_estimate,
|
||
MAX(catalog_limit) as catalog_limit
|
||
FROM app_telemetry INDEXED BY idx_app_telemetry_prune
|
||
WHERE reported_at > ?
|
||
GROUP BY app_name
|
||
ORDER BY deployment_count DESC, avg_memory_mb DESC`
|
||
|
||
// r1346Indexes lets the statements above read only what they need. controller_version and agent_version sit AFTER
|
||
// report_json in their rows, so without an index every read of them walks each row's whole JSON (overflow pages);
|
||
// received_at has no index of its own, so the prune scanned both tables INSIDE its write lock (39 s on 2026-10-30,
|
||
// and a controller report refused with „database is locked" meanwhile). Built once, at the first start that has them:
|
||
// 63 s measured on a copy of the live database in the hub pod (256 MiB) — inside the startup probe's 180 s with the
|
||
// measured 31–36 s start (R-1326).
|
||
var r1346Indexes = []struct{ name, ddl string }{
|
||
{"idx_reports_received", `CREATE INDEX IF NOT EXISTS idx_reports_received ON reports(received_at)`},
|
||
{"idx_host_reports_received", `CREATE INDEX IF NOT EXISTS idx_host_reports_received ON host_reports(received_at)`},
|
||
{"idx_reports_controller_version", `CREATE INDEX IF NOT EXISTS idx_reports_controller_version ON reports(controller_version)`},
|
||
{"idx_host_reports_agent_version", `CREATE INDEX IF NOT EXISTS idx_host_reports_agent_version ON host_reports(host_id, agent_version, received_at)`},
|
||
}
|
||
|
||
func (s *Store) migrateR1346Indexes() error {
|
||
for _, ix := range r1346Indexes {
|
||
var n int
|
||
if err := s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type = 'index' AND name = ?`, ix.name).Scan(&n); err != nil {
|
||
return err
|
||
}
|
||
if n > 0 {
|
||
continue
|
||
}
|
||
began := time.Now()
|
||
if _, err := s.db.Exec(ix.ddl); err != nil {
|
||
return fmt.Errorf("%s: %w", ix.name, err)
|
||
}
|
||
if s.logger != nil {
|
||
s.logger.Printf("[INFO] store: index %s built in %s (R-1346, once)", ix.name, time.Since(began).Round(time.Millisecond))
|
||
}
|
||
}
|
||
return nil
|
||
}
|