Normalize feedback primary key to (id) for upgraded installs#1801
Normalize feedback primary key to (id) for upgraded installs#1801iplay88keys wants to merge 1 commit intomainfrom
Conversation
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
There was a problem hiding this comment.
Pull request overview
Adds a corrective core migration to make upgraded databases converge on the same feedback schema that fresh installs already get: PRIMARY KEY (id) instead of the legacy composite key. This fits the migration subsystem’s goal of making schema state deterministic across install/upgrade paths.
Changes:
- Add
000004_feedback_single_pk.up.sqlto replace the legacyfeedbackcomposite primary key with a single-column primary key onid. - Add a documented no-op down migration for this convergence step.
- Document the migration’s assumptions and its relationship to existing feedback queries/indexes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
go/core/pkg/migrations/core/000004_feedback_single_pk.up.sql |
Adds the schema change that normalizes feedback PKs to (id). |
go/core/pkg/migrations/core/000004_feedback_single_pk.down.sql |
Adds a no-op rollback file with rationale for leaving the normalized PK in place. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| -- No-op: 000004 is a corrective convergence step. Both fresh installs (which | ||
| -- already had PRIMARY KEY (id) from 000001) and upgraded installs (which | ||
| -- carried the legacy composite PRIMARY KEY (id, user_id) from GORM | ||
| -- AutoMigrate) end at PRIMARY KEY (id). Once up has run, there is no way to | ||
| -- tell which path the database came from, so a symmetric down would put fresh | ||
| -- installs into a composite-PK state they never originally had. Leaving the | ||
| -- single-column PK in place on rollback is safer and the application is | ||
| -- PK-shape independent (queries do not reference the PK directly). | ||
| SELECT 1; |
There was a problem hiding this comment.
This would result in a fresh install env applying this migration on top having the wrong primary key. I think it's fine to leave as a result.
| -- Both variants use the default constraint name `feedback_pkey`, so this | ||
| -- atomic drop+add converges both paths to the single-column PK without | ||
| -- changing column types or affecting sqlc-generated code (queries do not | ||
| -- reference the PK; ListFeedback filters on user_id via idx_feedback_user_id). |
There was a problem hiding this comment.
@EItanya I don't think this is necessary based on the limited usefulness of the feedback feature and the unlikelihood that this will have duplicate ids. The only reason I'm considering it is to have a clearer message for manual fixes in the unlikely event that there is an issue with this upgrade. Another option is we we could just raise it in the release notes if we want, but as before, I think this is unlikely to be an issue.
Summary
Adds migration
000004_feedback_single_pkthat converts thefeedbacktable's primary key from the legacy composite(id, user_id)to single(id), so fresh installs and upgraded installs end at the same schema.Background
Feedback's PK shape diverged in OSS PR #1306 (commit
cbc8d57e, first released in v0.7.15):Feedbackstructgorm.Model+UserID gorm:"primaryKey;not null"(id, user_id)gorm.Model+UserID gorm:"not null;index"(id)GORM AutoMigrate is additive-only, so it cannot drop a primary key. Any deployment that was ever migrated under v0.7.14 or earlier kept the composite PK forever, even after the struct was fixed.
The
000001_initialbaseline matches the fixed struct's output (id BIGSERIAL PRIMARY KEY), which is correct for fresh installs but no-ops on upgraded installs (theCREATE TABLE IF NOT EXISTSis skipped). Result: upgraded installs still carry the composite PK after running the new migration tooling.Risks
Duplicate
idrows would block the migration. The old composite(id, user_id)only enforces uniqueness of the pair, so it permits two rows withid=1if theiruser_iddiffers. The new single-column(id)requiresiditself to be unique. This isn't purely hypothetical for long-lived deployments: throughout the GORM era (and still today, until sqlc closed the gap in v0.9.x) the feedback HTTP handler unmarshalled the entiredatabase.Feedbackstruct directly from the request body, including the embeddedgorm.Model.IDfield — so a client sending{"id": 99999, "user_id": "...", ...}could insert with an explicit id. The pre-fixsave()helper usedON CONFLICT ... DO UPDATEon the composite PK, andStoreFeedbackafter PR #1306 useddb.Create(); both paths passed a non-zero ID through to Postgres unchanged. The composite PK then allowed the same explicit id to appear under multipleuser_idvalues. The sqlc-era query is hardcoded to omitidfrom the column list, which is why this gap doesn't grow further on v0.9.x — but pre-v0.9.x history is what's already in the database.If any such rows exist,
ADD CONSTRAINT feedback_pkey PRIMARY KEY (id)will fail with a unique-violation and the migration aborts atomically. This surfaces at startup before traffic, which is the correct failure mode, but operators should run the following against any long-lived database before deploying and dedupe if it returns rows:Brief table lock during migration.
DROP CONSTRAINTandADD CONSTRAINT PRIMARY KEYeach take anAccessExclusiveLockonfeedback, blocking all reads and writes for the duration. Adding the new PK requires a full table scan to validate uniqueness and not-null. For typical feedback data sizes this completes in milliseconds; an unusually large table could take seconds. The whole migration runs in a single transaction so the state is always consistent.No foreign keys reference
feedback.id, so there are no cascades or referential-integrity ripples to manage.