From 1a92bea08f4c667303b336800079da74a5ce3152 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Mon, 8 Jun 2026 16:06:50 -0500 Subject: [PATCH] RE1-T117 migration fix --- .../M0073_AddingReportingIndexes.cs | 46 +++++++++++-------- .../M0073_AddingReportingIndexesPg.cs | 46 +++++++++++-------- Workers/Resgrid.Workers.Console/Program.cs | 6 +++ 3 files changed, 58 insertions(+), 40 deletions(-) diff --git a/Providers/Resgrid.Providers.Migrations/Migrations/M0073_AddingReportingIndexes.cs b/Providers/Resgrid.Providers.Migrations/Migrations/M0073_AddingReportingIndexes.cs index 3799b2ada..f8562a16c 100644 --- a/Providers/Resgrid.Providers.Migrations/Migrations/M0073_AddingReportingIndexes.cs +++ b/Providers/Resgrid.Providers.Migrations/Migrations/M0073_AddingReportingIndexes.cs @@ -9,31 +9,36 @@ namespace Resgrid.Providers.Migrations.Migrations /// live dashboard aggregates GROUP BY / filter on. Index creation is guarded so it is safe if a /// matching index already exists. /// - [Migration(73)] + [Migration(73, TransactionBehavior.None)] public class M0073_AddingReportingIndexes : Migration { public override void Up() { // Pre-aggregated daily rollup (DepartmentId null => system-wide row). - Create.Table("ReportingDailyRollup") - .WithColumn("ReportingDailyRollupId").AsInt64().NotNullable().PrimaryKey().Identity() - .WithColumn("DepartmentId").AsInt32().Nullable() - .WithColumn("BucketDateUtc").AsDateTime2().NotNullable() - .WithColumn("Metric").AsString(128).NotNullable() - .WithColumn("Dimension").AsString(256).Nullable() - .WithColumn("ItemCount").AsInt64().NotNullable().WithDefaultValue(0) - .WithColumn("SumValue").AsDecimal(18, 4).Nullable() - .WithColumn("MinValue").AsDecimal(18, 4).Nullable() - .WithColumn("MaxValue").AsDecimal(18, 4).Nullable() - .WithColumn("P50").AsDecimal(18, 4).Nullable() - .WithColumn("P90").AsDecimal(18, 4).Nullable() - .WithColumn("CreatedOnUtc").AsDateTime2().NotNullable().WithDefault(SystemMethods.CurrentUTCDateTime); + // TransactionBehavior.None means each statement self-commits, so every create below is + // guarded with an existence check to stay safe on a re-run after a partial apply + // (e.g. when a later large index build times out and the migration is retried). + if (!Schema.Table("ReportingDailyRollup").Exists()) + Create.Table("ReportingDailyRollup") + .WithColumn("ReportingDailyRollupId").AsInt64().NotNullable().PrimaryKey().Identity() + .WithColumn("DepartmentId").AsInt32().Nullable() + .WithColumn("BucketDateUtc").AsDateTime2().NotNullable() + .WithColumn("Metric").AsString(128).NotNullable() + .WithColumn("Dimension").AsString(256).Nullable() + .WithColumn("ItemCount").AsInt64().NotNullable().WithDefaultValue(0) + .WithColumn("SumValue").AsDecimal(18, 4).Nullable() + .WithColumn("MinValue").AsDecimal(18, 4).Nullable() + .WithColumn("MaxValue").AsDecimal(18, 4).Nullable() + .WithColumn("P50").AsDecimal(18, 4).Nullable() + .WithColumn("P90").AsDecimal(18, 4).Nullable() + .WithColumn("CreatedOnUtc").AsDateTime2().NotNullable().WithDefault(SystemMethods.CurrentUTCDateTime); - Create.Index("IX_ReportingDailyRollup_Dept_Date_Metric") - .OnTable("ReportingDailyRollup") - .OnColumn("DepartmentId").Ascending() - .OnColumn("BucketDateUtc").Ascending() - .OnColumn("Metric").Ascending(); + if (!Schema.Table("ReportingDailyRollup").Index("IX_ReportingDailyRollup_Dept_Date_Metric").Exists()) + Create.Index("IX_ReportingDailyRollup_Dept_Date_Metric") + .OnTable("ReportingDailyRollup") + .OnColumn("DepartmentId").Ascending() + .OnColumn("BucketDateUtc").Ascending() + .OnColumn("Metric").Ascending(); // Source-table covering indexes for the live dashboard aggregates. if (!Schema.Table("Calls").Index("IX_Calls_DepartmentId_LoggedOn").Exists()) @@ -100,7 +105,8 @@ public override void Down() if (Schema.Table("Calls").Index("IX_Calls_DepartmentId_LoggedOn").Exists()) Delete.Index("IX_Calls_DepartmentId_LoggedOn").OnTable("Calls"); - Delete.Table("ReportingDailyRollup"); + if (Schema.Table("ReportingDailyRollup").Exists()) + Delete.Table("ReportingDailyRollup"); } } } diff --git a/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0073_AddingReportingIndexesPg.cs b/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0073_AddingReportingIndexesPg.cs index 14d1323c8..e3fe11ab3 100644 --- a/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0073_AddingReportingIndexesPg.cs +++ b/Providers/Resgrid.Providers.MigrationsPg/Migrations/M0073_AddingReportingIndexesPg.cs @@ -8,31 +8,36 @@ namespace Resgrid.Providers.MigrationsPg.Migrations /// covering indexes on the source tables. All identifiers are lowercased per the PG convention. /// Index creation is guarded so it is safe if a matching index already exists. /// - [Migration(73)] + [Migration(73, TransactionBehavior.None)] public class M0073_AddingReportingIndexesPg : Migration { public override void Up() { // Pre-aggregated daily rollup (DepartmentId null => system-wide row). - Create.Table("ReportingDailyRollup".ToLower()) - .WithColumn("ReportingDailyRollupId".ToLower()).AsInt64().NotNullable().PrimaryKey().Identity() - .WithColumn("DepartmentId".ToLower()).AsInt32().Nullable() - .WithColumn("BucketDateUtc".ToLower()).AsDateTime2().NotNullable() - .WithColumn("Metric".ToLower()).AsCustom("citext").NotNullable() - .WithColumn("Dimension".ToLower()).AsCustom("citext").Nullable() - .WithColumn("ItemCount".ToLower()).AsInt64().NotNullable().WithDefaultValue(0) - .WithColumn("SumValue".ToLower()).AsDecimal(18, 4).Nullable() - .WithColumn("MinValue".ToLower()).AsDecimal(18, 4).Nullable() - .WithColumn("MaxValue".ToLower()).AsDecimal(18, 4).Nullable() - .WithColumn("P50".ToLower()).AsDecimal(18, 4).Nullable() - .WithColumn("P90".ToLower()).AsDecimal(18, 4).Nullable() - .WithColumn("CreatedOnUtc".ToLower()).AsDateTime2().NotNullable().WithDefault(SystemMethods.CurrentUTCDateTime); + // TransactionBehavior.None means each statement self-commits, so every create below is + // guarded with an existence check to stay safe on a re-run after a partial apply + // (e.g. when a later large index build times out and the migration is retried). + if (!Schema.Table("ReportingDailyRollup".ToLower()).Exists()) + Create.Table("ReportingDailyRollup".ToLower()) + .WithColumn("ReportingDailyRollupId".ToLower()).AsInt64().NotNullable().PrimaryKey().Identity() + .WithColumn("DepartmentId".ToLower()).AsInt32().Nullable() + .WithColumn("BucketDateUtc".ToLower()).AsDateTime2().NotNullable() + .WithColumn("Metric".ToLower()).AsCustom("citext").NotNullable() + .WithColumn("Dimension".ToLower()).AsCustom("citext").Nullable() + .WithColumn("ItemCount".ToLower()).AsInt64().NotNullable().WithDefaultValue(0) + .WithColumn("SumValue".ToLower()).AsDecimal(18, 4).Nullable() + .WithColumn("MinValue".ToLower()).AsDecimal(18, 4).Nullable() + .WithColumn("MaxValue".ToLower()).AsDecimal(18, 4).Nullable() + .WithColumn("P50".ToLower()).AsDecimal(18, 4).Nullable() + .WithColumn("P90".ToLower()).AsDecimal(18, 4).Nullable() + .WithColumn("CreatedOnUtc".ToLower()).AsDateTime2().NotNullable().WithDefault(SystemMethods.CurrentUTCDateTime); - Create.Index("IX_ReportingDailyRollup_Dept_Date_Metric".ToLower()) - .OnTable("ReportingDailyRollup".ToLower()) - .OnColumn("DepartmentId".ToLower()).Ascending() - .OnColumn("BucketDateUtc".ToLower()).Ascending() - .OnColumn("Metric".ToLower()).Ascending(); + if (!Schema.Table("ReportingDailyRollup".ToLower()).Index("IX_ReportingDailyRollup_Dept_Date_Metric".ToLower()).Exists()) + Create.Index("IX_ReportingDailyRollup_Dept_Date_Metric".ToLower()) + .OnTable("ReportingDailyRollup".ToLower()) + .OnColumn("DepartmentId".ToLower()).Ascending() + .OnColumn("BucketDateUtc".ToLower()).Ascending() + .OnColumn("Metric".ToLower()).Ascending(); // Source-table covering indexes for the live dashboard aggregates. if (!Schema.Table("Calls".ToLower()).Index("IX_Calls_DepartmentId_LoggedOn".ToLower()).Exists()) @@ -98,7 +103,8 @@ public override void Down() if (Schema.Table("Calls".ToLower()).Index("IX_Calls_DepartmentId_LoggedOn".ToLower()).Exists()) Delete.Index("IX_Calls_DepartmentId_LoggedOn".ToLower()).OnTable("Calls".ToLower()); - Delete.Table("ReportingDailyRollup".ToLower()); + if (Schema.Table("ReportingDailyRollup".ToLower()).Exists()) + Delete.Table("ReportingDailyRollup".ToLower()); } } } diff --git a/Workers/Resgrid.Workers.Console/Program.cs b/Workers/Resgrid.Workers.Console/Program.cs index bf66938c4..e5088e834 100644 --- a/Workers/Resgrid.Workers.Console/Program.cs +++ b/Workers/Resgrid.Workers.Console/Program.cs @@ -562,6 +562,9 @@ private static IServiceProvider CreateServices() .AddPostgres11_0() // Set the connection string .WithGlobalConnectionString(Config.DataConfig.CoreConnectionString) + // Index builds on large tables (e.g. ActionLogs) exceed the 30s default; + // allow up to 30 minutes per command so migrations don't time out. + .WithGlobalCommandTimeout(TimeSpan.FromMinutes(30)) // Define the assembly containing the migrations .ScanIn(typeof(M0001_InitialMigrationPg).Assembly).For.Migrations().For.EmbeddedResources()) // Enable logging to console in the FluentMigrator way @@ -580,6 +583,9 @@ private static IServiceProvider CreateServices() .AddSqlServer() // Set the connection string .WithGlobalConnectionString(Config.DataConfig.CoreConnectionString) + // Index builds on large tables (e.g. ActionLogs) exceed the 30s default; + // allow up to 30 minutes per command so migrations don't time out. + .WithGlobalCommandTimeout(TimeSpan.FromMinutes(30)) // Define the assembly containing the migrations .ScanIn(typeof(M0001_InitialMigration).Assembly).For.Migrations().For.EmbeddedResources()) // Enable logging to console in the FluentMigrator way