diff --git a/JobTrackerApi/Services/StartupInitializationExtensions.cs b/JobTrackerApi/Services/StartupInitializationExtensions.cs index cfde895..1cf1a36 100644 --- a/JobTrackerApi/Services/StartupInitializationExtensions.cs +++ b/JobTrackerApi/Services/StartupInitializationExtensions.cs @@ -1937,7 +1937,14 @@ public static class StartupInitializationExtensions // Hot-path composite indexes for tenant-scoped list/board/stats/analytics // (OwnerUserId + IsDeleted) and reminders (OwnerUserId + FollowUpAt). TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_IsDeleted", "`OwnerUserId`(191), `IsDeleted`"); - TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_FollowUpAt", "`OwnerUserId`(191), `FollowUpAt`"); + // FollowUpAt is `text` on MariaDB (the migration was scaffolded against SQLite, which + // stores DateTimeOffset as TEXT). A text column cannot be indexed without a prefix + // length, so without one this index ALWAYS failed the 3072-byte key check, was caught + // and skipped on every boot, and left the follow-up reminder query unindexed — while + // logging a "Specified key was too long" line the deploy runbook flags as a rollback + // signal. Prefix it like Status(50) below. ISO-8601 date strings sort lexicographically, + // so a 20-char prefix ("YYYY-MM-DD HH:MM:SS") keeps the index useful for the reminder scan. + TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_FollowUpAt", "`OwnerUserId`(191), `FollowUpAt`(20)"); // Status is longtext in MySQL (see JobTrackerContext.OnModelCreating), so it // needs an explicit prefix length to be indexable under MariaDB's key-length rules. TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_IsDeleted_Status", "`OwnerUserId`(191), `IsDeleted`, `Status`(50)");