Skip to main content

Schema Migrations (Alembic)

Alembic owns the schema, including the TimescaleDB hypertables, compression and continuous aggregates. The chain is linear, with a single head.

Migration history​

RevisionFileChanges
001001_initial_schema.pyInitial schema: assets (unique ISIN index), asset_listings, asset_mappings, prices_eod, fundamentals, usage_logs, and the legacy tables stock_data, data_requests, cache_status
002002_refonte_fundamentals.pyfundamentals_highlights, financial_statements, earnings_history, analyst_ratings, etf_details, etf_holdings
003003_index_constituents.pyindex_constituents table (not used by the code)
004004_fix_assets_columns.pyProfile columns of assets
005005_premium_fields.pyShort interest, TTM and growth columns; GICS columns; earnings_trend, esg_scores, outstanding_shares_history
006006_prices_eod_resolution.pyresolution, adjusted, source on prices_eod; ingest_log
007007_realtime_tables.pyprices_intraday hypertable (30-day retention), realtime_subscriptions
008008_drop_legacy_tables.pyDestructive: drops the legacy price tables
009009_fix_assets_isin_unique.pyMerges ISIN duplicates, unique ISIN index and listing identity constraint
010010_news_articles.pynews_articles (unique url)
011011_provider_health.pyprovider_health_log hypertable, provider_health_daily, provider_alerts
012012_alembic_schema_authority.pyAlembic takes over hypertables, compression and weekly/monthly aggregates
013013_solvency_ratios.pySolvency ratios and cost of debt; macro_rates_cache
014014_prices_per_listing.pyprices_eod rebuilt per listing: key (asset_listing_id, resolution, time), rows re-dated to their session; compression and aggregates per listing
015015_dividend_yield_as_ratio.pyStored dividend yields converted from percentages to ratios
016016_price_series_adjustments.pyprice_series_adjustments: how each stored price series is adjusted and when it was last fetched in one piece. Series stored before are fetched again in full at their next ingestion

How migrations run​

  1. The API container runs alembic upgrade head in entrypoint.sh before starting the application. The fonrex-migrate service (profile migrate) does the same alone.
  2. main.py compares the revision stored in alembic_version with the head. A database behind the code is marked unavailable and the routes that need it answer 503 — the application never changes the schema itself.

Migration 014 first deletes the TimescaleDB jobs of the price tables (waiting for one that is running) and locks prices_eod: a compression or refresh job running at the same time would otherwise deadlock with it. The jobs are created again by the migration.

Adding a migration​

alembic revision -m "describe_the_change"

Rename the new file of alembic/versions/ and set its identifiers after the last migration (revision = "017", down_revision = "016", file 017_describe_the_change.py), then:

alembic upgrade head
make migration-check # one head only
  • Add the migration to the migrations table of ARCHITECTURE.md (tests/test_docs_consistency.py).
  • A migration that moves or rewrites data comes with a test in tests/test_timescale_integration.py, run on a real TimescaleDB (make test-db).
  • Write downgrade() too: the integration tests go down and up again.