diff --git a/changes/45415-vulnerability-and-versions-speed-imporvement b/changes/45415-vulnerability-and-versions-speed-imporvement new file mode 100644 index 0000000000..c67a85f67c --- /dev/null +++ b/changes/45415-vulnerability-and-versions-speed-imporvement @@ -0,0 +1 @@ +- Fixed latency issues with /vulnerabilities and filtered /software/versions queries diff --git a/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes.go b/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes.go new file mode 100644 index 0000000000..6a96702604 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes.go @@ -0,0 +1,66 @@ +package tables + +import ( + "database/sql" + "fmt" +) + +func init() { + MigrationClient.AddMigration(Up_20260608202705, Down_20260608202705) +} + +// Up_20260515000600 adds three indexes to speed up the /api/v1/fleet/vulnerabilities +// and /api/v1/fleet/software/versions endpoints, which were doing full-table scans +// for filter and scope predicates. All three are created with ALGORITHM=INPLACE, +// LOCK=NONE so they can be applied online without blocking writers. +// +// - idx_cve_meta_exploit (cisa_known_exploit, cve): +// CVE listing filters by cm.cisa_known_exploit = 1; that column has no index +// today, forcing a full scan of cve_meta. cisa_known_exploit is highly +// selective (a few thousand out of 200k+ CVEs), so this index turns the +// filter into an index range scan. The trailing cve makes the index +// covering for the join back to vulnerability_host_counts. +// +// - idx_cve_meta_cvss_score (cvss_score, cve): +// /software/versions filters by c.cvss_score >= ? on cve_meta with no +// supporting index. Kept separate from idx_cve_meta_exploit because mixing +// an equality column with a range column in a single composite would +// prevent independent use of either filter. +// +// - idx_vhc_scope_cve (global_stats, team_id, host_count, cve): +// vulnerability_host_counts only has UNIQUE KEY (cve, team_id, global_stats), +// which leads with cve and is useless for the scope filter shape used by +// ListVulnerabilities/CountVulnerabilities (global_stats = ?, team_id = ?, +// host_count > 0). Leading with the scope columns and including cve last +// makes this index covering for the inner query in the refactored +// ListVulnerabilities path. +func Up_20260608202705(tx *sql.Tx) error { + stmts := []struct { + name string + sql string + }{ + { + name: "idx_cve_meta_exploit", + sql: `ALTER TABLE cve_meta ADD INDEX idx_cve_meta_exploit (cisa_known_exploit, cve), ALGORITHM=INPLACE, LOCK=NONE`, + }, + { + name: "idx_cve_meta_cvss_score", + sql: `ALTER TABLE cve_meta ADD INDEX idx_cve_meta_cvss_score (cvss_score, cve), ALGORITHM=INPLACE, LOCK=NONE`, + }, + { + name: "idx_vhc_scope_cve", + sql: `ALTER TABLE vulnerability_host_counts ADD INDEX idx_vhc_scope_cve (global_stats, team_id, host_count, cve), ALGORITHM=INPLACE, LOCK=NONE`, + }, + } + + for _, s := range stmts { + if _, err := tx.Exec(s.sql); err != nil { + return fmt.Errorf("failed to add %s: %w", s.name, err) + } + } + return nil +} + +func Down_20260608202705(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes_test.go b/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes_test.go new file mode 100644 index 0000000000..e925c20565 --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260608202705_AddVulnPerfIndexes_test.go @@ -0,0 +1,53 @@ +package tables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUp_20260608202705(t *testing.T) { + db := applyUpToPrev(t) + + applyNext(t, db) + + expected := []struct { + table string + indexName string + columns []string + }{ + {"cve_meta", "idx_cve_meta_exploit", []string{"cisa_known_exploit", "cve"}}, + {"cve_meta", "idx_cve_meta_cvss_score", []string{"cvss_score", "cve"}}, + {"vulnerability_host_counts", "idx_vhc_scope_cve", []string{"global_stats", "team_id", "host_count", "cve"}}, + } + + for _, e := range expected { + rows, err := db.Query( + "SELECT seq_in_index, column_name FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ? ORDER BY seq_in_index", + e.table, e.indexName, + ) + require.NoError(t, err) + + var actualColumns []string + for rows.Next() { + var seqInIndex int + var columnName string + err := rows.Scan(&seqInIndex, &columnName) + require.NoError(t, err) + actualColumns = append(actualColumns, columnName) + } + require.NoError(t, rows.Err()) + require.NoError(t, rows.Close()) //nolint:sqlclosecheck // a defer per-iteration would leak until the loop ends; explicit close is fine in this test + + require.Equalf( + t, + e.columns, + actualColumns, + "expected index %s on %s to have columns %v in order, got %v", + e.indexName, + e.table, + e.columns, + actualColumns, + ) + } +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 2d27e7f33c..204b90ac49 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -434,7 +434,9 @@ CREATE TABLE `cve_meta` ( `cisa_known_exploit` tinyint(1) DEFAULT NULL, `published` timestamp NULL DEFAULT NULL, `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, - PRIMARY KEY (`cve`) + PRIMARY KEY (`cve`), + KEY `idx_cve_meta_exploit` (`cisa_known_exploit`,`cve`), + KEY `idx_cve_meta_cvss_score` (`cvss_score`,`cve`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET @saved_cs_client = @@character_set_client */; @@ -2030,9 +2032,9 @@ CREATE TABLE `migration_status_tables` ( `is_applied` tinyint(1) NOT NULL, `tstamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) -) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=545 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=546 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; -INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260317120000,1,'2020-01-01 01:01:01'),(496,20260318184559,1,'2020-01-01 01:01:01'),(497,20260319120000,1,'2020-01-01 01:01:01'),(498,20260323144117,1,'2020-01-01 01:01:01'),(499,20260324161944,1,'2020-01-01 01:01:01'),(500,20260324223334,1,'2020-01-01 01:01:01'),(501,20260326131501,1,'2020-01-01 01:01:01'),(502,20260326210603,1,'2020-01-01 01:01:01'),(503,20260331000000,1,'2020-01-01 01:01:01'),(504,20260401153000,1,'2020-01-01 01:01:01'),(505,20260401153001,1,'2020-01-01 01:01:01'),(506,20260401153503,1,'2020-01-01 01:01:01'),(507,20260403120000,1,'2020-01-01 01:01:01'),(508,20260409153713,1,'2020-01-01 01:01:01'),(509,20260409153714,1,'2020-01-01 01:01:01'),(510,20260409153715,1,'2020-01-01 01:01:01'),(511,20260409153716,1,'2020-01-01 01:01:01'),(512,20260409153717,1,'2020-01-01 01:01:01'),(513,20260409183610,1,'2020-01-01 01:01:01'),(514,20260410173222,1,'2020-01-01 01:01:01'),(515,20260422181702,1,'2020-01-01 01:01:01'),(516,20260423161823,1,'2020-01-01 01:01:01'),(517,20260423161824,1,'2020-01-01 01:01:01'),(518,20260518194422,1,'2020-01-01 01:01:01'),(519,20260522195224,1,'2020-01-01 01:01:01'),(520,20260522195225,1,'2020-01-01 01:01:01'),(521,20260522195226,1,'2020-01-01 01:01:01'),(522,20260522195227,1,'2020-01-01 01:01:01'),(523,20260522195229,1,'2020-01-01 01:01:01'),(524,20260522195230,1,'2020-01-01 01:01:01'),(525,20260522195231,1,'2020-01-01 01:01:01'),(526,20260522195232,1,'2020-01-01 01:01:01'),(527,20260522195233,1,'2020-01-01 01:01:01'),(528,20260522195234,1,'2020-01-01 01:01:01'),(529,20260522195235,1,'2020-01-01 01:01:01'),(530,20260527215817,1,'2020-01-01 01:01:01'),(531,20260528201143,1,'2020-01-01 01:01:01'),(532,20260528201150,1,'2020-01-01 01:01:01'),(533,20260528211626,1,'2020-01-01 01:01:01'),(534,20260528213326,1,'2020-01-01 01:01:01'),(535,20260529091823,1,'2020-01-01 01:01:01'),(536,20260529120000,1,'2020-01-01 01:01:01'),(537,20260601200727,1,'2020-01-01 01:01:01'),(538,20260603101320,1,'2020-01-01 01:01:01'),(539,20260603120000,1,'2020-01-01 01:01:01'),(540,20260604221206,1,'2020-01-01 01:01:01'),(541,20260605195941,1,'2020-01-01 01:01:01'),(542,20260606051849,1,'2020-01-01 01:01:01'),(543,20260608160653,1,'2020-01-01 01:01:01'),(544,20260608173427,1,'2020-01-01 01:01:01'); +INSERT INTO `migration_status_tables` VALUES (1,0,1,'2020-01-01 01:01:01'),(2,20161118193812,1,'2020-01-01 01:01:01'),(3,20161118211713,1,'2020-01-01 01:01:01'),(4,20161118212436,1,'2020-01-01 01:01:01'),(5,20161118212515,1,'2020-01-01 01:01:01'),(6,20161118212528,1,'2020-01-01 01:01:01'),(7,20161118212538,1,'2020-01-01 01:01:01'),(8,20161118212549,1,'2020-01-01 01:01:01'),(9,20161118212557,1,'2020-01-01 01:01:01'),(10,20161118212604,1,'2020-01-01 01:01:01'),(11,20161118212613,1,'2020-01-01 01:01:01'),(12,20161118212621,1,'2020-01-01 01:01:01'),(13,20161118212630,1,'2020-01-01 01:01:01'),(14,20161118212641,1,'2020-01-01 01:01:01'),(15,20161118212649,1,'2020-01-01 01:01:01'),(16,20161118212656,1,'2020-01-01 01:01:01'),(17,20161118212758,1,'2020-01-01 01:01:01'),(18,20161128234849,1,'2020-01-01 01:01:01'),(19,20161230162221,1,'2020-01-01 01:01:01'),(20,20170104113816,1,'2020-01-01 01:01:01'),(21,20170105151732,1,'2020-01-01 01:01:01'),(22,20170108191242,1,'2020-01-01 01:01:01'),(23,20170109094020,1,'2020-01-01 01:01:01'),(24,20170109130438,1,'2020-01-01 01:01:01'),(25,20170110202752,1,'2020-01-01 01:01:01'),(26,20170111133013,1,'2020-01-01 01:01:01'),(27,20170117025759,1,'2020-01-01 01:01:01'),(28,20170118191001,1,'2020-01-01 01:01:01'),(29,20170119234632,1,'2020-01-01 01:01:01'),(30,20170124230432,1,'2020-01-01 01:01:01'),(31,20170127014618,1,'2020-01-01 01:01:01'),(32,20170131232841,1,'2020-01-01 01:01:01'),(33,20170223094154,1,'2020-01-01 01:01:01'),(34,20170306075207,1,'2020-01-01 01:01:01'),(35,20170309100733,1,'2020-01-01 01:01:01'),(36,20170331111922,1,'2020-01-01 01:01:01'),(37,20170502143928,1,'2020-01-01 01:01:01'),(38,20170504130602,1,'2020-01-01 01:01:01'),(39,20170509132100,1,'2020-01-01 01:01:01'),(40,20170519105647,1,'2020-01-01 01:01:01'),(41,20170519105648,1,'2020-01-01 01:01:01'),(42,20170831234300,1,'2020-01-01 01:01:01'),(43,20170831234301,1,'2020-01-01 01:01:01'),(44,20170831234303,1,'2020-01-01 01:01:01'),(45,20171116163618,1,'2020-01-01 01:01:01'),(46,20171219164727,1,'2020-01-01 01:01:01'),(47,20180620164811,1,'2020-01-01 01:01:01'),(48,20180620175054,1,'2020-01-01 01:01:01'),(49,20180620175055,1,'2020-01-01 01:01:01'),(50,20191010101639,1,'2020-01-01 01:01:01'),(51,20191010155147,1,'2020-01-01 01:01:01'),(52,20191220130734,1,'2020-01-01 01:01:01'),(53,20200311140000,1,'2020-01-01 01:01:01'),(54,20200405120000,1,'2020-01-01 01:01:01'),(55,20200407120000,1,'2020-01-01 01:01:01'),(56,20200420120000,1,'2020-01-01 01:01:01'),(57,20200504120000,1,'2020-01-01 01:01:01'),(58,20200512120000,1,'2020-01-01 01:01:01'),(59,20200707120000,1,'2020-01-01 01:01:01'),(60,20201011162341,1,'2020-01-01 01:01:01'),(61,20201021104586,1,'2020-01-01 01:01:01'),(62,20201102112520,1,'2020-01-01 01:01:01'),(63,20201208121729,1,'2020-01-01 01:01:01'),(64,20201215091637,1,'2020-01-01 01:01:01'),(65,20210119174155,1,'2020-01-01 01:01:01'),(66,20210326182902,1,'2020-01-01 01:01:01'),(67,20210421112652,1,'2020-01-01 01:01:01'),(68,20210506095025,1,'2020-01-01 01:01:01'),(69,20210513115729,1,'2020-01-01 01:01:01'),(70,20210526113559,1,'2020-01-01 01:01:01'),(71,20210601000001,1,'2020-01-01 01:01:01'),(72,20210601000002,1,'2020-01-01 01:01:01'),(73,20210601000003,1,'2020-01-01 01:01:01'),(74,20210601000004,1,'2020-01-01 01:01:01'),(75,20210601000005,1,'2020-01-01 01:01:01'),(76,20210601000006,1,'2020-01-01 01:01:01'),(77,20210601000007,1,'2020-01-01 01:01:01'),(78,20210601000008,1,'2020-01-01 01:01:01'),(79,20210606151329,1,'2020-01-01 01:01:01'),(80,20210616163757,1,'2020-01-01 01:01:01'),(81,20210617174723,1,'2020-01-01 01:01:01'),(82,20210622160235,1,'2020-01-01 01:01:01'),(83,20210623100031,1,'2020-01-01 01:01:01'),(84,20210623133615,1,'2020-01-01 01:01:01'),(85,20210708143152,1,'2020-01-01 01:01:01'),(86,20210709124443,1,'2020-01-01 01:01:01'),(87,20210712155608,1,'2020-01-01 01:01:01'),(88,20210714102108,1,'2020-01-01 01:01:01'),(89,20210719153709,1,'2020-01-01 01:01:01'),(90,20210721171531,1,'2020-01-01 01:01:01'),(91,20210723135713,1,'2020-01-01 01:01:01'),(92,20210802135933,1,'2020-01-01 01:01:01'),(93,20210806112844,1,'2020-01-01 01:01:01'),(94,20210810095603,1,'2020-01-01 01:01:01'),(95,20210811150223,1,'2020-01-01 01:01:01'),(96,20210818151827,1,'2020-01-01 01:01:01'),(97,20210818151828,1,'2020-01-01 01:01:01'),(98,20210818182258,1,'2020-01-01 01:01:01'),(99,20210819131107,1,'2020-01-01 01:01:01'),(100,20210819143446,1,'2020-01-01 01:01:01'),(101,20210903132338,1,'2020-01-01 01:01:01'),(102,20210915144307,1,'2020-01-01 01:01:01'),(103,20210920155130,1,'2020-01-01 01:01:01'),(104,20210927143115,1,'2020-01-01 01:01:01'),(105,20210927143116,1,'2020-01-01 01:01:01'),(106,20211013133706,1,'2020-01-01 01:01:01'),(107,20211013133707,1,'2020-01-01 01:01:01'),(108,20211102135149,1,'2020-01-01 01:01:01'),(109,20211109121546,1,'2020-01-01 01:01:01'),(110,20211110163320,1,'2020-01-01 01:01:01'),(111,20211116184029,1,'2020-01-01 01:01:01'),(112,20211116184030,1,'2020-01-01 01:01:01'),(113,20211202092042,1,'2020-01-01 01:01:01'),(114,20211202181033,1,'2020-01-01 01:01:01'),(115,20211207161856,1,'2020-01-01 01:01:01'),(116,20211216131203,1,'2020-01-01 01:01:01'),(117,20211221110132,1,'2020-01-01 01:01:01'),(118,20220107155700,1,'2020-01-01 01:01:01'),(119,20220125105650,1,'2020-01-01 01:01:01'),(120,20220201084510,1,'2020-01-01 01:01:01'),(121,20220208144830,1,'2020-01-01 01:01:01'),(122,20220208144831,1,'2020-01-01 01:01:01'),(123,20220215152203,1,'2020-01-01 01:01:01'),(124,20220223113157,1,'2020-01-01 01:01:01'),(125,20220307104655,1,'2020-01-01 01:01:01'),(126,20220309133956,1,'2020-01-01 01:01:01'),(127,20220316155700,1,'2020-01-01 01:01:01'),(128,20220323152301,1,'2020-01-01 01:01:01'),(129,20220330100659,1,'2020-01-01 01:01:01'),(130,20220404091216,1,'2020-01-01 01:01:01'),(131,20220419140750,1,'2020-01-01 01:01:01'),(132,20220428140039,1,'2020-01-01 01:01:01'),(133,20220503134048,1,'2020-01-01 01:01:01'),(134,20220524102918,1,'2020-01-01 01:01:01'),(135,20220526123327,1,'2020-01-01 01:01:01'),(136,20220526123328,1,'2020-01-01 01:01:01'),(137,20220526123329,1,'2020-01-01 01:01:01'),(138,20220608113128,1,'2020-01-01 01:01:01'),(139,20220627104817,1,'2020-01-01 01:01:01'),(140,20220704101843,1,'2020-01-01 01:01:01'),(141,20220708095046,1,'2020-01-01 01:01:01'),(142,20220713091130,1,'2020-01-01 01:01:01'),(143,20220802135510,1,'2020-01-01 01:01:01'),(144,20220818101352,1,'2020-01-01 01:01:01'),(145,20220822161445,1,'2020-01-01 01:01:01'),(146,20220831100036,1,'2020-01-01 01:01:01'),(147,20220831100151,1,'2020-01-01 01:01:01'),(148,20220908181826,1,'2020-01-01 01:01:01'),(149,20220914154915,1,'2020-01-01 01:01:01'),(150,20220915165115,1,'2020-01-01 01:01:01'),(151,20220915165116,1,'2020-01-01 01:01:01'),(152,20220928100158,1,'2020-01-01 01:01:01'),(153,20221014084130,1,'2020-01-01 01:01:01'),(154,20221027085019,1,'2020-01-01 01:01:01'),(155,20221101103952,1,'2020-01-01 01:01:01'),(156,20221104144401,1,'2020-01-01 01:01:01'),(157,20221109100749,1,'2020-01-01 01:01:01'),(158,20221115104546,1,'2020-01-01 01:01:01'),(159,20221130114928,1,'2020-01-01 01:01:01'),(160,20221205112142,1,'2020-01-01 01:01:01'),(161,20221216115820,1,'2020-01-01 01:01:01'),(162,20221220195934,1,'2020-01-01 01:01:01'),(163,20221220195935,1,'2020-01-01 01:01:01'),(164,20221223174807,1,'2020-01-01 01:01:01'),(165,20221227163855,1,'2020-01-01 01:01:01'),(166,20221227163856,1,'2020-01-01 01:01:01'),(167,20230202224725,1,'2020-01-01 01:01:01'),(168,20230206163608,1,'2020-01-01 01:01:01'),(169,20230214131519,1,'2020-01-01 01:01:01'),(170,20230303135738,1,'2020-01-01 01:01:01'),(171,20230313135301,1,'2020-01-01 01:01:01'),(172,20230313141819,1,'2020-01-01 01:01:01'),(173,20230315104937,1,'2020-01-01 01:01:01'),(174,20230317173844,1,'2020-01-01 01:01:01'),(175,20230320133602,1,'2020-01-01 01:01:01'),(176,20230330100011,1,'2020-01-01 01:01:01'),(177,20230330134823,1,'2020-01-01 01:01:01'),(178,20230405232025,1,'2020-01-01 01:01:01'),(179,20230408084104,1,'2020-01-01 01:01:01'),(180,20230411102858,1,'2020-01-01 01:01:01'),(181,20230421155932,1,'2020-01-01 01:01:01'),(182,20230425082126,1,'2020-01-01 01:01:01'),(183,20230425105727,1,'2020-01-01 01:01:01'),(184,20230501154913,1,'2020-01-01 01:01:01'),(185,20230503101418,1,'2020-01-01 01:01:01'),(186,20230515144206,1,'2020-01-01 01:01:01'),(187,20230517140952,1,'2020-01-01 01:01:01'),(188,20230517152807,1,'2020-01-01 01:01:01'),(189,20230518114155,1,'2020-01-01 01:01:01'),(190,20230520153236,1,'2020-01-01 01:01:01'),(191,20230525151159,1,'2020-01-01 01:01:01'),(192,20230530122103,1,'2020-01-01 01:01:01'),(193,20230602111827,1,'2020-01-01 01:01:01'),(194,20230608103123,1,'2020-01-01 01:01:01'),(195,20230629140529,1,'2020-01-01 01:01:01'),(196,20230629140530,1,'2020-01-01 01:01:01'),(197,20230711144622,1,'2020-01-01 01:01:01'),(198,20230721135421,1,'2020-01-01 01:01:01'),(199,20230721161508,1,'2020-01-01 01:01:01'),(200,20230726115701,1,'2020-01-01 01:01:01'),(201,20230807100822,1,'2020-01-01 01:01:01'),(202,20230814150442,1,'2020-01-01 01:01:01'),(203,20230823122728,1,'2020-01-01 01:01:01'),(204,20230906152143,1,'2020-01-01 01:01:01'),(205,20230911163618,1,'2020-01-01 01:01:01'),(206,20230912101759,1,'2020-01-01 01:01:01'),(207,20230915101341,1,'2020-01-01 01:01:01'),(208,20230918132351,1,'2020-01-01 01:01:01'),(209,20231004144339,1,'2020-01-01 01:01:01'),(210,20231009094541,1,'2020-01-01 01:01:01'),(211,20231009094542,1,'2020-01-01 01:01:01'),(212,20231009094543,1,'2020-01-01 01:01:01'),(213,20231009094544,1,'2020-01-01 01:01:01'),(214,20231016091915,1,'2020-01-01 01:01:01'),(215,20231024174135,1,'2020-01-01 01:01:01'),(216,20231025120016,1,'2020-01-01 01:01:01'),(217,20231025160156,1,'2020-01-01 01:01:01'),(218,20231031165350,1,'2020-01-01 01:01:01'),(219,20231106144110,1,'2020-01-01 01:01:01'),(220,20231107130934,1,'2020-01-01 01:01:01'),(221,20231109115838,1,'2020-01-01 01:01:01'),(222,20231121054530,1,'2020-01-01 01:01:01'),(223,20231122101320,1,'2020-01-01 01:01:01'),(224,20231130132828,1,'2020-01-01 01:01:01'),(225,20231130132931,1,'2020-01-01 01:01:01'),(226,20231204155427,1,'2020-01-01 01:01:01'),(227,20231206142340,1,'2020-01-01 01:01:01'),(228,20231207102320,1,'2020-01-01 01:01:01'),(229,20231207102321,1,'2020-01-01 01:01:01'),(230,20231207133731,1,'2020-01-01 01:01:01'),(231,20231212094238,1,'2020-01-01 01:01:01'),(232,20231212095734,1,'2020-01-01 01:01:01'),(233,20231212161121,1,'2020-01-01 01:01:01'),(234,20231215122713,1,'2020-01-01 01:01:01'),(235,20231219143041,1,'2020-01-01 01:01:01'),(236,20231224070653,1,'2020-01-01 01:01:01'),(237,20240110134315,1,'2020-01-01 01:01:01'),(238,20240119091637,1,'2020-01-01 01:01:01'),(239,20240126020642,1,'2020-01-01 01:01:01'),(240,20240126020643,1,'2020-01-01 01:01:01'),(241,20240129162819,1,'2020-01-01 01:01:01'),(242,20240130115133,1,'2020-01-01 01:01:01'),(243,20240131083822,1,'2020-01-01 01:01:01'),(244,20240205095928,1,'2020-01-01 01:01:01'),(245,20240205121956,1,'2020-01-01 01:01:01'),(246,20240209110212,1,'2020-01-01 01:01:01'),(247,20240212111533,1,'2020-01-01 01:01:01'),(248,20240221112844,1,'2020-01-01 01:01:01'),(249,20240222073518,1,'2020-01-01 01:01:01'),(250,20240222135115,1,'2020-01-01 01:01:01'),(251,20240226082255,1,'2020-01-01 01:01:01'),(252,20240228082706,1,'2020-01-01 01:01:01'),(253,20240301173035,1,'2020-01-01 01:01:01'),(254,20240302111134,1,'2020-01-01 01:01:01'),(255,20240312103753,1,'2020-01-01 01:01:01'),(256,20240313143416,1,'2020-01-01 01:01:01'),(257,20240314085226,1,'2020-01-01 01:01:01'),(258,20240314151747,1,'2020-01-01 01:01:01'),(259,20240320145650,1,'2020-01-01 01:01:01'),(260,20240327115530,1,'2020-01-01 01:01:01'),(261,20240327115617,1,'2020-01-01 01:01:01'),(262,20240408085837,1,'2020-01-01 01:01:01'),(263,20240415104633,1,'2020-01-01 01:01:01'),(264,20240430111727,1,'2020-01-01 01:01:01'),(265,20240515200020,1,'2020-01-01 01:01:01'),(266,20240521143023,1,'2020-01-01 01:01:01'),(267,20240521143024,1,'2020-01-01 01:01:01'),(268,20240601174138,1,'2020-01-01 01:01:01'),(269,20240607133721,1,'2020-01-01 01:01:01'),(270,20240612150059,1,'2020-01-01 01:01:01'),(271,20240613162201,1,'2020-01-01 01:01:01'),(272,20240613172616,1,'2020-01-01 01:01:01'),(273,20240618142419,1,'2020-01-01 01:01:01'),(274,20240625093543,1,'2020-01-01 01:01:01'),(275,20240626195531,1,'2020-01-01 01:01:01'),(276,20240702123921,1,'2020-01-01 01:01:01'),(277,20240703154849,1,'2020-01-01 01:01:01'),(278,20240707134035,1,'2020-01-01 01:01:01'),(279,20240707134036,1,'2020-01-01 01:01:01'),(280,20240709124958,1,'2020-01-01 01:01:01'),(281,20240709132642,1,'2020-01-01 01:01:01'),(282,20240709183940,1,'2020-01-01 01:01:01'),(283,20240710155623,1,'2020-01-01 01:01:01'),(284,20240723102712,1,'2020-01-01 01:01:01'),(285,20240725152735,1,'2020-01-01 01:01:01'),(286,20240725182118,1,'2020-01-01 01:01:01'),(287,20240726100517,1,'2020-01-01 01:01:01'),(288,20240730171504,1,'2020-01-01 01:01:01'),(289,20240730174056,1,'2020-01-01 01:01:01'),(290,20240730215453,1,'2020-01-01 01:01:01'),(291,20240730374423,1,'2020-01-01 01:01:01'),(292,20240801115359,1,'2020-01-01 01:01:01'),(293,20240802101043,1,'2020-01-01 01:01:01'),(294,20240802113716,1,'2020-01-01 01:01:01'),(295,20240814135330,1,'2020-01-01 01:01:01'),(296,20240815000000,1,'2020-01-01 01:01:01'),(297,20240815000001,1,'2020-01-01 01:01:01'),(298,20240816103247,1,'2020-01-01 01:01:01'),(299,20240820091218,1,'2020-01-01 01:01:01'),(300,20240826111228,1,'2020-01-01 01:01:01'),(301,20240826160025,1,'2020-01-01 01:01:01'),(302,20240829165448,1,'2020-01-01 01:01:01'),(303,20240829165605,1,'2020-01-01 01:01:01'),(304,20240829165715,1,'2020-01-01 01:01:01'),(305,20240829165930,1,'2020-01-01 01:01:01'),(306,20240829170023,1,'2020-01-01 01:01:01'),(307,20240829170033,1,'2020-01-01 01:01:01'),(308,20240829170044,1,'2020-01-01 01:01:01'),(309,20240905105135,1,'2020-01-01 01:01:01'),(310,20240905140514,1,'2020-01-01 01:01:01'),(311,20240905200000,1,'2020-01-01 01:01:01'),(312,20240905200001,1,'2020-01-01 01:01:01'),(313,20241002104104,1,'2020-01-01 01:01:01'),(314,20241002104105,1,'2020-01-01 01:01:01'),(315,20241002104106,1,'2020-01-01 01:01:01'),(316,20241002210000,1,'2020-01-01 01:01:01'),(317,20241003145349,1,'2020-01-01 01:01:01'),(318,20241004005000,1,'2020-01-01 01:01:01'),(319,20241008083925,1,'2020-01-01 01:01:01'),(320,20241009090010,1,'2020-01-01 01:01:01'),(321,20241017163402,1,'2020-01-01 01:01:01'),(322,20241021224359,1,'2020-01-01 01:01:01'),(323,20241022140321,1,'2020-01-01 01:01:01'),(324,20241025111236,1,'2020-01-01 01:01:01'),(325,20241025112748,1,'2020-01-01 01:01:01'),(326,20241025141855,1,'2020-01-01 01:01:01'),(327,20241110152839,1,'2020-01-01 01:01:01'),(328,20241110152840,1,'2020-01-01 01:01:01'),(329,20241110152841,1,'2020-01-01 01:01:01'),(330,20241116233322,1,'2020-01-01 01:01:01'),(331,20241122171434,1,'2020-01-01 01:01:01'),(332,20241125150614,1,'2020-01-01 01:01:01'),(333,20241203125346,1,'2020-01-01 01:01:01'),(334,20241203130032,1,'2020-01-01 01:01:01'),(335,20241205122800,1,'2020-01-01 01:01:01'),(336,20241209164540,1,'2020-01-01 01:01:01'),(337,20241210140021,1,'2020-01-01 01:01:01'),(338,20241219180042,1,'2020-01-01 01:01:01'),(339,20241220100000,1,'2020-01-01 01:01:01'),(340,20241220114903,1,'2020-01-01 01:01:01'),(341,20241220114904,1,'2020-01-01 01:01:01'),(342,20241224000000,1,'2020-01-01 01:01:01'),(343,20241230000000,1,'2020-01-01 01:01:01'),(344,20241231112624,1,'2020-01-01 01:01:01'),(345,20250102121439,1,'2020-01-01 01:01:01'),(346,20250121094045,1,'2020-01-01 01:01:01'),(347,20250121094500,1,'2020-01-01 01:01:01'),(348,20250121094600,1,'2020-01-01 01:01:01'),(349,20250121094700,1,'2020-01-01 01:01:01'),(350,20250124194347,1,'2020-01-01 01:01:01'),(351,20250127162751,1,'2020-01-01 01:01:01'),(352,20250213104005,1,'2020-01-01 01:01:01'),(353,20250214205657,1,'2020-01-01 01:01:01'),(354,20250217093329,1,'2020-01-01 01:01:01'),(355,20250219090511,1,'2020-01-01 01:01:01'),(356,20250219100000,1,'2020-01-01 01:01:01'),(357,20250219142401,1,'2020-01-01 01:01:01'),(358,20250224184002,1,'2020-01-01 01:01:01'),(359,20250225085436,1,'2020-01-01 01:01:01'),(360,20250226000000,1,'2020-01-01 01:01:01'),(361,20250226153445,1,'2020-01-01 01:01:01'),(362,20250304162702,1,'2020-01-01 01:01:01'),(363,20250306144233,1,'2020-01-01 01:01:01'),(364,20250313163430,1,'2020-01-01 01:01:01'),(365,20250317130944,1,'2020-01-01 01:01:01'),(366,20250318165922,1,'2020-01-01 01:01:01'),(367,20250320132525,1,'2020-01-01 01:01:01'),(368,20250320200000,1,'2020-01-01 01:01:01'),(369,20250326161930,1,'2020-01-01 01:01:01'),(370,20250326161931,1,'2020-01-01 01:01:01'),(371,20250331042354,1,'2020-01-01 01:01:01'),(372,20250331154206,1,'2020-01-01 01:01:01'),(373,20250401155831,1,'2020-01-01 01:01:01'),(374,20250408133233,1,'2020-01-01 01:01:01'),(375,20250410104321,1,'2020-01-01 01:01:01'),(376,20250421085116,1,'2020-01-01 01:01:01'),(377,20250422095806,1,'2020-01-01 01:01:01'),(378,20250424153059,1,'2020-01-01 01:01:01'),(379,20250430103833,1,'2020-01-01 01:01:01'),(380,20250430112622,1,'2020-01-01 01:01:01'),(381,20250501162727,1,'2020-01-01 01:01:01'),(382,20250502154517,1,'2020-01-01 01:01:01'),(383,20250502222222,1,'2020-01-01 01:01:01'),(384,20250507170845,1,'2020-01-01 01:01:01'),(385,20250513162912,1,'2020-01-01 01:01:01'),(386,20250519161614,1,'2020-01-01 01:01:01'),(387,20250519170000,1,'2020-01-01 01:01:01'),(388,20250520153848,1,'2020-01-01 01:01:01'),(389,20250528115932,1,'2020-01-01 01:01:01'),(390,20250529102706,1,'2020-01-01 01:01:01'),(391,20250603105558,1,'2020-01-01 01:01:01'),(392,20250609102714,1,'2020-01-01 01:01:01'),(393,20250609112613,1,'2020-01-01 01:01:01'),(394,20250613103810,1,'2020-01-01 01:01:01'),(395,20250616193950,1,'2020-01-01 01:01:01'),(396,20250624140757,1,'2020-01-01 01:01:01'),(397,20250626130239,1,'2020-01-01 01:01:01'),(398,20250629131032,1,'2020-01-01 01:01:01'),(399,20250701155654,1,'2020-01-01 01:01:01'),(400,20250707095725,1,'2020-01-01 01:01:01'),(401,20250716152435,1,'2020-01-01 01:01:01'),(402,20250718091828,1,'2020-01-01 01:01:01'),(403,20250728122229,1,'2020-01-01 01:01:01'),(404,20250731122715,1,'2020-01-01 01:01:01'),(405,20250731151000,1,'2020-01-01 01:01:01'),(406,20250803000000,1,'2020-01-01 01:01:01'),(407,20250805083116,1,'2020-01-01 01:01:01'),(408,20250807140441,1,'2020-01-01 01:01:01'),(409,20250808000000,1,'2020-01-01 01:01:01'),(410,20250811155036,1,'2020-01-01 01:01:01'),(411,20250813205039,1,'2020-01-01 01:01:01'),(412,20250814123333,1,'2020-01-01 01:01:01'),(413,20250815130115,1,'2020-01-01 01:01:01'),(414,20250816115553,1,'2020-01-01 01:01:01'),(415,20250817154557,1,'2020-01-01 01:01:01'),(416,20250825113751,1,'2020-01-01 01:01:01'),(417,20250827113140,1,'2020-01-01 01:01:01'),(418,20250828120836,1,'2020-01-01 01:01:01'),(419,20250902112642,1,'2020-01-01 01:01:01'),(420,20250904091745,1,'2020-01-01 01:01:01'),(421,20250905090000,1,'2020-01-01 01:01:01'),(422,20250922083056,1,'2020-01-01 01:01:01'),(423,20250923120000,1,'2020-01-01 01:01:01'),(424,20250926123048,1,'2020-01-01 01:01:01'),(425,20251015103505,1,'2020-01-01 01:01:01'),(426,20251015103600,1,'2020-01-01 01:01:01'),(427,20251015103700,1,'2020-01-01 01:01:01'),(428,20251015103800,1,'2020-01-01 01:01:01'),(429,20251015103900,1,'2020-01-01 01:01:01'),(430,20251028140000,1,'2020-01-01 01:01:01'),(431,20251028140100,1,'2020-01-01 01:01:01'),(432,20251028140110,1,'2020-01-01 01:01:01'),(433,20251028140200,1,'2020-01-01 01:01:01'),(434,20251028140300,1,'2020-01-01 01:01:01'),(435,20251028140400,1,'2020-01-01 01:01:01'),(436,20251031154558,1,'2020-01-01 01:01:01'),(437,20251103160848,1,'2020-01-01 01:01:01'),(438,20251104112849,1,'2020-01-01 01:01:01'),(439,20251106000000,1,'2020-01-01 01:01:01'),(440,20251107164629,1,'2020-01-01 01:01:01'),(441,20251107170854,1,'2020-01-01 01:01:01'),(442,20251110172137,1,'2020-01-01 01:01:01'),(443,20251111153133,1,'2020-01-01 01:01:01'),(444,20251117020000,1,'2020-01-01 01:01:01'),(445,20251117020100,1,'2020-01-01 01:01:01'),(446,20251117020200,1,'2020-01-01 01:01:01'),(447,20251121100000,1,'2020-01-01 01:01:01'),(448,20251121124239,1,'2020-01-01 01:01:01'),(449,20251124090450,1,'2020-01-01 01:01:01'),(450,20251124135808,1,'2020-01-01 01:01:01'),(451,20251124140138,1,'2020-01-01 01:01:01'),(452,20251124162948,1,'2020-01-01 01:01:01'),(453,20251127113559,1,'2020-01-01 01:01:01'),(454,20251202162232,1,'2020-01-01 01:01:01'),(455,20251203170808,1,'2020-01-01 01:01:01'),(456,20251207050413,1,'2020-01-01 01:01:01'),(457,20251208215800,1,'2020-01-01 01:01:01'),(458,20251209221730,1,'2020-01-01 01:01:01'),(459,20251209221850,1,'2020-01-01 01:01:01'),(460,20251215163721,1,'2020-01-01 01:01:01'),(461,20251217000000,1,'2020-01-01 01:01:01'),(462,20251217120000,1,'2020-01-01 01:01:01'),(463,20251229000000,1,'2020-01-01 01:01:01'),(464,20251229000010,1,'2020-01-01 01:01:01'),(465,20251229000020,1,'2020-01-01 01:01:01'),(466,20260106000000,1,'2020-01-01 01:01:01'),(467,20260108200708,1,'2020-01-01 01:01:01'),(468,20260108214732,1,'2020-01-01 01:01:01'),(469,20260109231821,1,'2020-01-01 01:01:01'),(470,20260113012054,1,'2020-01-01 01:01:01'),(471,20260124200020,1,'2020-01-01 01:01:01'),(472,20260126150840,1,'2020-01-01 01:01:01'),(473,20260126210724,1,'2020-01-01 01:01:01'),(474,20260202151756,1,'2020-01-01 01:01:01'),(475,20260205184907,1,'2020-01-01 01:01:01'),(476,20260210151544,1,'2020-01-01 01:01:01'),(477,20260210155109,1,'2020-01-01 01:01:01'),(478,20260210181120,1,'2020-01-01 01:01:01'),(479,20260211200153,1,'2020-01-01 01:01:01'),(480,20260217141240,1,'2020-01-01 01:01:01'),(481,20260217200906,1,'2020-01-01 01:01:01'),(482,20260218175704,1,'2020-01-01 01:01:01'),(483,20260314120000,1,'2020-01-01 01:01:01'),(484,20260316120000,1,'2020-01-01 01:01:01'),(485,20260316120001,1,'2020-01-01 01:01:01'),(486,20260316120002,1,'2020-01-01 01:01:01'),(487,20260316120003,1,'2020-01-01 01:01:01'),(488,20260316120004,1,'2020-01-01 01:01:01'),(489,20260316120005,1,'2020-01-01 01:01:01'),(490,20260316120006,1,'2020-01-01 01:01:01'),(491,20260316120007,1,'2020-01-01 01:01:01'),(492,20260316120008,1,'2020-01-01 01:01:01'),(493,20260316120009,1,'2020-01-01 01:01:01'),(494,20260316120010,1,'2020-01-01 01:01:01'),(495,20260317120000,1,'2020-01-01 01:01:01'),(496,20260318184559,1,'2020-01-01 01:01:01'),(497,20260319120000,1,'2020-01-01 01:01:01'),(498,20260323144117,1,'2020-01-01 01:01:01'),(499,20260324161944,1,'2020-01-01 01:01:01'),(500,20260324223334,1,'2020-01-01 01:01:01'),(501,20260326131501,1,'2020-01-01 01:01:01'),(502,20260326210603,1,'2020-01-01 01:01:01'),(503,20260331000000,1,'2020-01-01 01:01:01'),(504,20260401153000,1,'2020-01-01 01:01:01'),(505,20260401153001,1,'2020-01-01 01:01:01'),(506,20260401153503,1,'2020-01-01 01:01:01'),(507,20260403120000,1,'2020-01-01 01:01:01'),(508,20260409153713,1,'2020-01-01 01:01:01'),(509,20260409153714,1,'2020-01-01 01:01:01'),(510,20260409153715,1,'2020-01-01 01:01:01'),(511,20260409153716,1,'2020-01-01 01:01:01'),(512,20260409153717,1,'2020-01-01 01:01:01'),(513,20260409183610,1,'2020-01-01 01:01:01'),(514,20260410173222,1,'2020-01-01 01:01:01'),(515,20260422181702,1,'2020-01-01 01:01:01'),(516,20260423161823,1,'2020-01-01 01:01:01'),(517,20260423161824,1,'2020-01-01 01:01:01'),(518,20260518194422,1,'2020-01-01 01:01:01'),(519,20260522195224,1,'2020-01-01 01:01:01'),(520,20260522195225,1,'2020-01-01 01:01:01'),(521,20260522195226,1,'2020-01-01 01:01:01'),(522,20260522195227,1,'2020-01-01 01:01:01'),(523,20260522195229,1,'2020-01-01 01:01:01'),(524,20260522195230,1,'2020-01-01 01:01:01'),(525,20260522195231,1,'2020-01-01 01:01:01'),(526,20260522195232,1,'2020-01-01 01:01:01'),(527,20260522195233,1,'2020-01-01 01:01:01'),(528,20260522195234,1,'2020-01-01 01:01:01'),(529,20260522195235,1,'2020-01-01 01:01:01'),(530,20260527215817,1,'2020-01-01 01:01:01'),(531,20260528201143,1,'2020-01-01 01:01:01'),(532,20260528201150,1,'2020-01-01 01:01:01'),(533,20260528211626,1,'2020-01-01 01:01:01'),(534,20260528213326,1,'2020-01-01 01:01:01'),(535,20260529091823,1,'2020-01-01 01:01:01'),(536,20260529120000,1,'2020-01-01 01:01:01'),(537,20260601200727,1,'2020-01-01 01:01:01'),(538,20260603101320,1,'2020-01-01 01:01:01'),(539,20260603120000,1,'2020-01-01 01:01:01'),(540,20260604221206,1,'2020-01-01 01:01:01'),(541,20260605195941,1,'2020-01-01 01:01:01'),(542,20260606051849,1,'2020-01-01 01:01:01'),(543,20260608160653,1,'2020-01-01 01:01:01'),(544,20260608173427,1,'2020-01-01 01:01:01'),(545,20260608202705,1,'2020-01-01 01:01:01'); /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `mobile_device_management_solutions` ( @@ -3356,7 +3358,8 @@ CREATE TABLE `vulnerability_host_counts` ( `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `global_stats` tinyint(1) NOT NULL DEFAULT '0', - UNIQUE KEY `cve_team_id_global_stats` (`cve`,`team_id`,`global_stats`) + UNIQUE KEY `cve_team_id_global_stats` (`cve`,`team_id`,`global_stats`), + KEY `idx_vhc_scope_cve` (`global_stats`,`team_id`,`host_count`,`cve`) ) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; /*!40101 SET @saved_cs_client = @@character_set_client */; diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index b50c290689..0ac1681d9c 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -1697,20 +1697,17 @@ func canUseOptimizedListQuery(opts fleet.SoftwareListOptions) bool { // Only optimize if: // 1. We're listing all software (not filtering by HostID) // 2. We're ordering by hosts_count only (covering index requirement) - // 3. We're not filtering by CVE fields - // 4. We're not searching (which requires CVE join) - // 5. We're not using multi-column sorts (e.g., "name,id") + // 3. We're not using multi-column sorts (e.g., "name,id") // // The covering index optimization only works when ordering by hosts_count // because the inner query uses a covering index scan that only includes // (team_id, global_stats, hosts_count DESC, software_id). This dramatically // improves performance. + // + // Filters (VulnerableOnly / KnownExploit / MinimumCVSS / MaximumCVSS / + // MatchQuery) are now supported in the inner query via EXISTS pushdown — + // see buildOptimizedListSoftwareSQL. return opts.HostID == nil && - !opts.VulnerableOnly && - opts.MinimumCVSS == 0 && - opts.MaximumCVSS == 0 && - !opts.KnownExploit && - opts.ListOptions.MatchQuery == "" && orderKey == "hosts_count" && !isMultiColumnSort(opts.ListOptions.OrderKey) } @@ -1772,6 +1769,54 @@ func buildOptimizedListSoftwareSQL(opts fleet.SoftwareListOptions) (string, []in args = append(args, *opts.TeamID) } + // Filter pushdown: when the caller requests vulnerable software, a CISA + // known exploit, a CVSS range, or a search, push these into the inner + // query as semi-joins so they prune candidate rows BEFORE pagination + // instead of expanding row count via outer JOIN+GROUP BY (as the goqu + // fallback does). The covering index scan on idx_software_host_counts_ + // team_global_hosts_desc still drives the query; each EXISTS probe uses + // idx_software_cve_cve / unq_software_id_cve / idx_cve_meta_exploit / + // idx_cve_meta_cvss_score from #45415. + if opts.VulnerableOnly || opts.KnownExploit || opts.MinimumCVSS > 0 || opts.MaximumCVSS > 0 { + needsCVEMeta := opts.KnownExploit || opts.MinimumCVSS > 0 || opts.MaximumCVSS > 0 + innerSQL += ` AND EXISTS ( + SELECT 1 FROM software_cve sc` + if needsCVEMeta { + innerSQL += ` INNER JOIN cve_meta cm ON cm.cve = sc.cve` + if opts.KnownExploit { + innerSQL += ` AND cm.cisa_known_exploit = 1` + } + if opts.MinimumCVSS > 0 { + innerSQL += ` AND cm.cvss_score >= ?` + args = append(args, opts.MinimumCVSS) + } + if opts.MaximumCVSS > 0 { + innerSQL += ` AND cm.cvss_score <= ?` + args = append(args, opts.MaximumCVSS) + } + } + innerSQL += ` WHERE sc.software_id = shc.software_id)` + } + + // Search filter (matches the semantics of the goqu fallback): + // software must have a software_titles row, and any of name / version / + // title name / one of its CVEs must match the LIKE pattern. + if match := opts.ListOptions.MatchQuery; match != "" { + pattern := likePattern(match) + innerSQL += ` AND EXISTS ( + SELECT 1 FROM software s + INNER JOIN software_titles st ON st.id = s.title_id + WHERE s.id = shc.software_id + AND ( + s.name LIKE ? + OR s.version LIKE ? + OR st.name LIKE ? + OR EXISTS (SELECT 1 FROM software_cve sc WHERE sc.software_id = s.id AND sc.cve LIKE ?) + ) + )` + args = append(args, pattern, pattern, pattern, pattern) + } + // software_id is the secondary key to make ordering deterministic innerSQL += " ORDER BY shc.hosts_count " + direction + ", shc.software_id " + secondaryDirection diff --git a/server/datastore/mysql/vulnerabilities.go b/server/datastore/mysql/vulnerabilities.go index f0953a689e..c516669da4 100644 --- a/server/datastore/mysql/vulnerabilities.go +++ b/server/datastore/mysql/vulnerabilities.go @@ -231,17 +231,204 @@ func (ds *Datastore) SoftwareByCVE(ctx context.Context, cve string, teamID *uint return } +// vulnerabilitiesCMOrderKeys are the columns sourced from cve_meta that the API +// allows clients to sort by. When the OrderKey is one of these, the inner query +// must LEFT JOIN cve_meta so the ORDER BY in the paginated subquery can +// reference the column. +var vulnerabilitiesCMOrderKeys = map[string]struct{}{ + "cvss_score": {}, + "epss_probability": {}, + "cisa_known_exploit": {}, + "cve_published": {}, +} + +// vulnerabilitiesOuterOrderKeys maps user-facing order keys to column +// expressions valid in the OUTER query of buildListVulnerabilitiesSQL, where +// the paginated inner query is aliased as `p`. The inner query uses +// vulnerabilitiesAllowedOrderKeys (vhc.*), but the `vhc` alias is out of scope +// in the outer SELECT, so the restated outer ORDER BY needs these outer-scope +// references instead. Every sortable column is projected by the inner query +// (cve_meta columns are included whenever they're the order key, see +// needCMInInner), so all references go through `p` regardless of IsEE — the +// outer `cm` join only exists for EE and only supplies `description`. +var vulnerabilitiesOuterOrderKeys = common_mysql.OrderKeyAllowlist{ + "cve": "p.cve", + "cvss_score": "p.cvss_score", + "epss_probability": "p.epss_probability", + "cisa_known_exploit": "p.cisa_known_exploit", + "cve_published": "p.cve_published", + "host_count": "p.hosts_count", + "hosts_count": "p.hosts_count", + "host_count_updated_at": "p.hosts_count_updated_at", + "hosts_count_updated_at": "p.hosts_count_updated_at", +} + func (ds *Datastore) ListVulnerabilities(ctx context.Context, opt fleet.VulnListOptions) ([]fleet.VulnerabilityWithMetadata, *fleet.PaginationMetadata, error) { - // Define base select statements for EE and Free versions - // - // created_at: Use MIN() to get earliest discovery date across both tables. - // This is important because users can sort by this field, and in production (Dogfood) - // data shows significant differences (>1 year) between tables. - // Note: created_at can be NULL in schema but never is in practice. - // - // source: Pick first match, prioritizing software_cve. - // This field is not exposed in the API (json:"-"). - // Note: source can be NULL in schema but never is in practice. + opt.ListOptions.IncludeMetadata = !(opt.ListOptions.UsesCursorPagination()) + + selectStmt, args, err := buildListVulnerabilitiesSQL(&opt) + if err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "list vulnerabilities") + } + + var vulns []fleet.VulnerabilityWithMetadata + if err := sqlx.SelectContext(ctx, ds.reader(ctx), &vulns, selectStmt, args...); err != nil { + return nil, nil, ctxerr.Wrap(ctx, err, "list vulnerabilities") + } + + var metaData *fleet.PaginationMetadata + if opt.ListOptions.IncludeMetadata { + metaData = &fleet.PaginationMetadata{HasPreviousResults: opt.ListOptions.Page > 0} + if len(vulns) > int(opt.ListOptions.PerPage) { //nolint:gosec // dismiss G115 + metaData.HasNextResults = true + vulns = vulns[:len(vulns)-1] + } + } + + return vulns, metaData, nil +} + +// buildListVulnerabilitiesSQL constructs the SQL for ListVulnerabilities. +// +// The query is split into two stages so the expensive correlated scalar +// subqueries that compute `created_at` (MIN across software_cve and +// operating_system_vulnerabilities) and `source` only run on the paginated +// page, not on every matching row in vulnerability_host_counts. +// +// Inner query: filter, sort, and paginate vulnerability_host_counts (with +// an optional LEFT JOIN to cve_meta when filtering or sorting by a cve_meta +// column). The new idx_vhc_scope_cve makes the scope filter +// (global_stats, team_id, host_count > 0) an index range scan instead of +// the full-table scan previously observed. +// +// Outer query: enrich the paginated page with the cve_meta metadata +// columns (EE only) and the heavy created_at / source scalar subqueries. +// +// Special case: when OrderKey == "created_at" the value to sort by is the +// scalar subquery output itself, so the inner query has to include it. +// That falls back to the legacy single-statement form (preserved verbatim +// below) — performance is unchanged for that specific sort, but every +// other sort key benefits from the two-stage refactor. +func buildListVulnerabilitiesSQL(opt *fleet.VulnListOptions) (string, []any, error) { + if opt.ListOptions.OrderKey == "created_at" { + return buildListVulnerabilitiesLegacySQL(opt) + } + + _, cmOrderKey := vulnerabilitiesCMOrderKeys[opt.ListOptions.OrderKey] + needCMInInner := cmOrderKey || opt.KnownExploit + + var inner strings.Builder + inner.WriteString(` + SELECT + vhc.cve, + vhc.host_count AS hosts_count, + vhc.updated_at AS hosts_count_updated_at`) + if cmOrderKey { + inner.WriteString(`, + cm.cvss_score, + cm.epss_probability, + cm.cisa_known_exploit, + cm.published AS cve_published`) + } + inner.WriteString(` + FROM vulnerability_host_counts vhc`) + if needCMInInner { + inner.WriteString(` + LEFT JOIN cve_meta cm ON cm.cve = vhc.cve`) + } + inner.WriteString(` + WHERE vhc.host_count > 0 + AND ( + EXISTS (SELECT 1 FROM software_cve WHERE cve = vhc.cve) + OR EXISTS (SELECT 1 FROM operating_system_vulnerabilities WHERE cve = vhc.cve) + )`) + + var args []any + if opt.TeamID == nil { + inner.WriteString(" AND vhc.global_stats = 1") + } else { + inner.WriteString(" AND vhc.global_stats = 0 AND vhc.team_id = ?") + args = append(args, *opt.TeamID) + } + if opt.KnownExploit { + inner.WriteString(" AND cm.cisa_known_exploit = 1") + } + + innerSQL := inner.String() + if match := opt.ListOptions.MatchQuery; match != "" { + innerSQL, args = searchLike(innerSQL, args, match, "vhc.cve") + } + + // Add cve as a deterministic tie-breaker so pagination is stable across + // pages — vhc.cve is unique within a (global_stats, team_id) scope, so it + // fully orders any rows that tie on the primary sort column. See + // query_results.go for prior art using TestSecondaryOrderKey for this. + if opt.ListOptions.OrderKey != "" && opt.ListOptions.OrderKey != "cve" { + opt.ListOptions.TestSecondaryOrderKey = "cve" + opt.ListOptions.TestSecondaryOrderDirection = fleet.OrderAscending + } + + innerSQL, args, err := appendListOptionsWithCursorToSQLSecure(innerSQL, args, &opt.ListOptions, vulnerabilitiesAllowedOrderKeys) + if err != nil { + return "", nil, err + } + + var outer strings.Builder + outer.WriteString(` + SELECT + p.cve, + (SELECT MIN(created_at) FROM ( + SELECT created_at FROM software_cve WHERE cve = p.cve + UNION ALL + SELECT created_at FROM operating_system_vulnerabilities WHERE cve = p.cve + ) AS combined_dates) AS created_at, + COALESCE( + (SELECT source FROM software_cve WHERE cve = p.cve LIMIT 1), + (SELECT source FROM operating_system_vulnerabilities WHERE cve = p.cve LIMIT 1) + ) AS source,`) + if opt.IsEE { + outer.WriteString(` + cm.cvss_score, + cm.epss_probability, + cm.cisa_known_exploit, + cm.published AS cve_published, + cm.description,`) + } + outer.WriteString(` + p.hosts_count, + p.hosts_count_updated_at + FROM (`) + outer.WriteString(innerSQL) + outer.WriteString(`) AS p`) + if opt.IsEE { + outer.WriteString(` + LEFT JOIN cve_meta cm ON cm.cve = p.cve`) + } + + // The optimizer may not preserve the inner ORDER BY when wrapped in an + // outer SELECT, so restate the sort. The inner has already limited rows + // to the page, so this re-sort is bounded to perPage rows. Tie-break on + // p.cve so within-page order matches the inner's secondary sort. Use the + // outer-scope allowlist: the inner is aliased `p` and cve_meta is re-joined + // as `cm`, so the inner's `vhc.*` references are out of scope here. + if orderCol, ok := vulnerabilitiesOuterOrderKeys[opt.ListOptions.OrderKey]; ok && orderCol != "" { + direction := "ASC" + if opt.ListOptions.OrderDirection == fleet.OrderDescending { + direction = "DESC" + } + outer.WriteString(fmt.Sprintf(" ORDER BY %s %s", orderCol, direction)) + if orderCol != "p.cve" { + outer.WriteString(", p.cve ASC") + } + } + + return outer.String(), args, nil +} + +// buildListVulnerabilitiesLegacySQL preserves the original single-statement +// query used when OrderKey == "created_at" (the only sort key that has to +// reference the cross-table scalar subquery result). +func buildListVulnerabilitiesLegacySQL(opt *fleet.VulnListOptions) (string, []any, error) { eeSelectStmt := ` SELECT vhc.cve as cve, @@ -291,80 +478,64 @@ func (ds *Datastore) ListVulnerabilities(ctx context.Context, opt fleet.VulnList ) ` - // Choose the appropriate select statement based on EE or Free - var selectStmt string - if opt.IsEE { - selectStmt = eeSelectStmt - } else { + selectStmt := eeSelectStmt + if !opt.IsEE { selectStmt = freeSelectStmt } - // Prepare arguments for the query - var args []interface{} + var args []any if opt.TeamID == nil { selectStmt += " AND vhc.global_stats = 1" } else { selectStmt += " AND vhc.global_stats = 0 AND vhc.team_id = ?" args = append(args, *opt.TeamID) } - if opt.KnownExploit { selectStmt += " AND cm.cisa_known_exploit = 1" } - if match := opt.ListOptions.MatchQuery; match != "" { selectStmt, args = searchLike(selectStmt, args, match, "vhc.cve") } - opt.ListOptions.IncludeMetadata = !(opt.ListOptions.UsesCursorPagination()) - selectStmt, args, err := appendListOptionsWithCursorToSQLSecure(selectStmt, args, &opt.ListOptions, vulnerabilitiesAllowedOrderKeys) - if err != nil { - return nil, nil, ctxerr.Wrap(ctx, err, "list vulnerabilities") + // Tie-break on cve so pagination is stable across pages when the primary + // sort column has ties. + if opt.ListOptions.OrderKey != "" && opt.ListOptions.OrderKey != "cve" { + opt.ListOptions.TestSecondaryOrderKey = "cve" + opt.ListOptions.TestSecondaryOrderDirection = fleet.OrderAscending } - // Execute the query - var vulns []fleet.VulnerabilityWithMetadata - if err := sqlx.SelectContext(ctx, ds.reader(ctx), &vulns, selectStmt, args...); err != nil { - return nil, nil, ctxerr.Wrap(ctx, err, "list vulnerabilities") - } - - // Prepare metadata - var metaData *fleet.PaginationMetadata - if opt.ListOptions.IncludeMetadata { - metaData = &fleet.PaginationMetadata{HasPreviousResults: opt.ListOptions.Page > 0} - if len(vulns) > int(opt.ListOptions.PerPage) { //nolint:gosec // dismiss G115 - metaData.HasNextResults = true - vulns = vulns[:len(vulns)-1] - } - } - - return vulns, metaData, nil + return appendListOptionsWithCursorToSQLSecure(selectStmt, args, &opt.ListOptions, vulnerabilitiesAllowedOrderKeys) } func (ds *Datastore) CountVulnerabilities(ctx context.Context, opt fleet.VulnListOptions) (uint, error) { + // vhc.cve is already unique within a (global_stats, team_id) scope due to + // the existing UNIQUE KEY (cve, team_id, global_stats), so COUNT(*) gives + // the same result as COUNT(DISTINCT vhc.cve) but lets the optimizer pick + // idx_vhc_scope_cve without a dedup step. selectStmt := ` - SELECT - COUNT(DISTINCT vhc.cve) + SELECT COUNT(*) FROM vulnerability_host_counts vhc - LEFT JOIN cve_meta cm ON cm.cve = vhc.cve - WHERE vhc.host_count > 0 + ` + if opt.KnownExploit { + selectStmt += `LEFT JOIN cve_meta cm ON cm.cve = vhc.cve + ` + } + selectStmt += `WHERE vhc.host_count > 0 AND ( EXISTS (SELECT 1 FROM software_cve WHERE cve = vhc.cve) OR EXISTS (SELECT 1 FROM operating_system_vulnerabilities WHERE cve = vhc.cve) ) ` - var args []interface{} + var args []any if opt.TeamID == nil { selectStmt += " AND vhc.global_stats = 1" } else { selectStmt += " AND vhc.global_stats = 0 AND vhc.team_id = ?" - args = append(args, opt.TeamID) + args = append(args, *opt.TeamID) } - if opt.KnownExploit { selectStmt += " AND cm.cisa_known_exploit = 1" } - if match := opt.ListOptions.MatchQuery; match != "" { selectStmt, args = searchLike(selectStmt, args, match, "vhc.cve") } diff --git a/server/datastore/mysql/vulnerabilities_bench_test.go b/server/datastore/mysql/vulnerabilities_bench_test.go new file mode 100644 index 0000000000..0bcbba0c41 --- /dev/null +++ b/server/datastore/mysql/vulnerabilities_bench_test.go @@ -0,0 +1,576 @@ +package mysql + +import ( + "context" + "fmt" + "math/rand/v2" + "os" + "strings" + "testing" + "time" + + "github.com/fleetdm/fleet/v4/server/fleet" +) + +// To run: +// +// MYSQL_TEST=1 go test -bench=BenchmarkListVulnerabilities -benchtime=10x \ +// -run='^$' ./server/datastore/mysql/ +// +// To compare before/after with benchstat: +// +// git checkout main +// MYSQL_TEST=1 go test -bench=. -benchtime=10x -run='^$' \ +// ./server/datastore/mysql/ -count=5 > /tmp/before.txt +// +// git checkout +// MYSQL_TEST=1 go test -bench=. -benchtime=10x -run='^$' \ +// ./server/datastore/mysql/ -count=5 > /tmp/after.txt +// +// benchstat /tmp/before.txt /tmp/after.txt +// +// Tune the dataset size with FLEET_BENCH_SIZE: "smoke" (default, ~5s seed), +// "realistic" (~30s seed), or "large" (~3min seed). EXPLAIN plan differences +// show up at any size; wall-time differences widen with scale. + +type benchSize struct { + numSoftware int + numCVEs int + cvesPerSW int + // Fraction of CVEs that also appear in operating_system_vulnerabilities, + // so the (EXISTS software_cve OR EXISTS operating_system_vulnerabilities) + // branch in ListVulnerabilities actually has to consider both sides. + osVulnFraction float64 + // Number of fake operating_system rows the OS vulns are spread across. + // Doesn't have to be realistic — we just need ids to point at. + numOperatingSystems int + // teamSoftwareFraction: fraction of software that also has a team-scoped + // software_host_counts row (team_id=1, global_stats=0). Same idea for vulns. + teamSoftwareFraction float64 + teamVulnFraction float64 +} + +var benchSizes = map[string]benchSize{ + "smoke": { + numSoftware: 2_000, numCVEs: 5_000, cvesPerSW: 3, + osVulnFraction: 0.3, numOperatingSystems: 20, + teamSoftwareFraction: 0.4, teamVulnFraction: 0.4, + }, + "realistic": { + numSoftware: 50_000, numCVEs: 50_000, cvesPerSW: 4, + osVulnFraction: 0.3, numOperatingSystems: 50, + teamSoftwareFraction: 0.4, teamVulnFraction: 0.4, + }, + "large": { + numSoftware: 200_000, numCVEs: 200_000, cvesPerSW: 5, + osVulnFraction: 0.3, numOperatingSystems: 100, + teamSoftwareFraction: 0.4, teamVulnFraction: 0.4, + }, +} + +// benchTeamID is the team_id seeded for team-scoped rows. Benchmarks that +// exercise the team path should pass &benchTeamID as the TeamID option. +const benchTeamID uint = 1 + +func pickBenchSize(tb testing.TB) benchSize { + tb.Helper() + name := os.Getenv("FLEET_BENCH_SIZE") + if name == "" { + name = "smoke" + } + sz, ok := benchSizes[name] + if !ok { + tb.Fatalf("unknown FLEET_BENCH_SIZE=%q (want smoke|realistic|large)", name) + } + return sz +} + +// seedVulnPerfData populates a Fleet schema with enough data to make the +// query planner exercise the indexed paths. It bypasses Fleet's ingestion +// code in favor of direct multi-row INSERTs so seeding stays under a minute +// even at "large" size. +func seedVulnPerfData(tb testing.TB, ds *Datastore, sz benchSize) { + tb.Helper() + ctx := context.Background() + w := ds.writer(ctx) + + // FK checks slow batch insert significantly; safe to disable for seeding. + if _, err := w.ExecContext(ctx, "SET FOREIGN_KEY_CHECKS=0"); err != nil { + tb.Fatalf("disable FK: %v", err) + } + defer func() { + if _, err := w.ExecContext(ctx, "SET FOREIGN_KEY_CHECKS=1"); err != nil { + tb.Logf("re-enable FK: %v", err) + } + }() + + r := rand.New(rand.NewPCG(1, 2)) // nolint:gosec,G404 // benchmark seed, not security-sensitive + start := time.Now() + + // software_titles — one per software for simplicity + batchInsert(tb, ds, + "INSERT INTO software_titles (id, name, source) VALUES ", + sz.numSoftware, 3, func(i int) []any { + return []any{i + 1, fmt.Sprintf("title-%d", i+1), "programs"} + }) + + // software — title_id maps 1:1. checksum is a unique binary(16) so we + // derive it from the row index deterministically. + batchInsert(tb, ds, + "INSERT INTO software (id, name, version, source, title_id, checksum) VALUES ", + sz.numSoftware, 6, func(i int) []any { + checksum := make([]byte, 16) + for j := range 8 { + checksum[j] = byte((i + 1) >> (8 * j) & 0xff) + } + return []any{ + i + 1, + fmt.Sprintf("software-%d", i+1), + fmt.Sprintf("1.%d.%d", i%10, i%100), + "programs", + i + 1, + checksum, + } + }) + + // cve_meta — distribute cvss_score and a CISA known-exploit bit + batchInsert(tb, ds, + "INSERT INTO cve_meta (cve, cvss_score, epss_probability, cisa_known_exploit, published, description) VALUES ", + sz.numCVEs, 6, func(i int) []any { + return []any{ + fmt.Sprintf("CVE-2024-%07d", i), + r.Float64() * 10, + r.Float64(), + r.IntN(20) == 0, // ~5% are CISA known exploits + time.Now().Add(-time.Duration(r.IntN(365*24)) * time.Hour), + fmt.Sprintf("Description for CVE-2024-%07d", i), + } + }) + + // software_cve — every software gets [0, cvesPerSW] random CVEs + type swcve struct { + swID int + cve string + } + pairs := make([]swcve, 0, sz.numSoftware*sz.cvesPerSW) + seen := make(map[swcve]struct{}) + for i := 0; i < sz.numSoftware; i++ { + n := r.IntN(sz.cvesPerSW + 1) + for range n { + p := swcve{swID: i + 1, cve: fmt.Sprintf("CVE-2024-%07d", r.IntN(sz.numCVEs))} + if _, ok := seen[p]; ok { + continue + } + seen[p] = struct{}{} + pairs = append(pairs, p) + } + } + batchInsert(tb, ds, + "INSERT INTO software_cve (software_id, cve, source) VALUES ", + len(pairs), 3, func(i int) []any { + return []any{pairs[i].swID, pairs[i].cve, 1} + }) + + // operating_system_vulnerabilities — a fraction of CVEs also appear as + // OS vulns so the OR-EXISTS branch in ListVulnerabilities is actually + // exercised (in prod about half of vuln catalog entries are OS-side). + // Unique key is (operating_system_id, cve), so we spread CVEs across a + // small pool of OS ids. + if sz.osVulnFraction > 0 && sz.numOperatingSystems > 0 { + osPairs := make([]struct { + osID int + cve string + }, 0, int(float64(sz.numCVEs)*sz.osVulnFraction)) + osSeen := make(map[[2]int]struct{}) + want := int(float64(sz.numCVEs) * sz.osVulnFraction) + for len(osPairs) < want { + cveIdx := r.IntN(sz.numCVEs) + osID := 1 + r.IntN(sz.numOperatingSystems) + key := [2]int{osID, cveIdx} + if _, dup := osSeen[key]; dup { + continue + } + osSeen[key] = struct{}{} + osPairs = append(osPairs, struct { + osID int + cve string + }{osID, fmt.Sprintf("CVE-2024-%07d", cveIdx)}) + } + batchInsert(tb, ds, + "INSERT INTO operating_system_vulnerabilities (operating_system_id, cve, source) VALUES ", + len(osPairs), 3, func(i int) []any { + return []any{osPairs[i].osID, osPairs[i].cve, 1} + }) + } + + // software_host_counts — global row per software, plus a team-scoped row + // for a fraction of software so team_id-filtered benchmarks have data. + batchInsert(tb, ds, + "INSERT INTO software_host_counts (software_id, hosts_count, team_id, global_stats) VALUES ", + sz.numSoftware, 4, func(i int) []any { + return []any{i + 1, 1 + r.IntN(500), 0, 1} + }) + numTeamSW := int(float64(sz.numSoftware) * sz.teamSoftwareFraction) + if numTeamSW > 0 { + batchInsert(tb, ds, + "INSERT INTO software_host_counts (software_id, hosts_count, team_id, global_stats) VALUES ", + numTeamSW, 4, func(i int) []any { + return []any{i + 1, 1 + r.IntN(200), benchTeamID, 0} + }) + } + + // vulnerability_host_counts — global row per CVE, plus team-scoped rows + // for a fraction of CVEs. + batchInsert(tb, ds, + "INSERT INTO vulnerability_host_counts (cve, team_id, host_count, global_stats) VALUES ", + sz.numCVEs, 4, func(i int) []any { + return []any{fmt.Sprintf("CVE-2024-%07d", i), 0, 1 + r.IntN(500), 1} + }) + numTeamVuln := int(float64(sz.numCVEs) * sz.teamVulnFraction) + if numTeamVuln > 0 { + batchInsert(tb, ds, + "INSERT INTO vulnerability_host_counts (cve, team_id, host_count, global_stats) VALUES ", + numTeamVuln, 4, func(i int) []any { + return []any{fmt.Sprintf("CVE-2024-%07d", i), benchTeamID, 1 + r.IntN(200), 0} + }) + } + + tb.Logf("seeded %d software (%d team-scoped), %d cves (%d team-scoped, %d also OS-vuln), %d software_cve rows in %s", + sz.numSoftware, numTeamSW, sz.numCVEs, numTeamVuln, + int(float64(sz.numCVEs)*sz.osVulnFraction), len(pairs), + time.Since(start).Round(time.Millisecond)) +} + +// batchInsert issues multi-row INSERTs of `cols` placeholders per row in +// chunks small enough to stay under MySQL's default max_allowed_packet. +func batchInsert( + tb testing.TB, + ds *Datastore, + prefix string, + n, cols int, + row func(i int) []any, +) { + tb.Helper() + ctx := context.Background() + const rowsPerStmt = 500 + placeholder := "(" + strings.Repeat("?,", cols-1) + "?)" + for start := 0; start < n; start += rowsPerStmt { + end := min(start+rowsPerStmt, n) + parts := make([]string, 0, end-start) + args := make([]any, 0, (end-start)*cols) + for i := start; i < end; i++ { + parts = append(parts, placeholder) + args = append(args, row(i)...) + } + stmt := prefix + strings.Join(parts, ",") + if _, err := ds.writer(ctx).ExecContext(ctx, stmt, args...); err != nil { + tb.Fatalf("batch insert (rows %d-%d): %v", start, end, err) + } + } +} + +func BenchmarkListVulnerabilities(b *testing.B) { + ds := CreateMySQLDS(b) + sz := pickBenchSize(b) + seedVulnPerfData(b, ds, sz) + + cases := []struct { + name string + opt fleet.VulnListOptions + }{ + { + name: "cvss_score_page0_per20", + opt: fleet.VulnListOptions{ + IsEE: true, + ListOptions: fleet.ListOptions{ + OrderKey: "cvss_score", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "cvss_score_page50_per20", + opt: fleet.VulnListOptions{ + IsEE: true, + ListOptions: fleet.ListOptions{ + OrderKey: "cvss_score", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + Page: 50, + IncludeMetadata: true, + }, + }, + }, + { + name: "exploit_filter_page0", + opt: fleet.VulnListOptions{ + IsEE: true, + KnownExploit: true, + ListOptions: fleet.ListOptions{ + OrderKey: "cvss_score", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "created_at_page0_legacy", + opt: fleet.VulnListOptions{ + IsEE: true, + ListOptions: fleet.ListOptions{ + OrderKey: "created_at", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "team_cvss_score_page0", + opt: fleet.VulnListOptions{ + IsEE: true, + TeamID: new(benchTeamID), + ListOptions: fleet.ListOptions{ + OrderKey: "cvss_score", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "team_exploit_page0", + opt: fleet.VulnListOptions{ + IsEE: true, + TeamID: new(benchTeamID), + KnownExploit: true, + ListOptions: fleet.ListOptions{ + OrderKey: "cvss_score", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + } + + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := ds.ListVulnerabilities(context.Background(), c.opt) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkCountVulnerabilities(b *testing.B) { + ds := CreateMySQLDS(b) + sz := pickBenchSize(b) + seedVulnPerfData(b, ds, sz) + + cases := []struct { + name string + opt fleet.VulnListOptions + }{ + {"global_no_filter", fleet.VulnListOptions{IsEE: true}}, + {"global_exploit", fleet.VulnListOptions{IsEE: true, KnownExploit: true}}, + {"match_query", fleet.VulnListOptions{ + IsEE: true, + ListOptions: fleet.ListOptions{MatchQuery: "CVE-2024-0001"}, + }}, + {"team_no_filter", fleet.VulnListOptions{IsEE: true, TeamID: new(benchTeamID)}}, + {"team_exploit", fleet.VulnListOptions{IsEE: true, TeamID: new(benchTeamID), KnownExploit: true}}, + } + + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ds.CountVulnerabilities(context.Background(), c.opt) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkListSoftwareVersions(b *testing.B) { + ds := CreateMySQLDS(b) + sz := pickBenchSize(b) + seedVulnPerfData(b, ds, sz) + + cases := []struct { + name string + opt fleet.SoftwareListOptions + }{ + { + name: "no_filter_page0", + opt: fleet.SoftwareListOptions{ + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "vulnerable_page0", + opt: fleet.SoftwareListOptions{ + VulnerableOnly: true, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "vulnerable_exploit_cvss_page0", + opt: fleet.SoftwareListOptions{ + VulnerableOnly: true, + KnownExploit: true, + MinimumCVSS: 3, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "vulnerable_narrow_query_page0", + opt: fleet.SoftwareListOptions{ + VulnerableOnly: true, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + MatchQuery: "software-1", + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + // Broad match (1-char LIKE) — matches almost every software row; + // useful for spotting plan flips driven by predicate selectivity. + name: "vulnerable_broad_query_page0", + opt: fleet.SoftwareListOptions{ + VulnerableOnly: true, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + MatchQuery: "s", + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + name: "team_vulnerable_page0", + opt: fleet.SoftwareListOptions{ + TeamID: new(benchTeamID), + VulnerableOnly: true, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + { + // Matches the customer's full Q1 pattern: team + exploit + cvss + // floor + vulnerable + search. + name: "team_full_customer_pattern_page0", + opt: fleet.SoftwareListOptions{ + TeamID: new(benchTeamID), + VulnerableOnly: true, + KnownExploit: true, + MinimumCVSS: 3, + WithHostCounts: true, + IncludeCVEScores: true, + ListOptions: fleet.ListOptions{ + MatchQuery: "s", + OrderKey: "hosts_count", + OrderDirection: fleet.OrderDescending, + PerPage: 20, + IncludeMetadata: true, + }, + }, + }, + } + + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, _, err := ds.ListSoftware(context.Background(), c.opt) + if err != nil { + b.Fatal(err) + } + } + }) + } +} + +func BenchmarkCountSoftware(b *testing.B) { + ds := CreateMySQLDS(b) + sz := pickBenchSize(b) + seedVulnPerfData(b, ds, sz) + + cases := []struct { + name string + opt fleet.SoftwareListOptions + }{ + {"no_filter", fleet.SoftwareListOptions{}}, + {"vulnerable", fleet.SoftwareListOptions{VulnerableOnly: true}}, + {"vulnerable_exploit", fleet.SoftwareListOptions{ + VulnerableOnly: true, KnownExploit: true, IncludeCVEScores: true, + }}, + {"vulnerable_cvss", fleet.SoftwareListOptions{ + VulnerableOnly: true, MinimumCVSS: 3, IncludeCVEScores: true, + }}, + {"team_vulnerable", fleet.SoftwareListOptions{ + TeamID: new(benchTeamID), VulnerableOnly: true, + }}, + {"team_vulnerable_exploit", fleet.SoftwareListOptions{ + TeamID: new(benchTeamID), VulnerableOnly: true, + KnownExploit: true, IncludeCVEScores: true, + }}, + } + + for _, c := range cases { + b.Run(c.name, func(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ds.CountSoftware(context.Background(), c.opt) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/server/service/hosts.go b/server/service/hosts.go index 940464b4f9..67b53c8baf 100644 --- a/server/service/hosts.go +++ b/server/service/hosts.go @@ -1897,6 +1897,13 @@ func (svc *Service) getHostDetails(ctx context.Context, host *fleet.Host, opts f if status.Status != nil && *status.Status == fleet.DiskEncryptionVerified { host.MDM.EncryptionKeyAvailable = true } + } else { + // Linux hosts only have OS settings via the disk-encryption (LUKS) + // path above. When disk encryption isn't enabled, clear the stray + // empty struct initialized at the top of this method (which runs + // whenever any platform's MDM is enabled & configured) so the API + // reports no OS settings instead of an empty object. + host.MDM.OSSettings = nil } } diff --git a/server/service/hosts_test.go b/server/service/hosts_test.go index 1729aff30e..8e78aa9f82 100644 --- a/server/service/hosts_test.go +++ b/server/service/hosts_test.go @@ -666,12 +666,12 @@ func TestHostDetailsOSSettings(t *testing.T) { // service should call this function to check whether disk encryption is enabled for a Linux host require.True(t, ds.GetConfigEnableDiskEncryptionFuncInvoked) - // `hostDetail.MDM.OSSettings` and `hostDetail.MDM.OSSettings.DiskEncryption` will actually not - // be `nil` here due to the way those fields are initialized by `svc.ds.Host`, so we can't - // expect them to be `nil` in these tests. However, since the relevant struct tags are set to - // `omitempty`, the resulting API response WILL omit these fields/subfields when empty, - // which is confirmed at the integration layer. - require.Nil(t, hostDetail.MDM.OSSettings.DiskEncryption.Status) + // Linux hosts only get OS settings via the disk-encryption (LUKS) path. When disk + // encryption isn't enabled, OSSettings is nil — even though some other platform's MDM is + // EnabledAndConfigured (which initializes an empty struct earlier in getHostDetails). A + // non-nil pointer to an empty struct is NOT omitted by `omitempty`, so the service clears + // it so the API reports no OS settings instead of an empty `os_settings: {}` object. + require.Nil(t, hostDetail.MDM.OSSettings) case "darwin": require.True(t, ds.GetHostMDMAppleProfilesFuncInvoked)