1 07 Migrations
sophrosyne edited this page 2026-10-06 21:53:18 +00:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Миграции схемы БД

Система миграций управляет изменениями схемы базы данных. Построена на чистом asyncpg без внешних зависимостей и ORM.

Назначение

Миграции обеспечивают воспроизводимое и отслеживаемое изменение схемы БД при обновлениях приложения. Каждая миграция применяется один раз и записывается в таблицу _migration.

Как это работает

  1. При старте приложения (run.py) после инициализации пула соединений вызывается migrations.runner.run_migrations.
  2. Runner берёт advisory-блокировку PostgreSQL, чтобы исключить гонку между процессами.
  3. Создаётся таблица _migration (id TEXT PRIMARY KEY, applied_at TIMESTAMPTZ), если её нет.
  4. Runner читает уже применённые миграции и обнаруживает все модули migrations/m*.py.
  5. Неприменённые миграции выполняются по порядку номеров, каждая в отдельной транзакции.
  6. После успешного применения миграция записывается в _migration.

Структура миграции

Миграция — Python-модуль в директории migrations/ с именем формата mNNN_описание.py, где NNN — трёхзначный номер.

Каждый модуль экспортирует:

  • MIGRATION_ID: str — уникальный идентификатор миграции (обычно совпадает с именем модуля).
  • async def upgrade(conn) -> None — асинхронная функция, выполняющая изменения схемы. Получает соединение asyncpg.Connection внутри транзакции.

Пример

"""Миграция m002: добавляет колонку example в таблицу chat."""

MIGRATION_ID = "m002_add_example_column"


async def upgrade(conn) -> None:
    """
    Добавляет колонку example в таблицу chat.

    Аргументы:
        conn: Соединение с БД внутри транзакции.
    """

    await conn.execute("ALTER TABLE chat ADD COLUMN IF NOT EXISTS example TEXT")

Baseline-миграция и schema.sql

Базовая схема БД описана в migrations/schema.sql — чистый DDL (таблицы, последовательности, констрейнты, индексы), извлечённый из дампа. Таблица _migration в файл не входит — её создаёт runner.

Миграция m001_base_schema применяет schema.sql только на пустой базе: если таблица app_user уже существует (to_regclass), миграция завершается без изменений. Поэтому DDL в schema.sql не содержит IF NOT EXISTS — повторное применение к существующей схеме невозможно по устройству guard.

schema.sql также используется при сборке Docker-образа: стадия schema-builder применяет все миграции к временной Postgres и снимает итоговую схему через pg_dump --schema-only для генерации ERD-диаграммы (см. deployment.md).

Добавление новой миграции

  1. Создай файл migrations/mNNN_краткое_описание.py, где NNN — следующий свободный номер.
  2. Определи MIGRATION_ID и async def upgrade(conn).
  3. Пиши raw SQL через conn.execute / conn.fetch / conn.fetchval.
  4. Используй IF NOT EXISTS / IF EXISTS для идемпотентности, где применимо.
  5. Миграция применяется автоматически при следующем старте приложения.

Ограничения

  • Миграции необратимы — операции отката (downgrade) не поддерживаются.
  • Каждая миграция выполняется в отдельной транзакции. Если upgrade бросает исключение, транзакция откатывается, миграция не записывается как применённая.
  • Нельзя изменять уже применённую миграцию — её MIGRATION_ID записан в _migration. Для новых изменений создавай новую миграцию со следующим номером.
  • Имя файла должно начинаться с m и трёх цифр (m001, m002, ...), иначе runner его не обнаружит.

Авто-применение при старте

Миграции применяются автоматически при запуске run.py (в режимах --bot, --panel, --all) после инициализации пула соединений и до старта бота/панели. Отдельная команда не требуется.

Advisory-блокировка (pg_try_advisory_lock) гарантирует, что при конкурентном запуске нескольких процессов против одной БД миграции применяются только одним процессом, остальные пропускают.