mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-11-10 10:13:50 +08:00
> [!WARNING] > Recreate PostgreSQL databases from earlier development builds. ## Summary This change replaces startup schema setup with embedded, versioned SQL migrations. `ateapi` uses Goose to apply migrations before readiness. Goose stores one ledger record for each applied migration. Goose runs each migration and inserts its ledger record in one PostgreSQL transaction. Closes #901. Based on this design: https://docs.google.com/document/d/13ixDKRoAIFXeLxS-_1nikNcAy76ca8m0eVgobfib93E/edit?usp=sharing ## Migration behavior `ateapi` gets a session advisory lock for the configured schema before it applies pending migrations. One replica applies migrations while other replicas wait. Goose reads the ledger again after it gets the lock. If a migration fails, PostgreSQL rolls back its SQL and ledger record. Earlier successful migrations remain applied and recorded. Kubernetes restarts the failed replica. The next startup resumes from the first migration without a ledger record. ## Changes - Add Goose and a per-migration ledger. - Replace the initial up and down files with one transactional, up-only migration. - Keep migration 1 aligned with the current schema, including actor egress policy storage. - Remove existence guards and explicit transaction statements from the baseline migration. - Apply all pending migrations before `ateapi` becomes ready. - Serialize each migration run with a PostgreSQL session advisory lock. - Start without changes when the database schema is current or ahead. - Reject application tables that do not have a migration ledger. - Log the starting, current, and latest versions. - Log the applied migration count and duration. - Retry only initial database connection failures. - Return schema and migration errors without a retry. - Add `--postgres-schema` and `ATE_API_POSTGRES_SCHEMA`. - Use `public` as the default PostgreSQL schema. - Use the configured schema for the main and watch pools. - Restrict outbox partition maintenance to the configured schema. - Let the installer use an external PostgreSQL database. - Add the migration design and recovery policy to the repository. ## Migration file policy Migration files use sequential versions and contain exactly one Goose `Up` section. CI rejects down migrations, nontransactional migrations, environment substitution, explicit transaction control, and `IF NOT EXISTS` guards. Before the first stable v1 release, developers can change or squash migrations. Developers must recreate databases after migration history changes. After that release, CI rejects changes or deletions against the latest stable release tag that contains migrations. Goose does not store migration checksums. The binary embeds each migration file, and release-tag checks protect released migration history. ## Compatibility No release includes PostgreSQL support. The `v0.0.0` release predates the PostgreSQL backend. Users must recreate databases from earlier PostgreSQL development builds. Every committed migration prefix must work with the current and previous `ateapi` releases. This rule supports rolling upgrades and temporary binary rollback. A binary rollback does not roll back the database schema. ## Testing Tests cover: - Fresh database migration. - Concurrent startup. - Advisory lock waits. - Current and ahead database schemas. - Rejection of application tables without a migration ledger. - Atomic rollback of a failed migration. - Retention of earlier successful migrations. - Resume from the failed migration after restart. - Configured schema isolation. - Outbox partition isolation. - Migration file policy checks. - Stable release migration immutability.
71 lines
3.2 KiB
Markdown
71 lines
3.2 KiB
Markdown
# PostgreSQL schema evolution
|
|
|
|
`ateapi` applies PostgreSQL migrations before it becomes ready. During a rolling update, the previous binary continues to serve requests while the new binary changes the schema.
|
|
|
|
Goose commits one migration at a time. A failed migration run can leave any completed migration prefix in place. These rules keep the previous binary safe with every migration prefix.
|
|
|
|
## Compatibility contract
|
|
|
|
- Keep every migration prefix compatible with reads and writes from the previous binary.
|
|
- Treat every migration file boundary as a durable database state.
|
|
- Do not depend on a later migration to make an earlier prefix compatible.
|
|
- Let the new binary migrate the schema from the previous release.
|
|
- Make the new binary wait for all pending migrations before readiness.
|
|
- Use explicit column lists in `SELECT` and `INSERT` statements.
|
|
|
|
## Schema change rules
|
|
|
|
Prefer additive changes. Add a table, column, or index before the new binary uses it.
|
|
|
|
Add a column as nullable or give it a compatible database default. Do not make previous binary inserts fail because they omit a new column.
|
|
|
|
Do not remove or rename a table or column that the previous binary uses. Do not change its type or meaning in an incompatible way.
|
|
|
|
Do not tighten a constraint while the previous binary can write a value that violates it. Do not remove a default that the previous binary requires.
|
|
|
|
If two schema structures hold the same data, keep them consistent while both binaries can write. Backfill existing rows before the new binary requires the new structure.
|
|
|
|
Do not run a large data backfill during startup. Propose a separate migration process before you add such a change.
|
|
|
|
## Expand and contract
|
|
|
|
Use an expand and contract sequence for a schema replacement or removal:
|
|
|
|
1. In release N, add the new structure and keep the old structure.
|
|
2. In release N+1, stop using the old structure.
|
|
3. In release N+2, remove the old structure.
|
|
|
|
This sequence keeps the previous binary compatible during a rollout and a temporary binary rollback. A binary rollback does not roll back the schema.
|
|
|
|
## Migration file rules
|
|
|
|
Store migration files in `cmd/ateapi/internal/store/atepg/migrations`.
|
|
|
|
- Use the next sequential `NNNNNN_name.sql` filename.
|
|
- Add exactly one `-- +goose Up` annotation.
|
|
- Use SQL migrations only.
|
|
- Do not add down migrations.
|
|
- Do not use `NO TRANSACTION` or `ENVSUB`.
|
|
- Do not add SQL transaction control statements.
|
|
- Do not use `IF NOT EXISTS` for a schema change.
|
|
- Keep each startup migration short.
|
|
|
|
Before the first stable v1 release, developers can change or squash migration files. Recreate a development database after its migration history changes.
|
|
|
|
After the first stable v1 release, do not change or delete a released migration file. Add a new migration to correct it.
|
|
|
|
Do not edit the `schema_migrations` ledger manually.
|
|
|
|
## Before you submit
|
|
|
|
1. Identify the previous binary reads and writes that use the changed objects.
|
|
2. Check those operations against every new migration prefix.
|
|
3. Use expand and contract when one prefix would break an operation.
|
|
4. Add or update a test for the schema behavior.
|
|
5. Run the migration verifier and PostgreSQL store tests.
|
|
|
|
```sh
|
|
hack/verify/postgresql-migrations.sh
|
|
go test ./cmd/ateapi/internal/store/atepg
|
|
```
|