Switch to using goose for migrations - #2667
Draft
iplay88keys wants to merge 2 commits into
Draft
Conversation
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
Signed-off-by: Jeremy Alvis <jeremy.alvis@solo.io>
iplay88keys
force-pushed
the
iplay88keys/goose-migrations
branch
from
September 2, 2026 23:50
6b00123 to
a7aeb46
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Why this change
Kagent currently uses golang-migrate to apply PostgreSQL schema changes.
The runner records a dirty version before it applies a migration.
It clears that state after the migration transaction commits.
A failure between those operations can commit the schema while the migration table remains dirty.
The runner cannot use the dirty state to prove whether PostgreSQL committed the schema.
An operator must inspect the database and force a version before startup can continue.
Substrate PR #1196 moved Substrate to Goose for the same reason.
This review thread describes the commit and tracking failure.
Goose runs each normal SQL migration and its migration record in one PostgreSQL transaction.
PostgreSQL commits both changes or rolls back both changes.
This behavior removes the uncertain dirty state.
Benefits compared with golang-migrate
forcefor dirty state recoveryforcebecause Goose has no dirty stateThis change keeps the existing PostgreSQL advisory locks.
Each migration source still has an independent lock and migration table.
Fresh database contract
This cutover supports only a fresh installation with a new PostgreSQL database.
It does not migrate PostgreSQL data from a release that uses golang-migrate.
The core source keeps the
schema_migrationstable name.The vector source keeps the
vector_schema_migrationstable name.The tables use the standard Goose column structure after the cutover.
The runner rejects a table that contains the old
dirtycolumn.The error tells the operator to use a new PostgreSQL database.
This PR does not add a migration bridge.
It does not support a product downgrade across the cutover.
Upgrade tests skip releases that do not use Goose.
Later Goose releases can upgrade the database in place.
They will append migration files to the same source sequences.
Separate migration sources
The core and vector sources remain independent.
The core source always runs first.
The vector source runs second when database vectors are active.
Downstream sources run after the built-in sources in registration order.
Each source has these items:
The runner validates every source and migration file first.
It then runs all source prechecks before it applies a migration.
The runner applies sources in registration order.
It does not use one transaction across all sources.
If a later source fails, earlier sources remain committed.
The next startup skips completed sources and retries the failed source.
Migration files
The core source now contains
core/000001_initial.sql.The vector source now contains
vector/000001_initial.sql.Each file contains a
-- +goose Upsection and a-- +goose Downsection.Goose requires the Up section and makes the Down section optional.
Kagent requires both sections because the database CLI supports down migrations.
The baseline Up section combines the previous Up files in version order.
The baseline Down section combines the previous Down files in reverse order.
The runner and CI reject
-- +goose NO TRANSACTION.This rule preserves the atomic schema and migration record guarantee.
CI also protects merged Goose migration files from changes, renames, and deletions.
A later correction must use a new migration file.
Failure behavior
A successful migration can still contain an application defect.
Fix an accepted migration with a new forward migration.
Do not change a migration file after it merges.
The CLI can run a controlled down migration when a forward fix is not suitable.
Operators must stop controllers before a manual down migration.
Otherwise, a controller can apply the migration again during startup.
CLI changes
The database migration CLI keeps these commands:
upapplies pending migrations for all registered sources.down Nreverses migrations for one selected source.goto Vmoves one selected source to a migration version.statusreports applied and pending Goose migrations.versionreports the current version for each selected source.The CLI removes the
forcecommand.Status output also removes the dirty field.
Down migrations can delete data.
They support database operations within the Goose sequence.
They do not provide a product downgrade across the cutover.
Developer documentation
The Kagent development guide now defines the Goose migration rules.
It documents file names, required sections, transactions, immutability, source ownership, and PostgreSQL tests.
Testing
go test ./core/pkg/migrations ./core/pkg/cli/db/migrate ./core/test/upgrade ./core/internal/databasego vet ./core/pkg/migrations ./core/pkg/cli/db/migrate ./core/test/upgrade ./core/internal/databasemake lintmake sqlc-generategit diff --checkThe migration tests cover these cases: