fix(db): create follow-up reminder index on MariaDB
The reconciler's IX_JobApplications_OwnerUserId_FollowUpAt was declared as
(OwnerUserId(191), FollowUpAt) with no prefix length on FollowUpAt. But
FollowUpAt is `text` on MariaDB -- JobApplications is migration-owned and
the migration was scaffolded against SQLite, which stores DateTimeOffset
as TEXT. A text column cannot be indexed without a prefix length, so this
index failed the 3072-byte key check on EVERY MariaDB boot, was caught by
TryCreateIndex, and was silently skipped -- leaving the follow-up reminder
query (OwnerUserId + FollowUpAt) unindexed.
Two consequences, both real:
- the index the code intends to create never existed on MariaDB
- every healthy boot logged "Specified key was too long", which
deploy/first-production-deployment.md lists as a STOP-AND-ROLL-BACK
signal -- so an operator following the runbook could abort a good deploy
The author already handled the identical problem for the longtext Status
column one line below with Status(50). Apply the same fix: FollowUpAt(20).
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.
Verified on a fresh empty MariaDB 11 container: the index is now created
(both key parts present), zero "Specified key was too long" lines, zero
skipped indexes, zero unhandled exceptions, 42 tables, app healthy. This
was the only unprefixed text column in any reconciler composite index --
the datetime columns on reconciler-owned tables are datetime(6). SQLite is
unaffected (its CREATE INDEX has no key-length limit).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1937,7 +1937,14 @@ public static class StartupInitializationExtensions
|
|||||||
// Hot-path composite indexes for tenant-scoped list/board/stats/analytics
|
// Hot-path composite indexes for tenant-scoped list/board/stats/analytics
|
||||||
// (OwnerUserId + IsDeleted) and reminders (OwnerUserId + FollowUpAt).
|
// (OwnerUserId + IsDeleted) and reminders (OwnerUserId + FollowUpAt).
|
||||||
TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_IsDeleted", "`OwnerUserId`(191), `IsDeleted`");
|
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
|
// Status is longtext in MySQL (see JobTrackerContext.OnModelCreating), so it
|
||||||
// needs an explicit prefix length to be indexable under MariaDB's key-length rules.
|
// 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)");
|
TryCreateIndex("JobApplications", "IX_JobApplications_OwnerUserId_IsDeleted_Status", "`OwnerUserId`(191), `IsDeleted`, `Status`(50)");
|
||||||
|
|||||||
Reference in New Issue
Block a user