← Назад к вопросам
Как реализовано версионирование в БД?
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 = спокойный сон в продакшене!