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 }