← Назад к вопросам

Как реализовано версионирование в БД?

2.0 Middle🔥 161 комментариев
#DevOps и инфраструктура#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как реализовано версионирование в БД

Версионирование (migration versioning) в БД — это способ отслеживания изменений схемы и управления их развёртыванием. Это критично для командной разработки.

1. Системы миграций

Основные инструменты

Python проекты:
  • Alembic (SQLAlchemy)
  • Django Migrations
  • Tortoise ORM migrations
  • Goose (Go, но популярен в Go + Python стеках)

Общие принципы:
  • Каждая миграция = отдельный файл
  • Версионирование помощью timestamp или sequence numbers
  • Up/Down функции (apply/rollback)
  • История всех изменений сохраняется

2. Классическая структура Alembic

# Структура проекта
alembic/
  └── versions/
      ├── 001_initial_schema.py
      ├── 002_add_users_table.py
      ├── 003_add_posts_table.py
      └── 004_add_indexes.py
  └── env.py
  └── script.py.mako
  └── alembic.ini

# Пример миграции
# 001_initial_schema.py
from alembic import op
import sqlalchemy as sa

revision = 'a1b2c3d4'  # Уникальный ID
down_revision = None   # Parent revision
branch_labels = None
depends_on = None

def upgrade():
    # Переход на новую версию
    op.create_table(
        'users',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.String(50), nullable=False),
        sa.Column('email', sa.String(100), unique=True),
    )

def downgrade():
    # Откат на предыдущую версию
    op.drop_table('users')

3. Versioning Schema (таблица версий)

БД хранит информацию о примененных миграциях:

-- Таблица миграций (создаётся автоматически)
CREATE TABLE alembic_version (
    version_num VARCHAR(32) PRIMARY KEY,
    applied_at TIMESTAMP DEFAULT NOW()
);

-- После каждой миграции добавляется запись
INSERT INTO alembic_version (version_num) VALUES ('a1b2c3d4');

-- Проверка текущей версии
SELECT version_num FROM alembic_version ORDER BY applied_at DESC LIMIT 1;

4. Версионирование по timestamp (Goose style)

# Файлы миграций с timestamp
migrations/
  ├── 20230101120000_create_users.sql
  ├── 20230102150000_add_email_index.sql
  ├── 20230105180000_create_posts.sql

# Внутренние версии (зависит от tools)
# goose версионирует по timestamp и последовательности
# Таблица: goose_db_version (migration_id, tstamp, is_applied)
-- 20230101120000_create_users.sql

-- +goose Up
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    email VARCHAR(100) UNIQUE
);

-- +goose Down
DROP TABLE users;

5. Сравнение подходов

Алембик (Alembic) — ORM-based

# Плюсы
+ Автоматическое обнаружение изменений моделей
+ Гибкость (Python код)
+ Хорошо интегрируется с SQLAlchemy

# Минусы
- Сложнее отладить сложные ситуации
- Иногда генерирует неправильный SQL
- Требует Python runtime

# Когда использовать
Оновляй модель → запусти: alembic revision --autogenerate
alembic upgrade head

Raw SQL (Goose, Flyway) — SQL-based

-- Плюсы
+ Полный контроль над SQL
+ Понятнее что происходит
+ Можно написать оптимальное изменение
+ Работает везде

-- Минусы
- Требует вручную писать up/down
- Больше кода для простых изменений
- Возможны ошибки

-- Когда использовать
Когда нужна сложная миграция или
не используешь ORM

6. Практический пример: миграция проекта

# alembic/versions/002_add_posts_table.py

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

revision = 'b2c3d4e5'
down_revision = 'a1b2c3d4'

def upgrade():
    # Создание таблицы
    op.create_table(
        'posts',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('user_id', sa.Integer, 
                  sa.ForeignKey('users.id', ondelete='CASCADE')),
        sa.Column('title', sa.String(200), nullable=False),
        sa.Column('content', sa.Text),
        sa.Column('created_at', sa.DateTime, 
                  server_default=sa.func.now()),
        sa.Column('updated_at', sa.DateTime,
                  server_default=sa.func.now(),
                  onupdate=sa.func.now()),
    )
    
    # Добавление индекса
    op.create_index('ix_posts_user_id', 'posts', ['user_id'])
    op.create_index('ix_posts_created_at', 'posts', ['created_at'])

def downgrade():
    op.drop_index('ix_posts_created_at')
    op.drop_index('ix_posts_user_id')
    op.drop_table('posts')

7. Управление миграциями

# Команды Алембика

# Создание новой миграции
alembic revision --message "add users table"
alembic revision --autogenerate -m "auto detect changes"

# Просмотр истории
alembic history
# Output:
#   <base> -> 001_initial, initial
#   001_initial -> 002_add_users, add users table
#   002_add_users -> 003_add_posts, add posts

# Применение миграций
alembic upgrade head        # До последней версии
alembic upgrade +2          # На 2 версии вперёд
alembic upgrade 003_add_posts  # На конкретную версию

# Откат
alembic downgrade -1        # На 1 версию назад
alembic downgrade base      # До базовой версии

# Проверка текущей версии
alembic current

8. Обработка конфликтов при параллельной разработке

# Проблема: два разработчика создали миграции одновременно

# Developer A создал:
# migrations/003_add_field_a.py (depends_on 002)

# Developer B создал:
# migrations/003_add_field_b.py (depends_on 002)

# Решение: переименовать и обновить зависимости
# migrations/003_add_field_a.py (depends_on 002)
# migrations/004_add_field_b.py (depends_on 003)

# Или использовать merge
alembic merge -m "merge branches" <rev_a> <rev_b>

9. Best Practices

# 1. Одна миграция = одно логическое изменение
# Плохо: изменить schema + заполнить data
# Хорошо: отдельные миграции

# 2. Называй понятно
# Плохо: 001_fix.py
# Хорошо: 001_create_users_table.py

# 3. Тестируй up и down
class TestMigration(TestCase):
    def test_upgrade(self):
        migrate.upgrade()  # Применить
        assert table_exists('users')
    
    def test_downgrade(self):
        migrate.downgrade()  # Откатить
        assert not table_exists('users')

# 4. Добавляй только необходимые constraints
op.create_table(
    'users',
    sa.Column('id', sa.Integer, primary_key=True),
    sa.Column('email', sa.String(100), nullable=False, unique=True),
    # unique=True автоматически создаёт индекс
)

# 5. Для больших таблиц используй CONCURRENTLY
op.create_index(
    'ix_large_table',
    'large_table',
    ['field'],
    postgresql_concurrently=True  # Не блокирует table
)

10. CI/CD интеграция

# Автоматический запуск миграций при деплое

# .github/workflows/deploy.yml
name: Deploy
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      # Проверка что migrations work
      - name: Test migrations
        run: |
          alembic upgrade head
          alembic downgrade base
          alembic upgrade head
      
      # Деплой
      - name: Deploy
        run: |
          git push dokku main
          # Dokku автоматически запустит миграции

11. Откаты в продакшене

# Сценарий: миграция сломала проду

# 1. Срочный откат
alembic downgrade -1  # На версию назад

# 2. Исправить миграцию
# Отредактировать файл миграции

# 3. Повторно применить
alembic upgrade head

# 4. ВАЖНО: Никогда не удаляй миграцию!
# Всегда создавай NEW миграцию для отката

# Плохо
# rm migrations/003_bad_migration.py

# Хорошо
# Создать миграцию 004_fix_from_003.py
op.alter_table(
    'users',
    sa.AlterColumn('field', new_column_name='fixed_field')
)

12. Мониторинг миграций

# Проверка примененных миграций
def get_applied_migrations(session):
    from alembic.migration import MigrationContext
    from alembic.operations import Operations
    
    context = MigrationContext.configure(session.connection())
    return context.get_current_revision()

# Логирование
import logging
logging.basicConfig()
logging.getLogger('alembic').setLevel(logging.INFO)
alembic upgrade head  # Выведет все шаги

Ключевые моменты

  • Версионирование = история всех изменений — никогда не теряй старые миграции
  • Идемпотентность — миграция должна работать, применённая 2 раза
  • Откаты = требование — всегда делай downgrade функции
  • Тестирование — проверяй both directions (up/down)
  • Документируй — комментируй нетривиальные миграции
  • Не смешивай code + data миграции — делай отдельно

Помни: good migrations = спокойный сон в продакшене!