Migrations
msm schema creation and schema evolution are admin workflows. Runtime code
attaches to already-registered MetaTable and TimeIndexMetaTable resources
through direct backend lookups keyed by SQLAlchemy table name; it does not
create tables, apply DDL, or repair schema drift.
The package exposes one application-owned MetaTables Alembic provider:
msm_migrations:migration
That single provider covers core msm, msm_portfolios, and msm_pricing
MetaTables. Do not create separate migration configurations for those packages.
msm.migrations:migration remains a compatibility alias for the same provider
object. The top-level migrations package shipped by ms-markets 2.0.x is no
longer installed, because generic top-level package names collide with other
distributions and shadow application-owned migrations packages. The rename
does not change the provider key msm:<namespace> or the
ms_markets__alembic_version table. See
ADR 0044.
Since 2.1.0 fresh databases start at 0018_initial_schema. Release 2.2.7
restores recognition of the retired 0017 head through a compatibility anchor
and merge revision 0020. A populated PostgreSQL database at 0017 upgrades
in place; databases at 0018 or 0019 reach the same head. Applied revisions
0018 and 0019, provider identity, and version-table binding are unchanged.
The bridge validates all 62 application tables and their column contracts.
Only ten known, formerly unbounded identifier columns may differ. It refuses
values longer than 255 characters before bounding those columns, locks affected
tables against writes, and verifies row counts and row multiset checksums before
and after the change. Extra tables and columns remain intact. Already-current
development columns need no alteration. No reset, downgrade, or manual stamp is
needed. Revisions 0001–0016 remain unsupported; investigate those separately.
The bridge is forward-only. See
ADR 0046.
Release 3.0.0 adds revision 0022. Each pre-3.0 *_data_node_uid column gets
a successor named after the identity it holds, *_table_update_uid or
*storage_table_uid, and the existing values are copied. ms-markets 3.x writes
both columns and reads the pre-3.0 ones, because 2.x writers update only those.
A later release switches reads once every writer runs 3.x, 4.0 stops writing the
pre-3.0 columns, and 4.1 drops them; each step needs its own release. See
ADR 0048.
Revision 0019_ledger_hypertable_uniqueness removes the event ledger's extra
unique constraint that omitted the TimescaleDB partition key. The full-grain
unique index and existing rows remain intact; the accounting engine validates
cross-time economic identities. This is a forward repair, including when 0018
committed but hypertable/catalog finalization failed. Do not rewrite or stamp
the applied revision. Once the ledger is a hypertable, TimescaleDB rejects a
downgrade restoring the old non-time constraint; use a forward repair instead.
Admin Commands
Use the metatables CLI from the mainsequence-metatable client directly:
metatables --json migrations current --provider msm_migrations:migration
metatables migrations revision --provider msm_migrations:migration -m "describe change"
metatables --local migrations upgrade --provider msm_migrations:migration head
metatables --local migrations downgrade --provider msm_migrations:migration <revision>
revision autogenerates by default. The retired mainsequence migrations
commands are not available in Main Sequence SDK 9.
There is no msm migrations ... command group. The msm package integration is
the provider object.
Run upgrade and downgrade yourself only against a local runtime
(metatables --local migrations ...). Hosted environments are migrated by the
deployment workflow, described next.
Hosted Deployments
Hosted runtimes are migrated only by a Job in the deployment workflow, which
runs from the candidate image before anything that uses the tables rolls out.
This repository's .mainsequence/workflows/ms-markets-api.yaml builds the image, runs
jobs/migrate_markets.py, and deploys the Markets API with
needs: [migrate]. The Job calls
metatables.upgrade_application("msm_migrations:migration"); a database already
at head is left unchanged, and a failure blocks the API rollout while the
previous release keeps serving.
Migration diagnostics and repeated runs
The minimum client is MetaTables 0.1.51. upgrade_application() automatically
uses fresh table inventories and batched physical inspections; PostgreSQL
reflection is batched by schema. Finalization requests remain bounded to ten
tables each, with the version table first. No batching option or project-specific
runner is required.
The hosted Job wraps upgrade_application in
api_endpoint_scope(discovery_timeout=300), explicitly allowing up to 300 seconds
for a waking MetaTables deployment. Ordinary client calls retain a 30-second
discovery budget. A deployment reported as unavailable, a discovery failure,
or a schema/catalog failure still fails the Job and blocks deployment. Verify
the Environment's MetaTables deployment is ready before restarting a failed workflow.
jobs/migrate_markets.py enables INFO logging for metatables.migrations.runner.
Its logs separate preparation, database setup, table checks, Alembic execution,
catalog finalization, and total time. Measure these separately from platform
scheduling and workflow coordination; do not log migration connection responses
or credentials.
An already-current revision still runs catalog reconciliation, including
missing-table checks and description, label, and column metadata refresh. Do not
skip the Job or finalization because migrated=False. If DDL committed but
finalization failed, inspect the actual schema and per-table errors, then rerun
the same migration through a new deployment. Do not stamp, rewrite applied
revisions, or downgrade to recover catalog state.
Legacy TimescaleDB finalization also requires the shared MetaTables API
0.1.34 ownership-before-conversion repair. On TimescaleDB 2.10.2, converting
a populated ordinary table and then reassigning it fails when the API login
lacks CREATE on _timescaledb_internal. The API now transfers the ordinary
table first, then converts it with migrate_data, in one transaction. This
requires no new grant or application revision. If 0020 committed but
finalization failed, deploy the repaired shared API and rerun the hosted Job at
head; keep all existing application data and applied revisions. An already
current development hypertable keeps its owner and rows. This is a server
requirement; the Markets migration client requirement is separate (currently 0.1.51).
The runner carries changed, non-empty authored table descriptions, labels, and column descriptions, labels, and logical names in each finalization batch. An unchanged revision can therefore refresh catalog metadata without new DDL. Empty or absent metadata preserves existing values; fresh physical inspection still owns types, keys, indexes, and nullability. Equal-valued reruns avoid catalog writes. This payload requires the matching MetaTables API update, which also corrects SQLite schema reflection for multi-batch downgrades.
Client improvements require an updated application image. API-side batching,
unchanged-contract write avoidance, and startup optimizations require the shared
MetaTables API to be deployed with that release too. Update matching guidance
with metatables copy-metatables-skills --path . after updating the client.
Required: MetaTables roles for the deployment
The migration Job runs as its own workload User and needs the editor role on
the mainsequence.markets namespace; the API needs data_writer. These are
MetaTables roles, not workflow entries. In each Environment the namespace's
owners grant them, or, where the Job's first run created the namespace, the
people who manage the Job. Without editor the Job's registration fails with
namespace_access_required and the API is not deployed; see
MetaTables access in each Environment.
The ms-markets schema is owned and migrated only by this repository's
deployment. An application that installs ms-markets to read or write its tables
does not apply msm_migrations:migration; its own migration Job, if it has one,
applies only its own providers, and it runs against the schema the ms-markets
deployment has applied.
Do not run migrations at application startup, and do not run
metatables migrations upgrade or downgrade against a hosted API from a
developer or agent session.
Each revision must stay usable by the release that is still deployed, because it keeps serving during the rollout and after a failed one: add tables and nullable or defaulted columns first, and drop or rename in a later release. Redeploying an older image does not roll back the schema.
revision creates normal Alembic revision files at the provider's
namespace version location. Revision files and namespace-specific revision
directories are generated authoring output; documentation must not treat them
as pre-existing checkout state. Revisions are generated by Alembic; they are not
hand-authored operation manifests.
The package migration environment stays on the standard MetaTables scaffold path:
src/msm_migrations/env.py;src/msm_migrations/script.py.mako;- namespace version locations calculated by
metatables.migrations.
env.py, provider construction, provider model registry, version-table class
construction, metadata extraction, namespace version-location calculation, and
the revision template should stay on the metatables.migrations helper path
instead of local boilerplate.
Default Schema Rule
PostgreSQL public is the default schema. In this provider's SQLAlchemy
metadata, default-schema tables must be authored with schema=None, not
schema="public".
This matters for Alembic autogenerate. PostgreSQL reflection reports
default-schema foreign keys as schema=None. If model metadata says
schema="public", Alembic treats identical foreign keys as changed and emits
false drop_constraint(...) / create_foreign_key(...) pairs. Reject that
generated revision; the model or migration environment is wrong.
Use explicit schema metadata only for real non-default schemas.
upgrade loads the provider in the application process, reserves its catalog
entries through the MetaTables API, applies the Alembic migration with the
environment connection selected by that API, and finalizes the provider
MetaTable resources.
downgrade uses the same provider and Alembic revision graph to move the
schema back to an earlier revision.
Lifecycle
- Add or change SQLAlchemy model declarations.
- Ensure the model is returned by the package model graph:
markets_sqlalchemy_models(),portfolio_sqlalchemy_models(), orpricing_sqlalchemy_models(). - Let
metatables migrations revision --provider msm_migrations:migrationgenerate a normal Alembic revision. - Review the generated Alembic operations. A no-op model state must not produce
FK drop/create churn, index churn, or
publicversus default-schema churn. - Apply and finalize the revision against the local runtime:
metatables --local migrations upgrade --provider msm_migrations:migration head - Start runtime code with
msm.start_engine(...). - Commit the revision with the code that needs it. The deployment workflow's migration Job applies it to hosted environments before the API rolls out.
msm.start_engine(...) is direct and read-only. It resolves selected backend
tables by model.__table__.name and fails if required platform MetaTable or
TimeIndexMetaTable resources are missing.
Registry
src/msm_migrations/registry.py defines the package-owned table universe used
by the MetaTables provider. It is the msm equivalent of an installed-app
registry, not migration history.
The registry is derived from:
msm.models.markets_sqlalchemy_models();msm_portfolios.models.portfolio_sqlalchemy_models();msm_pricing.meta_tables.pricing_sqlalchemy_models().
Managed models must inherit normal metatables authoring bases. Plain MetaTables use
PlatformManagedMetaTable through MarketsMetaTableMixin; time-indexed
time-index-table output uses PlatformTimeIndexMetaTable through
MarketsTimeIndexMetaTableMixin.
The registry is built through the metatables.migrations
build_metatable_model_registry(...) helper so filtering, duplicate identifier
detection, and provider order follow the same rules as other MetaTables
migration providers.
Time-index storage identifiers use the same CamelCase style as domain
MetaTables plus a TS suffix, for example OrdersTS and AssetSnapshotsTS.
See the platform-focused migration reference for provider details: MetaTable Migrations.