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

Что произойдёт, если миграция помечена как default?

3.0 Senior🔥 201 комментариев
#DevOps и инфраструктура

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

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

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

Миграции с флагом default (Goose)

Это специфичный вопрос про инструмент миграций Goose. Флаг default имеет критическое значение для работы.

Что такое Goose

Goose — это инструмент для управления миграциями БД на raw SQL (не ORM).

# Установка
go install github.com/pressly/goose/v3/cmd/goose@latest

# Использование
goose postgres "user=... dbname=..." up
goose postgres "user=... dbname=..." down

Миграции хранятся как .sql файлы в папке migrations/.

Флаг default в миграции

-- +goose Up
-- +goose StatementBegin
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
DROP TABLE users;
-- +goose StatementEnd

Вот так выглядит базовая миграция. Но что такое флаг default?

Понимание флага default

Это про контроль версии миграций:

Goose версия в базе = 1001
Файлы миграций:
  - 0001_create_users.sql
  - 0002_add_email_column.sql
  - ...
  - 1001_add_password_field.sql
  - 1002_add_audit_log.sql (помечена как default)

Флаг default указывает на "целевую версию по умолчанию".

Использование default флага

-- миграция 1002_add_audit_log.sql
-- +goose Up
-- +goose StatementBegin
CREATE TABLE audit_log (
    id SERIAL PRIMARY KEY,
    action VARCHAR(255),
    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
DROP TABLE audit_log;
-- +goose StatementEnd

Если запустить просто goose up БЕЗ указания версии:

goose postgres "..." up
# Будут применены ВСЕ миграции ДО последней помеченной как default
# Или ВСЕ миграции если default не указана

Практический сценарий: управление версиями

Разработка:
├─ 0001_create_users.sql
├─ 0002_add_posts.sql
├─ 0003_add_comments.sql ← помечена как default для разработки
└─ 0004_experimental_feature.sql (не применяется по умолчанию)

Production:
├─ 0001_create_users.sql
├─ 0002_add_posts.sql ← помечена как default для продакшена
└─ 0003_add_comments.sql (еще не готово)

Это позволяет:

  • Разработчикам работать со всеми миграциями
  • Production использовать только стабильные
  • Избежать применения экспериментальных изменений

Синтаксис default в Goose

В Python проектах с Goose (используется raw SQL):

-- +goose Up
-- +goose StatementBegin
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255),
    price DECIMAL(10, 2)
);
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
DROP TABLE products;
-- +goose StatementEnd

По умолчанию ВСЕ миграции применяются. Но можно управлять через версию:

# Применить только до версии 0002
goose postgres "..." up 2

# Откатить до версии 0001
goose postgres "..." down to 1

# Применить все доступные
goose postgres "..." up

Схема версионирования

Goose отслеживает примененные миграции в таблице:

-- Таблица, которую Goose создает автоматически
CREATE TABLE goose_db_version (
    id SERIAL PRIMARY KEY,
    version_id BIGINT NOT NULL UNIQUE,
    is_applied BOOLEAN NOT NULL,
    tstamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- После apply миграции 0001:
INSERT INTO goose_db_version (version_id, is_applied) VALUES (1, true);

-- После apply миграции 0002:
INSERT INTO goose_db_version (version_id, is_applied) VALUES (2, true);

Взаимодействие с Python кодом (SQLAlchemy)

# В проекте с Goose и SQLAlchemy:

from sqlalchemy import create_engine, text
from alembic import command
from alembic.config import Config

class Database:
    def __init__(self, db_url):
        self.engine = create_engine(db_url)
    
    def get_current_migration_version(self):
        """Получить текущую версию миграции из goose_db_version"""
        with self.engine.connect() as conn:
            result = conn.execute(
                text("""
                SELECT version_id FROM goose_db_version 
                WHERE is_applied = true 
                ORDER BY version_id DESC 
                LIMIT 1
                """)
            )
            row = result.fetchone()
            return row[0] if row else None
    
    def create_models_in_sync_with_migrations(self):
        """SQLAlchemy модели должны соответствовать миграциям"""
        from models import Base
        # Убедитесь, что модели соответствуют последней миграции
        Base.metadata.reflect(bind=self.engine)

Практический пример: deployment процесс

#!/bin/bash
# deploy.sh

set -e

DB_URL="postgres://user:pass@host/db"
MIGRATIONS_DIR="./migrations"

echo "Current migration version:"
goose postgres "$DB_URL" status

echo "\nApplying migrations..."
# Применяем ВСЕ миграции (или до default если указана)
goose postgres "$DB_URL" up

echo "\nFinal migration version:"
goose postgres "$DB_URL" status

echo "\nDeployment complete!"

Сценарий: откат миграции

# Текущее состояние
$ goose postgres "..." status
goose: status for environment 'production'
    Applied At                  Migration
    ===============             ============
    2024-03-23 10:00:00        0001_create_users.sql
    2024-03-23 10:05:00        0002_add_posts.sql
    2024-03-23 10:10:00        0003_add_comments.sql

# Нужно откатиться на 0002
$ goose postgres "..." down to 2
goose: DOWN 0003_add_comments.sql

# Новое состояние
$ goose postgres "..." status
goose: status for environment 'production'
    Applied At                  Migration
    ===============             ============
    2024-03-23 10:00:00        0001_create_users.sql
    2024-03-23 10:05:00        0002_add_posts.sql

Integration с Python приложением

from subprocess import run
import logging

logger = logging.getLogger(__name__)

def ensure_migrations_applied(db_url: str, migrations_dir: str = "./migrations"):
    """Убедиться что все миграции применены перед запуском приложения"""
    try:
        logger.info("Checking migration status...")
        result = run(
            ["goose", "postgres", db_url, "status"],
            capture_output=True,
            text=True,
            check=True
        )
        logger.info(f"Migration status:\n{result.stdout}")
        
        # Если нужны обновления
        if "pending" in result.stdout or result.returncode != 0:
            logger.info("Applying pending migrations...")
            run(
                ["goose", "postgres", db_url, "up"],
                check=True
            )
            logger.info("Migrations applied successfully")
    except Exception as e:
        logger.error(f"Migration check failed: {e}")
        raise

# Вызывается при запуске приложения
if __name__ == "__main__":
    from config import DATABASE_URL
    ensure_migrations_applied(DATABASE_URL)
    
    # Затем запускаем приложение
    app.run()

Лучшие практики с Goose

  1. Нумеруй последовательно

    ✓ 0001_create_users.sql
    ✓ 0002_add_posts.sql
    ✓ 0003_add_comments.sql
    
    ❌ 1_create_users.sql
    ❌ 002_add_posts.sql
    ❌ 10_add_comments.sql (создает путаницу)
    
  2. Каждая миграция = одна логическая часть

    -- ✓ Правильно: одно изменение
    -- +goose Up
    CREATE TABLE users (...);
    -- +goose Down
    DROP TABLE users;
    
    -- ❌ Неправильно: много изменений
    -- +goose Up
    CREATE TABLE users (...);
    CREATE TABLE posts (...);
    CREATE TABLE comments (...);
    
  3. Всегда кода UP и DOWN блоки

    -- ✓ Правильно
    -- +goose Up
    CREATE TABLE ...
    -- +goose Down
    DROP TABLE ...
    
    -- ❌ Неправильно: нет Down
    -- +goose Up
    CREATE TABLE ...
    -- нет Down!
    
  4. Никогда не меняй примененные миграции

    ❌ Никогда не редактируй 0001_create_users.sql если она уже applied
    ✓ Создай новую миграцию 0004_fix_users_table.sql
    
  5. Тестируй откаты

    # Примени
    goose postgres "..." up
    
    # Откати
    goose postgres "..." down
    
    # Примени снова
    goose postgres "..." up
    
    # Если все работает — миграция хорошая
    

Отличие от Alembic (Python ORM)

# Alembic (для SQLAlchemy) — в Python
from alembic.operations import Operations

def upgrade():
    op.create_table(
        'users',
        sa.Column('id', sa.Integer, primary_key=True),
    )

# Goose (raw SQL) — в SQL файлах
-- migrations/0001_create_users.sql
-- +goose Up
CREATE TABLE users (id SERIAL PRIMARY KEY);

# Goose проще для raw SQL, Alembic лучше интегрируется с SQLAlchemy

Заключение

Флаг default в контексте Goose контролирует:

  • Версию БД по умолчанию
  • Какие миграции применяются автоматически
  • Целевое состояние при развертывании

Главное: Goose отслеживает примененные миграции и гарантирует их консистентность через таблицу goose_db_version.