From 91edc6a2aaf5550e13caedc3b33a9f54bf36943b Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:39:30 -0600 Subject: [PATCH 1/7] Fixed dead rows accumulating in software host counts tables --- changes/35805-zero-host-counts | 1 + ...00000_CleanupSoftwareHostCountsZeroRows.go | 35 +++++++++ server/datastore/mysql/schema.sql | 10 ++- server/datastore/mysql/software.go | 75 +++++++++---------- server/datastore/mysql/software_test.go | 6 +- server/datastore/mysql/software_titles.go | 64 ++++++++-------- 6 files changed, 111 insertions(+), 80 deletions(-) create mode 100644 changes/35805-zero-host-counts create mode 100644 server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go diff --git a/changes/35805-zero-host-counts b/changes/35805-zero-host-counts new file mode 100644 index 00000000000..eeea04ba6cb --- /dev/null +++ b/changes/35805-zero-host-counts @@ -0,0 +1 @@ +* Fixed dead rows accumulating in software host counts tables by using an atomic table swap instead of in-place updates during the sync process. diff --git a/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go new file mode 100644 index 00000000000..e17b7b046eb --- /dev/null +++ b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go @@ -0,0 +1,35 @@ +package tables + +import ( + "database/sql" +) + +func init() { + MigrationClient.AddMigration(Up_20260223000000, Down_20260223000000) +} + +func Up_20260223000000(tx *sql.Tx) error { + // Delete any accumulated zero-count rows from software_host_counts and software_titles_host_counts. + // After this migration, the sync process uses an atomic swap table pattern that never produces zero-count rows. + // Add CHECK constraints to prevent zero-count rows from being inserted in the future. + + if _, err := tx.Exec(`DELETE FROM software_host_counts WHERE hosts_count = 0`); err != nil { + return err + } + if _, err := tx.Exec(`ALTER TABLE software_host_counts ADD CONSTRAINT ck_software_host_counts_positive CHECK (hosts_count > 0)`); err != nil { + return err + } + + if _, err := tx.Exec(`DELETE FROM software_titles_host_counts WHERE hosts_count = 0`); err != nil { + return err + } + if _, err := tx.Exec(`ALTER TABLE software_titles_host_counts ADD CONSTRAINT ck_software_titles_host_counts_positive CHECK (hosts_count > 0)`); err != nil { + return err + } + + return nil +} + +func Down_20260223000000(tx *sql.Tx) error { + return nil +} diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index 3e1f1683792..c70e5fc9f2c 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -1784,9 +1784,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=484 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +) /*!50100 TABLESPACE `innodb_system` */ ENGINE=InnoDB AUTO_INCREMENT=485 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,20260217181748,1,'2020-01-01 01:01:01'),(482,20260217200906,1,'2020-01-01 01:01:01'),(483,20260218165545,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,20260217181748,1,'2020-01-01 01:01:01'),(482,20260217200906,1,'2020-01-01 01:01:01'),(483,20260218165545,1,'2020-01-01 01:01:01'),(484,20260223000000,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` ( @@ -2630,7 +2630,8 @@ CREATE TABLE `software_host_counts` ( `global_stats` tinyint unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`software_id`,`team_id`,`global_stats`), KEY `idx_software_host_counts_updated_at_software_id` (`updated_at`,`software_id`), - KEY `idx_software_host_counts_team_global_hosts_desc` (`team_id`,`global_stats`,`hosts_count` DESC,`software_id`) + KEY `idx_software_host_counts_team_global_hosts_desc` (`team_id`,`global_stats`,`hosts_count` DESC,`software_id`), + CONSTRAINT `ck_software_host_counts_positive` CHECK ((`hosts_count` > 0)) ) /*!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 */; @@ -2788,7 +2789,8 @@ CREATE TABLE `software_titles_host_counts` ( `global_stats` tinyint unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`software_title_id`,`team_id`,`global_stats`), KEY `idx_software_titles_host_counts_team_counts_title` (`team_id`,`hosts_count`,`software_title_id`), - KEY `idx_software_titles_host_counts_updated_at_software_title_id` (`updated_at`,`software_title_id`) + KEY `idx_software_titles_host_counts_updated_at_software_title_id` (`updated_at`,`software_title_id`), + CONSTRAINT `ck_software_titles_host_counts_positive` CHECK ((`hosts_count` > 0)) ) /*!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 89dcc5e837d..8a16ebf903e 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -2620,9 +2620,8 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, teamID *uint, in // on removed hosts, software uninstalled on hosts, etc.) func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time) error { const ( - resetStmt = ` - UPDATE software_host_counts - SET hosts_count = 0, updated_at = ?` + swapTable = "software_host_counts_swap" + swapTableCreate = "CREATE TABLE IF NOT EXISTS " + swapTable + " LIKE software_host_counts" // team_id is added to the select list to have the same structure as // the teamCountsStmt, making it easier to use a common implementation @@ -2649,7 +2648,7 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time) GROUP BY hs.software_id` insertStmt = ` - INSERT INTO software_host_counts + INSERT INTO ` + swapTable + ` (software_id, hosts_count, team_id, global_stats, updated_at) VALUES %s @@ -2668,33 +2667,18 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time) LEFT JOIN software_host_counts shc ON s.id = shc.software_id WHERE - (shc.software_id IS NULL OR - (shc.team_id = 0 AND shc.hosts_count = 0)) AND - NOT EXISTS (SELECT 1 FROM host_software hsw WHERE hsw.software_id = s.id) + shc.software_id IS NULL AND + NOT EXISTS (SELECT 1 FROM host_software hsw WHERE hsw.software_id = s.id) ` - - cleanupOrphanedStmt = ` - DELETE shc - FROM - software_host_counts shc - LEFT JOIN software s ON s.id = shc.software_id - WHERE - s.id IS NULL - ` - - cleanupTeamStmt = ` - DELETE shc - FROM software_host_counts shc - LEFT JOIN teams t - ON t.id = shc.team_id - WHERE - shc.team_id > 0 AND - t.id IS NULL` ) - // first, reset all counts to 0 - if _, err := ds.writer(ctx).ExecContext(ctx, resetStmt, updatedAt); err != nil { - return ctxerr.Wrap(ctx, err, "reset all software_host_counts to 0") + // Create a fresh swap table to populate with new counts. If a previous run left a partial swap table, drop it first. + w := ds.writer(ctx) + if _, err := w.ExecContext(ctx, "DROP TABLE IF EXISTS "+swapTable); err != nil { + return ctxerr.Wrap(ctx, err, "drop existing swap table") + } + if _, err := w.ExecContext(ctx, swapTableCreate); err != nil { + return ctxerr.Wrap(ctx, err, "create swap table") } db := ds.reader(ctx) @@ -2772,19 +2756,32 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time) } - // remove any unused software (global counts = 0) - if _, err := ds.writer(ctx).ExecContext(ctx, cleanupSoftwareStmt); err != nil { - return ctxerr.Wrap(ctx, err, "delete unused software") - } - - // remove any software count row for software that don't exist anymore - if _, err := ds.writer(ctx).ExecContext(ctx, cleanupOrphanedStmt); err != nil { - return ctxerr.Wrap(ctx, err, "delete software_host_counts for non-existing software") + // Atomic table swap: rename the swap table to the real table, drop the old one. + // Also drop any leftover old table from a previous failed swap. + if err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS software_host_counts_old") + if err != nil { + return ctxerr.Wrap(ctx, err, "drop leftover old table") + } + _, err = tx.ExecContext(ctx, ` + RENAME TABLE + software_host_counts TO software_host_counts_old, + `+swapTable+` TO software_host_counts`) + if err != nil { + return ctxerr.Wrap(ctx, err, "atomic table swap") + } + _, err = tx.ExecContext(ctx, "DROP TABLE IF EXISTS software_host_counts_old") + if err != nil { + return ctxerr.Wrap(ctx, err, "drop old table after swap") + } + return nil + }); err != nil { + return err } - // remove any software count row for teams that don't exist anymore - if _, err := ds.writer(ctx).ExecContext(ctx, cleanupTeamStmt); err != nil { - return ctxerr.Wrap(ctx, err, "delete software_host_counts for non-existing teams") + // Remove any unused software (those not in host_software). + if _, err := ds.writer(ctx).ExecContext(ctx, cleanupSoftwareStmt); err != nil { + return ctxerr.Wrap(ctx, err, "delete unused software") } return nil } diff --git a/server/datastore/mysql/software_test.go b/server/datastore/mysql/software_test.go index d51f0060940..21392d8fa8b 100644 --- a/server/datastore/mysql/software_test.go +++ b/server/datastore/mysql/software_test.go @@ -1300,7 +1300,7 @@ func testSoftwareSyncHostsSoftware(t *testing.T, ds *Datastore) { cmpNameVersionCount(want, team1Counts) // composite pk (software_id, team_id, global_stats), so we expect more rows - checkTableTotalCount(11) + checkTableTotalCount(10) soft1ByID, err := ds.SoftwareByID(context.Background(), host1.HostSoftware.Software[0].ID, &team1.ID, false, nil) require.NoError(t, err) @@ -1346,7 +1346,7 @@ func testSoftwareSyncHostsSoftware(t *testing.T, ds *Datastore) { } cmpNameVersionCount(want, team2Counts) - checkTableTotalCount(9) + checkTableTotalCount(8) // update host4 (team2), remove all software and delete team software4 = []fleet.Software{} @@ -1384,7 +1384,7 @@ func testSoftwareSyncHostsSoftware(t *testing.T, ds *Datastore) { cmpNameVersionCount(want, team1Counts) listSoftwareCheckCount(t, ds, 0, 0, team2Opts, false) - checkTableTotalCount(8) + checkTableTotalCount(7) } // softwareChecksumComputedColumn computes the checksum for a software entry diff --git a/server/datastore/mysql/software_titles.go b/server/datastore/mysql/software_titles.go index b291c47f8e9..2fc0fad9276 100644 --- a/server/datastore/mysql/software_titles.go +++ b/server/datastore/mysql/software_titles.go @@ -724,9 +724,8 @@ GROUP BY s.id` // table. func (ds *Datastore) SyncHostsSoftwareTitles(ctx context.Context, updatedAt time.Time) error { const ( - resetStmt = ` - UPDATE software_titles_host_counts - SET hosts_count = 0, updated_at = ?` + swapTable = "software_titles_host_counts_swap" + swapTableCreate = "CREATE TABLE IF NOT EXISTS " + swapTable + " LIKE software_titles_host_counts" globalCountsStmt = ` SELECT @@ -766,7 +765,7 @@ func (ds *Datastore) SyncHostsSoftwareTitles(ctx context.Context, updatedAt time GROUP BY st.id` insertStmt = ` - INSERT INTO software_titles_host_counts + INSERT INTO ` + swapTable + ` (software_title_id, hosts_count, team_id, global_stats, updated_at) VALUES %s @@ -775,30 +774,18 @@ func (ds *Datastore) SyncHostsSoftwareTitles(ctx context.Context, updatedAt time updated_at = VALUES(updated_at)` valuesPart = `(?, ?, ?, ?, ?),` - - cleanupOrphanedStmt = ` - DELETE sthc - FROM - software_titles_host_counts sthc - LEFT JOIN software_titles st ON st.id = sthc.software_title_id - WHERE - st.id IS NULL` - - cleanupTeamStmt = ` - DELETE sthc - FROM software_titles_host_counts sthc - LEFT JOIN teams t ON t.id = sthc.team_id - WHERE - sthc.team_id > 0 AND - t.id IS NULL` ) - // first, reset all counts to 0 - if _, err := ds.writer(ctx).ExecContext(ctx, resetStmt, updatedAt); err != nil { - return ctxerr.Wrap(ctx, err, "reset all software_titles_host_counts to 0") + // Create a fresh swap table to populate with new counts. If a previous run left a partial swap table, drop it first. + w := ds.writer(ctx) + if _, err := w.ExecContext(ctx, "DROP TABLE IF EXISTS "+swapTable); err != nil { + return ctxerr.Wrap(ctx, err, "drop existing swap table") + } + if _, err := w.ExecContext(ctx, swapTableCreate); err != nil { + return ctxerr.Wrap(ctx, err, "create swap table") } - // next get a cursor for the global and team counts for each software + // Get a cursor for the global and team counts for each software title. stmtLabel := []string{"global", "team", "no_team"} for i, countStmt := range []string{globalCountsStmt, teamCountsStmt, noTeamCountsStmt} { rows, err := ds.reader(ctx).QueryContext(ctx, countStmt) @@ -850,16 +837,25 @@ func (ds *Datastore) SyncHostsSoftwareTitles(ctx context.Context, updatedAt time rows.Close() } - // remove any software count row for software that don't exist anymore - if _, err := ds.writer(ctx).ExecContext(ctx, cleanupOrphanedStmt); err != nil { - return ctxerr.Wrap(ctx, err, "delete software_titles_host_counts for non-existing software") - } - - // remove any software count row for teams that don't exist anymore - if _, err := ds.writer(ctx).ExecContext(ctx, cleanupTeamStmt); err != nil { - return ctxerr.Wrap(ctx, err, "delete software_titles_host_counts for non-existing teams") - } - return nil + // Atomic table swap: rename the swap table to the real table, drop the old one. + return ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error { + _, err := tx.ExecContext(ctx, "DROP TABLE IF EXISTS software_titles_host_counts_old") + if err != nil { + return ctxerr.Wrap(ctx, err, "drop leftover old table") + } + _, err = tx.ExecContext(ctx, ` + RENAME TABLE + software_titles_host_counts TO software_titles_host_counts_old, + `+swapTable+` TO software_titles_host_counts`) + if err != nil { + return ctxerr.Wrap(ctx, err, "atomic table swap") + } + _, err = tx.ExecContext(ctx, "DROP TABLE IF EXISTS software_titles_host_counts_old") + if err != nil { + return ctxerr.Wrap(ctx, err, "drop old table after swap") + } + return nil + }) } func (ds *Datastore) UpdateSoftwareTitleAutoUpdateConfig(ctx context.Context, titleID uint, teamID uint, config fleet.SoftwareAutoUpdateConfig) error { From 7f42b14b620bd81dbd3554e942ec3993687fa96f Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 08:49:07 -0600 Subject: [PATCH 2/7] Clean up unnecessary host_count checks. --- server/datastore/mysql/software.go | 21 ++++++--------------- server/datastore/mysql/software_titles.go | 7 +++---- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index 8a16ebf903e..bc6521a428b 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -1678,28 +1678,21 @@ func buildOptimizedListSoftwareSQL(opts fleet.SoftwareListOptions) (string, []in // This allows MySQL to read from the index without accessing the table, // which is critical for performance. // IMPORTANT: Update this query if modifying idx_software_host_counts_team_global_hosts_desc index. - // PERFORMANCE ISSUE: The hosts_count > 0 filter causes ASC queries to read the entire index - // instead of stopping after LIMIT rows like DESC queries do. While both ASC and DESC - // use the index, DESC can stop early but ASC with a range condition must read all matching rows. - // RECOMMENDED SOLUTION: Eliminate zero-count rows from this table entirely. - // This would allow removing the hosts_count > 0 filter, making ASC queries as fast as DESC - // without requiring a second index. Related issue: https://github.com/fleetdm/fleet/issues/35805 innerSQL := ` SELECT shc.software_id, shc.hosts_count FROM software_host_counts shc - WHERE shc.hosts_count > 0 ` // Apply team filtering with global_stats switch { case opts.TeamID == nil: - innerSQL += " AND shc.team_id = 0 AND shc.global_stats = 1" + innerSQL += " WHERE shc.team_id = 0 AND shc.global_stats = 1" case *opts.TeamID == 0: - innerSQL += " AND shc.team_id = 0 AND shc.global_stats = 0" + innerSQL += " WHERE shc.team_id = 0 AND shc.global_stats = 0" default: - innerSQL += " AND shc.team_id = ? AND shc.global_stats = 0" + innerSQL += " WHERE shc.team_id = ? AND shc.global_stats = 0" args = append(args, *opts.TeamID) } @@ -2154,8 +2147,6 @@ func countSoftwareDB( countSQL += ` INNER JOIN cve_meta c ON c.cve = scv.cve` } - countSQL += ` WHERE shc.hosts_count > 0` - var args []interface{} var whereClauses []string // Apply team filtering with global_stats @@ -2191,8 +2182,8 @@ func countSoftwareDB( } // Add all WHERE clauses - for _, clause := range whereClauses { - countSQL += " AND " + clause + if len(whereClauses) > 0 { + countSQL += " WHERE " + strings.Join(whereClauses, " AND ") } var count int @@ -2545,7 +2536,7 @@ func (ds *Datastore) SoftwareByID(ctx context.Context, id uint, teamID *uint, in // However, it is possible that the software was deleted from all hosts after the last host count update. q = q.Where( goqu.L( - "EXISTS (SELECT 1 FROM software_host_counts WHERE software_id = ? AND team_id = ? AND hosts_count > 0 AND global_stats = 0)", id, *teamID, + "EXISTS (SELECT 1 FROM software_host_counts WHERE software_id = ? AND team_id = ? AND global_stats = 0)", id, *teamID, ), ) } diff --git a/server/datastore/mysql/software_titles.go b/server/datastore/mysql/software_titles.go index 2fc0fad9276..6d694fc22c9 100644 --- a/server/datastore/mysql/software_titles.go +++ b/server/datastore/mysql/software_titles.go @@ -64,13 +64,13 @@ SELECT vap.icon_url AS icon_url FROM software_titles st %s -LEFT JOIN software_titles_host_counts sthc ON sthc.software_title_id = st.id AND sthc.hosts_count > 0 AND (%s) +LEFT JOIN software_titles_host_counts sthc ON sthc.software_title_id = st.id AND (%s) LEFT JOIN software_installers si ON si.title_id = st.id AND %s LEFT JOIN vpp_apps vap ON vap.title_id = st.id LEFT JOIN vpp_apps_teams vat ON vat.adam_id = vap.adam_id AND vat.platform = vap.platform AND %s LEFT JOIN in_house_apps iha ON iha.title_id = st.id AND %s WHERE st.id = ? AND - (sthc.hosts_count > 0 OR vat.adam_id IS NOT NULL OR si.id IS NOT NULL OR iha.title_id IS NOT NULL) + (sthc.software_title_id IS NOT NULL OR vat.adam_id IS NOT NULL OR si.id IS NOT NULL OR iha.title_id IS NOT NULL) GROUP BY st.id, st.name, @@ -561,7 +561,7 @@ WHERE {{with $defFilter := yesNo (hasTeamID .) "(si.id IS NOT NULL OR vat.adam_id IS NOT NULL OR iha.id IS NOT NULL)" "FALSE"}} -- add software installed for hosts if we're not filtering for "available for install" only {{if not $.AvailableForInstall}} - {{$defFilter = $defFilter | printf " ( %s OR sthc.hosts_count > 0 ) "}} + {{$defFilter = $defFilter | printf " ( %s OR sthc.software_title_id IS NOT NULL ) "}} {{ end }} {{if and $.SelfServiceOnly (hasTeamID $)}} {{$defFilter = $defFilter | printf "%s AND ( si.self_service = 1 OR vat.self_service = 1 OR iha.self_service = 1 ) "}} @@ -692,7 +692,6 @@ LEFT JOIN software_host_counts shc ON shc.software_id = s.id AND %s LEFT JOIN software_cve scve ON shc.software_id = scve.software_id WHERE s.title_id IN (?) AND %s -AND shc.hosts_count > 0 GROUP BY s.id` extraSelect := "" From bacb965e46aa16a2f3b806a2fb8ea3a38e5cece9 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:29:54 -0600 Subject: [PATCH 3/7] Fix test --- .../select_software_titles_sql_fixture.gz | Bin 32987 -> 33865 bytes tools/qacheck/go.mod | 2 -- 2 files changed, 2 deletions(-) diff --git a/server/datastore/mysql/testdata/select_software_titles_sql_fixture.gz b/server/datastore/mysql/testdata/select_software_titles_sql_fixture.gz index d51e56e7e2e62e1a409ea9ec7a8151d93abf0a0a..719b892d23fe635fdf177e488fcc7bcb4b105f38 100644 GIT binary patch literal 33865 zcmce-Wl&sSo41R*yE`Po-Gf_j2o~HO8h09Z2#^FP5F7%8U;%==1q<%(PUFz(TMivH6cL(}-dnKk09?L-;2d(@YbDf(A~+!*lOTa2|(w0 zcbMY|d_4#d_qpHNK5h_y1Z_72HG;1{FFyNSEWT3Xf1Z^$3G{#1ULMmA?2hATd$o^9OM;|3J_BJ7Vx*>+{1lvB^`r$>YPZ$;y+bF%aDA{d`>lcy4@3 zy8{yk-6fW<<~}_Kfr2WYPe;ZYZ#NPf4mkoIO8`~Q?GI05(6b*mm@ET9+s8Ynd+oVm zcemvLNc$GWgC3>@R^Q$oe=e`xwHf=N_Txv8=hMVw`JstXcAH@H!?TGI@cv-_Aa_)( zU97oLxV@$E?)GAOnfU2^GA=PEcR4%A!^<`B>HKc~Ag+YNL`*!$%P;UI@X%y`GVS^D z;Ig1L%O;Kb>)Sxr`@2Do9HSgD@n%0SuZy_y!&UKz=~IrXA63pdMnFGT@MV}u&f~?_ z<-yi*)!S=#vB0~D{i}I-fs$FZ)2H<<;%878w%F~}GvMm^Yrt72K=*m|x#gi(r+w*w z`1kL-#WCT#+v)AN&*j63RpKIno?h;OK=9+y!Pde3*4=#n?5pS9?YrJO@=Q>E zGvE5@KqnrUw?f1PF4gzfS;Otq})p`2%uV#YCEZKLqKBTY*Q$jB~-qi9XM} zZ~X(kKpRK*ms|63(_@VfGtIf+^}$-=@}S0tf)()Q-Sqy*E8?_VqowS?r;EGoyQ^jD zo2}Z_+*VJsa1)$1zc?$Qv*Tu&D77A)1Zc+y-D4%(>6b#t7qW-|SpsXf1o+-aSE*G| zKL)k{@7%7sZ=S?X+RiTvuDEVyZXDa{?$@awK&yeL1wq%dB^*IlkC*pH2lqRb?PCJ% zms#y&&8L139G4@B&u7GbH<-I)>>SSgL6v~E^^w_}oad*@m7qVMxU5wZpr7Zj`ybOm z6V0bShlwty5@&u%(B#hNtq1HuuA-r>C1XaM-Df{Nke# zq!+?X!X{rIcd<`7$`_~Zva+VO%d;9z&4M0eSD)aGzGg#AC$r5DpT+OdSHK-=;MlBZ zVcl0%Y9=dSbMNOXDsZpPO_n@x<6yS+VSn~8_o?C82-tYv3%oVi@0Bm72Cuiz-$y-P z=bk06ZtiKFNvbg4_Aa|#O$^>m5B6?f4fd9d<>nf;>4SIMpQlZJJPQN29#)?KH|{}Y z$DoIkvAVk=s)zfqI7dYDMFP;}gh59uPte1cAeV~v!1PnmYHFcxL6;uDONJFOzlO14 z-OCuT~Skx{G@i*IV|RSjZnmQ&w*(dp%yQ%%9J8L;lX;ikSDK z9;Fj8@;}?(YNVhvN0s7zj8v6}uP;oUs*y?6TlT|pNc9$fdLLw5v|;ovL;-J*mX4H= z_8Xm6c&xgsafBWYVGx_LglwtLYpuuK)kgWA}>9HTa2 zk-M~0@!K`(w#TzA?^8qig=})AK9ne=PKI=Z_i?s$J2S6EA9*?TLJA5zcv%fQ6!lP9 z%!-^RP*})i(V%Pb8r`zs^Y$M<|}lhiKWi77F}%ZYe`{d+6F3JWXB zTjV2Egs1$((?K$`Qkb+2PXWWf6l!#~f3nlNPc^p=P^c!zl_1eF7KYc!?4Fg%Nk@On zy>yQR%4uT~#L#Il#Ac`QiGB~E)7TP?4OobvCmtnPvHEVV)#iD0W2NP!n!-pPMIVv< z>Af4XEAK)R`CpzE;fx{8`OtR=9-*q^sKSKFa^pe_LBUYYy2LLRgj1}_N(|mmw(~4b z`oOJZtwcb89fKi_aiC~aQtF_^(V0$yZ(;t&x3h}BWQaCED|>B|dn<%0ThSEBSpv;BdYu}8v$}*|m=nJ$-o5RT z_Cdj3A0eMMIFR>2;fOk02soMj>+Z|(n>TgLSKUDzfNAf>%PPF=&8{wfAP>g4A$PK4 z6B}IRz@NLOODW{8B%_gsp1QzxJeq=-17QGHh_2sPsB0*OD%l5lf}R57D;AReI~F^u z7@Bpvuus_%bnmt>V*U(aOW?kSll>-GCGKMj3ndct*!~}wi@EyS+F#72k6PDt99`J! zM2TNBL=+}5>Pr_8{=za_pFtxpV@MIYf)%2Jdcy{|7{qw#`mu^$3VN*adPWoUc&9Xe z;bD1`of0f|jFqgo45E+fpYRnJVzOzIKis|8PzZLfkg>&KWWUYf66AVG_7?ev33v`+ z&U;6J+oH-wI;(BtUF_ABW8ZPrN6WD|J9UM5KjUdi=~o-ADH_JGxE6U#M6)b`y)p1?~k`kzU1h6A$<|24O(-UKYkiy1X_75=K!kAI`@(?=>@y1RRNq&j+5M@TfB!s;(@7 z%bVA*kg*{YKDz_olA3eok6jEKbf3|-+Ca!BBuMlXEW#H}%f%2W_nh#nlWgQpdNo&x z-p;aHH5h4vhD;`mTaup4aSCCahKxcs%vGavBl^4g>5p&Bg8agic@ zqy(L@``r+OavR>7A~~vh+HaD)(MS`fIGB+P;c~ol?ZlA6+-IcC{Lmsi?YtEuNo^NL z4`KdjhGd3Mj5VRixMZ3LPK61BXK94E9n`n*L2CeG*)tkA2x+gK+7Uk*{3i3sEwWX= z>sdX`N?4#^iDN>vL4%CELQM_Zx=elsYM&r>M9SiCaYQH;(S+4>Is`Oi)f-6*!GzTt zoI3HtghV=*vBozTtzCJ457lY&7`}w3q7YGqko1v!`Rrz0V5XB39663dCe`g5*aeRV zp&9w^E0lJ4QYv0K$F@e0drM2$`*^`W%WFpOC355LT^KtETW<+fZuY2_Sv26tDht@e zt{X7q+V7JW){#I8m}Rtlg+!|Mv|)F0g}-hIsLE*TfrY68uN&mdG@Uh9>@`n5F_|L7 z2ak}a7-WhcQ06#KRN8W)lgGpJm?HfNo^Kg!O!0(Jz?9|?$EN~e1c)xsEyZDuy+ zFxDg`#+QT|4gT6oxnbov?9m%^#MJx&Q~EUnF9qyd80t0n0PD+yV8D5ZCq_ zN1g|5bOIhvEp0uy$0daokd^*CAOB55K)jHD`gF(cxR7{yM%mWw4eA5yT%1E`I_hDO znqbQ_-1?zf{=Ra)Ei7-hQvzH{eZ|7H0I1uaHv1UlFd2vO>r3Y*)&nfm8q9%mD`gE% z2(Icjr9Ojg%jrlH05`0eJ?8qHkw1%&>VL)XNW%Kti3bWQ2Qx~BJSMkWwxMTTZ;Av& zot`bXxVBFNA>?*@Vn^I%5YTe!lu-OIA9W8jIhfqp((}4Lo%Dln@1RBuWw z(E{q52X39)Xb#5WhjxNU*jFnsx}^zz?X#|5Pf;xNKft|Jf$T%FO|~)Ie{H>8&N0m06Up7W z_tasrI%x$FqSjqV@R-Ru*Eh7`C_<%O=C~>5EH$vcgyn5_^p0W8(~4xLVTo$FS`w{= z8YZD5MZvK~BUaN8W$U7e{tZT~HkBlD1ns-T*}$ICYa_Z**@|4VWMyX44fi3+qvURV z4$w5Pt@HimqcF!IHfL(_UAf-5uH$9$qdbz2Yr#(SnvNkj6rQ#i1#OP$SgSkfPcn#~{oG5?|I!?2@HAgPt!dnx>?r1f&# zO6u`XpwN63B`Dp!ZewBby zr@|x&<dvChDB28P@t~0}&VfWn>BJ8ic2v|D9_8*+wbYZiD@m=6Xea(95 z-@K4~i2E{?@Y&7^^U%)WO@=%8@aPdn^fZ`I!_Ap%SGPnU*U%8?;Adl}(3(C4(LXB1@^9L(rA`B%gJsfm%Nmjus zA)h2Tbo0(o*0~*v6(Vd5W8Lo@g}F~kt<2vCNoyVbTsnTL{2Js>iIO9CPT-cl5}-Lg}q-p@FE2QrwiFhs0Mjy#N~&qQUF*4FY3 z>tLB8`7qK^@XhFyvx&Jx?O~zxr>CdjRotR?n!9miYAvvlcRS&%4LBRC`J=}Rl2X^N;OWOXzizB z$W%!nce?rgmhbCa@u?l1kpD>V$`}qLoVZL~GU9=+0ui2^CTD-Js zO9KFU2lbjxs^6^QA~&PTxV4L!ZF%(@$Tc}ya5H8v7J6VpnKc-{%cR`P(|@PH6H$k< zwtdV9SySQcvMhVq7o}|RKdJNFi@vQr@HCq);r$nLL^z~6@)8Wu2+1^V##HA%C=d#x zaWldZ=G-zf7}$vu5kDR&*Q`(KUJ}64rMowLM#jKpf9mA`Vhz#?S;pW@*!DV~ADDI~ zOk#TdhWCP1j@sv>EJnBaoy0yKy-$<~la4isY5luHs5KBf%rES|EX+?=yxhXG!g-}| zg&tl$5*Dj}U6{sj!H}v;QV<1seFt-g?MPY>Wf#8bKxMu4^IsgX-8bbH?aw8HTs3E` ztE64e3}zlJuOkVyV^S%0 zOasbQo_e55w2Sh`L9X3xWHmejD>S*m*W^j2`i>Hz3OJ~Aoxb&*@Te~&fva;pM1K`u6nHJR*aeAkVZkLw}m5HfJx@S9qN#ivdr%3591&@z0# z1+QrsbSL)uNBb`jet!$1hk5m!n3}Hp6D8lE-)_esRUV%SdXy(!XhSze5F{H^prMD{79I#fy{%Lv2xc*D8ejU{v0*oe*6#^ zY2i{a%)1~Q>{{nqS6;a_Ef; zkXSQTWKNVFf(wER(>fRS^b(L*De3YCfxiNzy^Hr%XD&$t^Ee=(b`o+c#R@ns`4ZYv zsrdat86Rpt)W%hI_sV$-&@zAD8nSlDnbaE#k?UOS>rsM|zGrs0F zuTR@Vjzo_9_(h~kzMLoGQPT6XY2sJHwhiPY8+~Iw7>Vm#qC}$>w41wfqSgPEIid>QgrdC_;ewSpP>UgId1s?a89sfz|JbW+xpv< zdGzu4tb{BhC$=B_zootRRAcPi3nIwT$Dtj`7T@F&jqE>5KX<36s)b4DKhw(!sY`*u zx5KwfGZ-08fYYEs(=umpf?YtuTF+n-HG{XkMQmRqe#{gI`2~a7zBbGID16`I`@kdE z0zc9?-#9-#vteH#;t_T7C7oYDzp)xdB^X6L=8lJ|+X9ja@mz1Y66HR1V;Ho$}Zw-dhkv90^c@M;JBU+BU9o%rmcR!uft_yxn{ZM zmm%?v_6es9M*HG6N4FG*?&0Ji5q=weYnJ%vYm=EmPG~Kck zjqXaz&e+#Z zDeAeMHLs#1%+!R>?23dMLYT&R0SgY*#1Jdlc-^hz?Ehq96*-jEc#S5T_qt$3j=-v# zSQfLOv}L`@_P@67dO;)a4d1snj&tbH#=)t8id%{NCGl;)!>UAmJ$8mXS{GooJPkR{ zI?is-Y`PV^;f*N%l77suuUNgHWEdGU3|rF5i6+0Js@GmG;ue_MLHjPOT3rq>(xsRQ~HD>(Hra0r#vY*j*kvfn%el+CIl6&T{ zPZjkb%=uNx66R}$W&Yi1Ek)(aW18XxtU0t2^W_zjO?f(^Poo4rV~*O+M3J-dAw+WLj zWUAeEpyJhEKV6|zR_n~6z;nQVFiP&(%uY%WX0Fb(oNyh*3Up^yo%X-RTnZ+4fp?Z$ zP3Rce5P%RUa{Zb}xm%?hzGR(US*Q2A-gJv=V=n+*mLgVqngqDXn;kjnve2^M&RTsjXCrWZjiqhzZ)u3y|8JVo|3A|#(AA!d z+?+gm5CKYg4*3toE-Drynrm)6&vb3wecTEDeBA1fL-Oyh(B<*|C zyVX7=_%<#h!n?ws_k=XEeAYc@Hs%A+IX^7w;KrG2$nLC4V3xx2ed7nfID^Td zL?m)@og+NyJbBV#*MR<68j{;;ZrL_w;9daIjFJA;0n8>^fi3I%<>;k~AbNoF!G1WL%%-hehB^~?-R1L(7U%ZuzGc+#2Jdh0)~ z(UEhVj1~}_h3=Ynl*hNyW#EGjAl#}<%J8ILVQ#NWXgHh6p09li+ilam+p}KF@)@(5 zmY)}J?>OyOhDmkO9T@p1bDgjyRQJq&<>!1TFN8+U?C&OObbn^BX0alxrW&RHiGsZE z2-}u(;GfBC!Xb{O;M-(tiD)=WedkWUQZp0ZaHf+nU;7(y>v)?j7~o}9L(AV2xucR> z$+y_NE;e$;>@wFZ@dB1dMRI{Qf7erh&AP)%gomluulzcjftAXHLT0sY$QnLiPY z>OG~TfOV)V)Q83L?^1L_1yrqVeO+-vE=Df)3Mn^gnk5a}&&BR5u{P2%Iy;aJP9yhAROt=-ulE;le&O;eAhtUW_yas2u z-a*Li=!{^(afo zUul_l1 z0eJQfZJWo6vn=r}arx0)qs)M4?DITdc-!HTRKT1XPg^Xxz$QyTMAg}Ji*N0=HH0$H zr{N~IkFopDesgzQb#I5S*RXtkaG^fI-x8y(-IiB=o`63Gg)T|-~+5UHj$dFmlK+RL7!jtqyVo* zK9{JqJso77KHqQcs{z+d{Lkh#C-*$Vg1!vQh+o1C%$T%|SwO$*XcBUP_4D^t;Qg#; zw1J9w_4~(-jiYJs>eH6}75Up1hqo840+2lmUk#VR;^nzDPdheMV^7!eEzcmxwuWY3 zSFb?7i=*BHo29_R<*}zT=w=4-B{1Zb#`)vH<<950;W2jSQL&)A$BRclPqq0r*9xRP z5u{Cb>{NNAebp3T3EJwWe?V1_XzK)UVszbCHs7b;sk9-n2Nd_&|nPnCTY0hdpqX09K@3P3g~vU$4ACn>vS))8dDdR=d~`jMLB$-p7t=8 zv#KO;tFF&YC6pY^Kf$y>n@{=oBN>}D7OLzDirTQ9uQr@<4Iiy3LVPf;3kqyhVX+*7 zXd%l>p&ZXeKfT7KNcpN2pZPuF@E7`+bbNPnF%cEt3Oka#ap<(@RpjdqbRSrbwXa6` zqL&CzdP{QlF=APozXZCdEg$Xorf$dMmV8NsIJbJYv(rAYo92L%rhO~zF}3tKqh_SE~;xkmS&R4gr2)VS|MYsX%IUhP+-6G_cossHFRXi!sYDN=m@O~@s z`0~-7HYY%co8o16Kz|B$MGRjx(@gePF;S0DzG`cO8H&en(BHD|L$8#Ne@e)1E`R^5 zel?sw(o-#EVaJ+)IWd=H`PViKa4uG3Quq^9Aqmxuu8%#_!jv^WqMDN*!9v8a!S{RP-askM%88s-fUWYCSVzonXe6GIxF zj)T7uVuW0uuQL^^ZxcnCc=!!UL7()I-4FlL_LGNuW(zlbf7|}a>8z>NcH4Dt-0HV- znzV-y!v*g`nB#komcQAM!Sy|<*_HsViH3M9bO~nw(@rXZra=YfrJxhB)EH`c_fG12 zK<(!wW+sLbtk&Y$OFu8^i*M>8D72H7naYW*a_K~q=K9JaBBZ|YC{TLv<~-KRS=Oak zcuYRwIllMz1|}5=P&r6vH2egWPiCpHvx_%33Ozh-gN8we(-}{rh_*+AHEH*odNJZU z(cBtLLyQ}01o}bV` zDqrzQMM0~KNIRRQCv^^QabCNZo==u5tnaTLnL%NmsS0$k>>NdJXb?kav1VrtxjgM7 z(@I}?hSu-SqdPt;i?Oe=ak0`-Tb~R+k<&HNN)}BNn-=(SZ!oTXNyqE|wx17+lqm&$ z;*uWcEo|qNnv|MN6ueaH1CXW2m%W)es0H78FZL$)qH%z^SK3-Ml zd&<%%c^w4laA{>c5HFnugRsb$j|Wl=-9#RPaJp>TDA!7Dd7noU_~?$&CQPkJ)*yjC z26|7Xpc|kqE1r1TNMhftU-QUR#a0yQAZ&!wT_c10L2Ffl+lVzN2rbO6Qx52V!kM~3 zVnWcykQ`-@QYl5fI>Ot^`)U>~mAlUq$UYV5!mQd`Cg|yPd#-@}LUHf=xep}q$DpsV z>nGwu7<0N;(^iCj#=phEBG%1D(kOwoW2`K!moc=ft8DanSIwe8_s%k9JcN{s-nu6~ zBAtuDgV(&Tw;Ty_lwR%c^OL4P^fmTS*k-WYUj?!^Z+(7j&Ur|Xy`#6Qz$2rflL?Q; zarLr0Q$U1}S+wA7^Vg$1nmJ=K?V-eXa zIh7qB{oI&H{FE(e8N-@f`B|fbX$~RF)ZI(|90wIjwd9Uet(lHNSlJB)u}q9791Z#J z+<0AojO&ppSxEEo7iRG4BOl=?I9I~s8JHWi=YNB`Cv08^M#_AVyPVHUeqx}do@Z`RxWno%fh0Ph2x!K;E`m7l2mojXS#H_QkiwAi&{x@6dr zI4^iqyO1lQ?#tp^u1%OO0^wI zRwP5ZoZ~`q!%LwAnVdx`R%P4&i~0MSIYBR5F1F&{+Rq97rVAP|?nuf8AF3g17A5clS0oF)pvZ(K(wN7HkE+9zx*g*qtxY zqxCfN89K0&~TbKnsoNnElf%iv}0k$IbZx(?TXf zxg;VMHb=D&De~oH8))LYyN{yiz*>7kh;ohiadcFhsuB)LROmqD&C|cWL*+lG%hc|efc3* zA-I~t=48*>lfNC!j+k&Q!Ng~9D>!SI zF_H%1hBfYhOzQxz&mv`tZnQW^1krS}<-eI);NGLi@EfZ@BwSx^gbgX_(e zILZpFJ;ggNn`pBg9IuNLV{(dcv-&|1TJ}&T105q;>G~A!rRg;2mDxUon=(Vjkw$uE zn#U23o$y@)i%N;>q$@(T)vrIPHu99%1cZ!u&VLe`T${tg6gA#3%NEs=e?$&chf)}| zgh|W1k6n%B`+c3dIgyP%Dve(M_#J=xNJss+dWa_4;x%f9?$AH(OY9x*&Vw zWlk(JojSE4fH*ykFm=sUEgCRY@>Kk0_L@$5(DWou@;o8A^6+ zcs(Z8;KdHnQ%yxmutM$p1m@Cz)#;;;S3_P3=E;<=>Psj8vp7upzY}-TzI+5{Yf9zH zxLKMiX`-;-OQl5q-Ailmj`Lbum*&eAS)NgP-U@H*>plxHp!-gU$nWmuMK?Qs2yMh6 z-jG_t>A^%*O2F|}kbNmc(m5MP)`%BLU$su}tB;6$*^Dcy^+**Hk@2;(BN=gVQym>N zuzn>IXN$|0|8@DR=y*7k0!{KMm1?8dl(RTa9v#(A0XpeeLC7MetEB#36hDsz1@p16 znEr%kxWxRLw1sy+PqKJU&fuN|enm9OSMrEqN1w1#u(6X#i<2jW98DCRn0=PN#Svqs zq_SJ@9_&KKcw!0$* zHV?=$5*Z*H*G(+c@*>b<=%Xjy^i|0>>S#j>CoWbt_$=FR%}|rrYPWsQ5lc3-vSZ&% z3-T4V@@`D^N~5dxmI>C2mLTa+`b*WQ-h&lq_P=^&O9o_cfVSt2`olOFlfx-%ExeYh zH##)+(3H&fkK#9R$zDj0^)0Y&{;5uaqk7s=aVf@`FNb2G_u(zdr+ zI=88QV<2_@qA)t_y(=@smzPcU*M-d0KFai^pzV1?J}BIKqFbYSTCs_-F${5!od{F) zzKLNtN~f&d{78tL+68sn*ia=jc0~Mb)%?Y&3fPdc+WG24gL2vNTyG z)rliDE{p#IXPrRMPygrUR`s{-pA>l;Mt8+NTBx%t-y(KQkAC*)41A4XoeGW=j5VHf^` z90}ES<8WeRYzx3im)}7SQxUow_uFK#Hv>1pXq$n$%n1dM&E?kttM+XvtbgSec2q&G zG*F?-wcx7#i~1+%4EW%l)8amHv~nco;&m!jT>x~>rg z6+mK=pheQwKFXQe@=-(dpI$=lnf>RG1|tohWET_`m;NoF!AL(esw&xv|8D+#>wI9y zd@FiNjRyY^QNAN>!Gj#Ro(||8MGOmSWE>VK1lq$6P6P~T>$`-GaeFbyA!8?}^KZaJ zx6i6idza5@isGg%MMBow)rL#}V;rQNO_Ko>(eRLn*>6W1G83WX$kVd=$Soo+IjBU8 zd5U?OMzzwcS@1&((hUWJnDxK{O4i{!$c0H^=;D@hH} zf3i#$Sc5E^bpg6e%)3rC8Qo+)4vbKp8;m!k$u8P1u7$i5X0G}_ zM0a)x6__uVB7jE9Orn#(xI z3FrXKcZaWrxA%mv7AbB;Lw?incBA1NfFthL#IH{HKpGSTNW|>76Aj<6p~NcGz6x2P z=WK+o&=+qVzcjX=VzpUwv+7^haX+wtuYwf)ek(2@;%o_6eCC5Swk{;9o4qR)d6wYg z+^7FGr`w%YG)+LcLC-AHIn&rY^F*@^dl8&j)NoA+LhIx38 z!`&A`+jOg1K5&{39918V_rGkU5}l+dFDE@e#6Q|32-iIdR%(QoG7d zQ*KD%Lrk7uL1pnH-xWA|VN51T;2G)W2^qLsmbH2g*^<_%ufy7dkzs}+)wXF#EhVLZ zf=cU8XE|Wljw`jp(I@?K+>3Zfdl342yn?;4w24+=E3y_8`V|~@9Ka>XG=MV#D-JQ73%JJ-_9`9nLp_?_>fcoOn;Hc z#Me56BID!Go6E!}_xN0T{RC&|8YjG=R6wqu3~|UrzL20IIS8TM{x+eLfsCI{%X5^F z)bE6fg9o*e#wO9e$-|ZM86`H%l!AuawlL(X~qyo20kYqyuv7ec($ODP*m3)t-;7a_wm#%tWR>Q4bGHa;Y$1Q!%CYEtYDDN5jz&4V8BT?bgX^1jyg)B9A(GIQT^%A zhuCDwz+HTWFn~fzOK+J?Ng$d71EqMGzHLy5faQ1O1Ib#ywS4PPl0`|)Y$EyLrJ2Yem_AQ#iX_){5GDRS>&NL(f=w)EECxN{;LbYPBh76^8 zvT6Ujf_JU&0&Cy3#$~pj3p^2R0BFPR<%V@Hw4Wd3wrq~6 zAbOb#^z7}$Mb1A0e@^-Tcx$^0tM>0(tAQ$)7yHazFn*jLQSHIL{f2yLjeT0V3=bv6 z#DfNL`oTk7l>?Q7IJE<#smFx&^9J=GbkZpS{k9sfu{{zax;si=kHkkvCTU>a7BP1d zKn6H-lKdYLTQUDj#D!KTR;2zlvayp8Dri8!_1Vh8OVq0A(v^Q~St|cw!}(7(Hqqbm z|1=cm0zG3%fKJ>2nwRmD$r|hGN~Bqv8L+{utvqwmRajNPKNE1g!cP=t;gVo;1L+?m zpn$&*;P@q~u8meZ{{kpe;8$?3Sm3AYIi)?a25CPF*LOf2(CR#A()G8@rO4J9 z)xD(n9x@m$&t%}N4flS&B?>ssfeXg#zw;9K0~!G>j)072Dn3qE|DJ~rY`)Q{5u}JZ z6m66DQ^4M*ZJh`C58u3y@uVN%9dCs|#VLPFO;q!CgW?3OyKw(!FjZy}=~X0PG$ z@?SQoaSltLRTMDmsngw2&wU%dgatR_4+q=-#-a{S?Qcr~_OO#~V zE!0l)s4v4Hi3q?_kv9qBV;65BhcSC;f};)1W;uYIRp*@38f#BTz!9oFgSLiGz3YO8 zyXSBJN<#yVOkfNkFy{TCRu~e{8vBxM)nIVLh2jV3mTI3C8qj*|lnoly8|Uu>waHU|L(u)^ z@w3mxCE(ddJ>B4$NhxuA)&VzBYWY5jigWpX_$JvIT>mxK>T(hwjo=C{KHDWGwy8RK zzJ8@!zPh)Ld6InE>U({Ev~_Uu`~m#sN`5*@i<$eDlQM z6nGu=YRtIeY5gS8?ry%D?^OA*d7A zqIv_T`i1Hh8e3sR-A@@9+q#*0_l4C=rfd3dfTi0NLf4y;`F}PFY}ZQ(d>qn9O!%B= zi7bfvYMmiFJNPqgN*=E2i87iCo@&=%pI}C+C>49PgyKlSl#?uIz@F+OLF$m^hl7?q zuG_MxnQ%$Nm_rZOgaaBJd~+$~Xd#Yp4B!%+xzwM|=)0^EFs=K#@WmuxFFa#opynn; zK>Ysx;9%!qe!IN(bI{#W81~cpFt!@7;l_n~ec%K4S3!U+TV+xG(7OU~n_PxvVnrw5 zj;-B@JO7`>lj6v~DE#Dz_>kq3PV3Edb31tzyMRfJ_kZnU;iJ~M4I@Q3N_=pvsDhG= z|0?+vprOMceuE(h9#vwcdJp?T@ZtY;VEEyOpGfW74UgC9A&8ElDiB?mDNkV)Z|KMq*GplZwpedZ<2~7`$JCd)%#9AS zMvS!e(cw2#W*Pm|g=v?cR!Y0zBw$}d{2;tIc|8~?Ey4)HN)n&28*8&PYqcFx+*F$5 zV-+$M#7{=!y*F2^7=-M;r@%99SqUL`-{kj2ij~rMjV3#tA@^FCp;rRR1~z2FQo6@=?dng!4O0hqpU4(HtGGROTwkw=4!xbXL}wskqbe7yBg zUgVi<3(-a%4uoED?;s2r3McvC zmWzD~v=BN$uV=~@ikrbl#;Rug)uSV7SGP4={Ke-Ow%e*PP#A4NCd(GzY3Ox=ZLJEA zB$TgD-Vk{sr2O)xlw{;A6?A=B=>WyX_G=Wwq8t~s)(NoH(5NH)pHjX%s;Ms6Hwi8D z-Vq3d-kX3#rFW4cy(3-35D)=T=~WX~q;n2yKTb(?>s@&s^XYAukG=Hl?-9$!YYONc!E+@= zsv1RdEt;l0`#$&WhV-K4$REbgO7Uo>iLyyK6y*ilA7G0UFyFy;1%fbH9{jpHa>##4QVZ%O%1R zdP3DqD}+rcjc2jFlkTTA55@zy=2l8@jl#T~&)po_m=M z;Fap>KP&1l8*$3!f<5ggYdCw;3yLGQ&hJ(We>5*sP9=a@_pe;kI2ul7v!L zbGIO&6zE)CyVyOfuo<&=*5&hVOm=m`^1PXX4_pYrS8SzGWZQDyvAgHnNLsddo|rO^ zHeoEc(Q|jg*qd}jAeNH0x41Pav9K)qDZcmq>cDF0V%p;|au&{}EKP2|^qSX1uWC!7 z9HG2u!o-upC@rZFUi2O$@n^wNF8iykulFH|AD&>Iu)nc?ds^VKe6A9`vB*F6%;46g z)|MZ8Yg>CKnd1`U&&XYpy&*+}AsZ}*gArTj`$qQ^B zSJNrj@4}LP2JDOzW`4H+^_up<*H_DbzUkj?=~h|O3vReUdPtbpNgwb9I*v7)f}`}J zXje#4q1!uaPDWZ7p$7yhHgWvSWz?TbUd-IF^F0dIF=uh?@yeoP9+7llco=3RdEr@4 zfGZIm{aD}2NR(Nos2VMP7y9M<;>L2@XvDO~Bxc=xZOA5Dgj_zspNZY{QHK+$2mfes zt7=bZNi9Q0w=roMZN~Zca(FEE%0D@4`sWwqR$7AM82-P~%xlEw{G=;DzCFu9 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 }iy#y{fgQX^;H>A_P7;WYRyHCE-|g{j4|s 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

pA#@o3iUbImTE#-g4)WP} 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 diff --git a/tools/qacheck/go.mod b/tools/qacheck/go.mod index ecc3b425ff9..d175bd75b03 100644 --- a/tools/qacheck/go.mod +++ b/tools/qacheck/go.mod @@ -2,8 +2,6 @@ module qacheck go 1.25.7 -toolchain go1.24.5 - require ( github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed golang.org/x/oauth2 v0.35.0 From 103da6cfe679dd934a89cc93a8e90eb7380a66dc Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:51:42 -0600 Subject: [PATCH 4/7] Added migration steps --- ...00000_CleanupSoftwareHostCountsZeroRows.go | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go index e17b7b046eb..caf993c5831 100644 --- a/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go +++ b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go @@ -13,21 +13,24 @@ func Up_20260223000000(tx *sql.Tx) error { // After this migration, the sync process uses an atomic swap table pattern that never produces zero-count rows. // Add CHECK constraints to prevent zero-count rows from being inserted in the future. - if _, err := tx.Exec(`DELETE FROM software_host_counts WHERE hosts_count = 0`); err != nil { - return err - } - if _, err := tx.Exec(`ALTER TABLE software_host_counts ADD CONSTRAINT ck_software_host_counts_positive CHECK (hosts_count > 0)`); err != nil { - return err - } - - if _, err := tx.Exec(`DELETE FROM software_titles_host_counts WHERE hosts_count = 0`); err != nil { - return err - } - if _, err := tx.Exec(`ALTER TABLE software_titles_host_counts ADD CONSTRAINT ck_software_titles_host_counts_positive CHECK (hosts_count > 0)`); err != nil { - return err - } - - return nil + return withSteps([]migrationStep{ + basicMigrationStep( + `DELETE FROM software_host_counts WHERE hosts_count = 0`, + "deleting zero-count rows from software_host_counts", + ), + basicMigrationStep( + `ALTER TABLE software_host_counts ADD CONSTRAINT ck_software_host_counts_positive CHECK (hosts_count > 0)`, + "adding CHECK constraint to software_host_counts", + ), + basicMigrationStep( + `DELETE FROM software_titles_host_counts WHERE hosts_count = 0`, + "deleting zero-count rows from software_titles_host_counts", + ), + basicMigrationStep( + `ALTER TABLE software_titles_host_counts ADD CONSTRAINT ck_software_titles_host_counts_positive CHECK (hosts_count > 0)`, + "adding CHECK constraint to software_titles_host_counts", + ), + }, tx) } func Down_20260223000000(tx *sql.Tx) error { From ffbfa7fdbf32b6d60e7b6e3b0fc6a134e6677e10 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 09:56:59 -0600 Subject: [PATCH 5/7] Revert unrelated change to tools/qacheck/go.mod --- tools/qacheck/go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/qacheck/go.mod b/tools/qacheck/go.mod index d175bd75b03..ecc3b425ff9 100644 --- a/tools/qacheck/go.mod +++ b/tools/qacheck/go.mod @@ -2,6 +2,8 @@ module qacheck go 1.25.7 +toolchain go1.24.5 + require ( github.com/shurcooL/githubv4 v0.0.0-20260209031235-2402fdf4a9ed golang.org/x/oauth2 v0.35.0 From 2b499de54b3f5b08c293d3fc887317b8f0210d94 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:36:24 -0600 Subject: [PATCH 6/7] Noting that CHECK constraint has auto-generated name. --- .../20260223000000_CleanupSoftwareHostCountsZeroRows.go | 6 ++++-- server/datastore/mysql/software.go | 1 + server/datastore/mysql/software_titles.go | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go index caf993c5831..9c2bdfe0755 100644 --- a/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go +++ b/server/datastore/mysql/migrations/tables/20260223000000_CleanupSoftwareHostCountsZeroRows.go @@ -12,6 +12,8 @@ func Up_20260223000000(tx *sql.Tx) error { // Delete any accumulated zero-count rows from software_host_counts and software_titles_host_counts. // After this migration, the sync process uses an atomic swap table pattern that never produces zero-count rows. // Add CHECK constraints to prevent zero-count rows from being inserted in the future. + // Constraints are unnamed because the sync process uses CREATE TABLE ... LIKE to create swap tables, + // which copies CHECK constraints with auto-generated names. Named constraints would drift after each swap. return withSteps([]migrationStep{ basicMigrationStep( @@ -19,7 +21,7 @@ func Up_20260223000000(tx *sql.Tx) error { "deleting zero-count rows from software_host_counts", ), basicMigrationStep( - `ALTER TABLE software_host_counts ADD CONSTRAINT ck_software_host_counts_positive CHECK (hosts_count > 0)`, + `ALTER TABLE software_host_counts ADD CHECK (hosts_count > 0)`, "adding CHECK constraint to software_host_counts", ), basicMigrationStep( @@ -27,7 +29,7 @@ func Up_20260223000000(tx *sql.Tx) error { "deleting zero-count rows from software_titles_host_counts", ), basicMigrationStep( - `ALTER TABLE software_titles_host_counts ADD CONSTRAINT ck_software_titles_host_counts_positive CHECK (hosts_count > 0)`, + `ALTER TABLE software_titles_host_counts ADD CHECK (hosts_count > 0)`, "adding CHECK constraint to software_titles_host_counts", ), }, tx) diff --git a/server/datastore/mysql/software.go b/server/datastore/mysql/software.go index bc6521a428b..69175f0486f 100644 --- a/server/datastore/mysql/software.go +++ b/server/datastore/mysql/software.go @@ -2668,6 +2668,7 @@ func (ds *Datastore) SyncHostsSoftware(ctx context.Context, updatedAt time.Time) if _, err := w.ExecContext(ctx, "DROP TABLE IF EXISTS "+swapTable); err != nil { return ctxerr.Wrap(ctx, err, "drop existing swap table") } + // CREATE TABLE ... LIKE copies structure including CHECK constraints (with auto-generated names). if _, err := w.ExecContext(ctx, swapTableCreate); err != nil { return ctxerr.Wrap(ctx, err, "create swap table") } diff --git a/server/datastore/mysql/software_titles.go b/server/datastore/mysql/software_titles.go index 6d694fc22c9..0a7923ddeae 100644 --- a/server/datastore/mysql/software_titles.go +++ b/server/datastore/mysql/software_titles.go @@ -780,6 +780,7 @@ func (ds *Datastore) SyncHostsSoftwareTitles(ctx context.Context, updatedAt time if _, err := w.ExecContext(ctx, "DROP TABLE IF EXISTS "+swapTable); err != nil { return ctxerr.Wrap(ctx, err, "drop existing swap table") } + // CREATE TABLE ... LIKE copies structure including CHECK constraints (with auto-generated names). if _, err := w.ExecContext(ctx, swapTableCreate); err != nil { return ctxerr.Wrap(ctx, err, "create swap table") } From 1bbcf89049ad09fabb328430b87b666c97ad61e1 Mon Sep 17 00:00:00 2001 From: Victor Lyuboslavsky <2685025+getvictor@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:44:29 -0600 Subject: [PATCH 7/7] Regenerated schema.sql --- server/datastore/mysql/schema.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/datastore/mysql/schema.sql b/server/datastore/mysql/schema.sql index c70e5fc9f2c..eb37b986d6a 100644 --- a/server/datastore/mysql/schema.sql +++ b/server/datastore/mysql/schema.sql @@ -2631,7 +2631,7 @@ CREATE TABLE `software_host_counts` ( PRIMARY KEY (`software_id`,`team_id`,`global_stats`), KEY `idx_software_host_counts_updated_at_software_id` (`updated_at`,`software_id`), KEY `idx_software_host_counts_team_global_hosts_desc` (`team_id`,`global_stats`,`hosts_count` DESC,`software_id`), - CONSTRAINT `ck_software_host_counts_positive` CHECK ((`hosts_count` > 0)) + CONSTRAINT `software_host_counts_chk_1` CHECK ((`hosts_count` > 0)) ) /*!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 */; @@ -2790,7 +2790,7 @@ CREATE TABLE `software_titles_host_counts` ( PRIMARY KEY (`software_title_id`,`team_id`,`global_stats`), KEY `idx_software_titles_host_counts_team_counts_title` (`team_id`,`hosts_count`,`software_title_id`), KEY `idx_software_titles_host_counts_updated_at_software_title_id` (`updated_at`,`software_title_id`), - CONSTRAINT `ck_software_titles_host_counts_positive` CHECK ((`hosts_count` > 0)) + CONSTRAINT `software_titles_host_counts_chk_1` CHECK ((`hosts_count` > 0)) ) /*!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 */;