Files
play-life/play-life-backend/MIGRATION_RISKS_AND_SOLUTIONS.md
poignatov 90643c504a
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s
4.0.0: Исправлена обработка старых дампов
2026-01-25 16:41:50 +03:00

16 KiB
Raw Blame History

Анализ рисков миграции на golang-migrate с baseline

Критические риски

1. Потеря данных при неправильном применении baseline

Риск: При применении baseline миграции на существующую БД может произойти:

  • Попытка создать уже существующие таблицы (ошибка)
  • Потеря данных при DROP/CREATE операциях
  • Конфликты с существующими данными

Вероятность: Средняя
Влияние: Критическое

Решения:

  1. Обязательный backup перед применением

    # Создать backup перед миграцией
    ./dump-db.sh --env-file .env baseline-migration-backup
    
  2. Использование migrate force вместо migrate up для существующих БД

    # Для существующих БД - установить версию без применения
    migrate -path ./migrations -database "postgres://..." force 1
    
  3. Проверка существования таблиц в baseline миграции

    • Использовать CREATE TABLE IF NOT EXISTS (но это не идеально для baseline)
    • Или создать скрипт проверки перед применением
  4. Тестирование на dev окружении

    • Сначала применить на dev БД
    • Проверить целостность данных
    • Только потом применять на production

2. Ошибки в baseline миграции (неполная схема)

Риск: Baseline миграция может не включать:

  • Некоторые таблицы или колонки
  • Индексы или constraints
  • Materialized views
  • Начальные данные (словарь с id=0)
  • Sequences с правильными значениями

Вероятность: Высокая
Влияние: Критическое

Решения:

  1. Автоматическая проверка полноты схемы

    # Создать скрипт для сравнения текущей схемы с baseline
    # Использовать pg_dump --schema-only для сравнения
    pg_dump --schema-only -h $DB_HOST -U $DB_USER -d $DB_NAME > current_schema.sql
    # Сравнить с baseline миграцией
    
  2. Пошаговая сборка baseline

    • Собрать схему из всех init*DB функций
    • Добавить все миграции 012-029
    • Проверить через pg_dump --schema-only на актуальной БД
  3. Тестирование baseline на чистой БД

    # Создать тестовую БД
    createdb test_baseline
    # Применить baseline
    migrate -path ./migrations -database "postgres://.../test_baseline" up
    # Сравнить схему с production
    
  4. Валидация через SQL проверки

    • Добавить в baseline проверки существования всех таблиц
    • Использовать DO $$ BEGIN ... END $$; блоки для валидации

3. Проблемы с sequences и начальными данными

Риск:

  • Sequences могут быть не синхронизированы
  • Начальные данные (словарь id=0) могут конфликтовать
  • Автоинкременты могут начаться с неправильного значения

Вероятность: Средняя
Влияние: Среднее

Решения:

  1. Правильная настройка sequences в baseline

    -- После создания таблицы и вставки данных
    SELECT setval('dictionaries_id_seq', 
                  (SELECT MAX(id) FROM dictionaries), 
                  true);
    
  2. Использование ON CONFLICT для начальных данных

    INSERT INTO dictionaries (id, name) 
    VALUES (0, 'Все слова')
    ON CONFLICT (id) DO NOTHING;
    
  3. Проверка sequences после baseline

    -- Скрипт для проверки всех sequences
    SELECT schemaname, sequencename, last_value 
    FROM pg_sequences;
    

4. Проблемы с materialized views

Риск:

  • Materialized view может не создаться корректно
  • Зависимости от таблиц могут быть нарушены
  • Данные в MV могут быть неактуальными

Вероятность: Средняя
Влияние: Среднее

Решения:

  1. Создание MV после всех таблиц

    • Убедиться, что все таблицы созданы до создания MV
    • Использовать DROP MATERIALIZED VIEW IF EXISTS перед созданием
  2. Обновление данных после создания

    -- После создания MV
    REFRESH MATERIALIZED VIEW weekly_report_mv;
    
  3. Проверка зависимостей

    -- Проверить зависимости MV
    SELECT * FROM pg_depend 
    WHERE objid = 'weekly_report_mv'::regclass;
    

5. Конфликты версий миграций

Риск:

  • Таблица schema_migrations может быть в неправильном состоянии
  • Версия может быть установлена неправильно
  • Конфликт между старой и новой системой миграций

Вероятность: Средняя
Влияние: Высокое

Решения:

  1. Проверка состояния schema_migrations перед применением

    // Проверить, существует ли таблица schema_migrations
    // Если да - проверить текущую версию
    var version uint
    err := db.QueryRow("SELECT version FROM schema_migrations LIMIT 1").Scan(&version)
    
  2. Очистка старой таблицы (если была)

    -- Если была старая таблица миграций
    DROP TABLE IF EXISTS old_migrations_table;
    
  3. Использование migrate force только для существующих БД

    • Новые БД должны использовать migrate up
    • Существующие БД - migrate force 1

6. Проблемы с окружениями (dev/prod различия)

Риск:

  • Различия в схемах между dev и prod
  • Разные версии PostgreSQL
  • Разные настройки БД

Вероятность: Средняя
Влияние: Высокое

Решения:

  1. Проверка версии PostgreSQL

    SELECT version();
    
  2. Тестирование на всех окружениях

    • Dev окружение
    • Staging (если есть)
    • Production (после успешного тестирования)
  3. Документирование различий

    • Зафиксировать версию PostgreSQL
    • Зафиксировать настройки БД

7. Проблемы с откатом (rollback)

Риск:

  • Baseline миграция не может быть откачена
  • Ошибки при откате последующих миграций
  • Потеря данных при откате

Вероятность: Низкая
Влияние: Высокое

Решения:

  1. Baseline не откатывается (по дизайну)

    • Пустой 000001_baseline.down.sql
    • Документировать это ограничение
  2. Правильные down миграции для новых миграций

    • Каждая новая миграция должна иметь корректный down файл
    • Тестировать откат на dev окружении
  3. Backup перед откатом

    • Всегда создавать backup перед откатом
    • Особенно на production

8. Проблемы при старте приложения

Риск:

  • Миграции могут не примениться при старте
  • Ошибки подключения к БД во время миграций
  • Таймауты при применении миграций

Вероятность: Средняя
Влияние: Высокое

Решения:

  1. Обработка ошибок миграций

    m, err := migrate.NewWithDatabaseInstance(
        "file://migrations",
        "postgres", driver)
    if err != nil {
        log.Fatal("Failed to initialize migrations:", err)
    }
    
    if err := m.Up(); err != nil {
        if err != migrate.ErrNoChange {
            log.Fatal("Failed to apply migrations:", err)
        }
        log.Println("Database is up to date")
    }
    
  2. Retry логика для подключения к БД

    • Уже есть в коде (10 попыток)
    • Применить перед миграциями
  3. Таймауты для миграций

    // Установить таймаут для миграций
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
    defer cancel()
    
  4. Логирование процесса миграций

    • Логировать каждую применяемую миграцию
    • Логировать ошибки с деталями

9. Проблемы с Docker и путями к миграциям

Риск:

  • Миграции могут не найтись в контейнере
  • Неправильные пути к файлам миграций
  • Проблемы с правами доступа

Вероятность: Низкая
Влияние: Среднее

Решения:

  1. Проверка путей в Dockerfile

    # Убедиться, что миграции копируются
    COPY play-life-backend/migrations /migrations
    
  2. Использование абсолютных путей

    migrationsPath := "/migrations"
    if _, err := os.Stat(migrationsPath); os.IsNotExist(err) {
        // Fallback для локальной разработки
        migrationsPath = "play-life-backend/migrations"
    }
    
  3. Проверка доступности миграций при старте

    // Проверить, что папка миграций существует
    if _, err := os.Stat(migrationsPath); os.IsNotExist(err) {
        log.Fatal("Migrations directory not found:", migrationsPath)
    }
    

10. Проблемы с параллельным доступом

Риск:

  • Несколько инстансов приложения могут пытаться применить миграции одновременно
  • Конфликты при применении миграций

Вероятность: Низкая
Влияние: Высокое

Решения:

  1. Блокировки на уровне БД

    • golang-migrate использует транзакции
    • PostgreSQL блокирует таблицу schema_migrations
  2. Применение миграций только в одном инстансе

    • Использовать флаг --migrate для запуска миграций
    • Или применять миграции отдельным процессом
  3. Проверка версии перед применением

    version, dirty, err := m.Version()
    if dirty {
        log.Fatal("Database is in dirty state, manual intervention required")
    }
    

План митигации рисков

Этап 1: Подготовка (до применения baseline)

  1. Создать backup всех БД (dev, staging, prod)
  2. Собрать полную схему через pg_dump --schema-only
  3. Создать baseline миграцию на основе схемы
  4. Протестировать baseline на чистой БД
  5. Сравнить схему после baseline с текущей схемой

Этап 2: Тестирование (на dev окружении)

  1. Применить baseline через migrate force 1
  2. Проверить целостность данных
  3. Проверить работу приложения
  4. Проверить sequences и начальные данные
  5. Проверить materialized views

Этап 3: Применение (на production)

  1. Создать backup production БД
  2. Применить baseline через migrate force 1
  3. Проверить работу приложения
  4. Мониторинг в течение первых часов

Этап 4: Мониторинг (после применения)

  1. Проверить логи приложения
  2. Проверить ошибки БД
  3. Проверить производительность
  4. Собрать обратную связь от пользователей

Чеклист перед применением baseline

  • Backup всех БД создан и проверен
  • Baseline миграция протестирована на чистой БД
  • Схема после baseline совпадает с текущей схемой
  • Тестирование на dev окружении успешно
  • Инструкции по применению baseline готовы
  • Команда проинформирована о миграции
  • Окно для миграции запланировано (для production)
  • План отката подготовлен (если что-то пойдет не так)

Скрипты для проверки

Скрипт проверки схемы

#!/bin/bash
# check_schema.sh - Проверка полноты baseline миграции

DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-5432}
DB_USER=${DB_USER:-playeng}
DB_PASSWORD=${DB_PASSWORD:-playeng}
DB_NAME=${DB_NAME:-playeng}

echo "Проверка схемы БД..."

# Получить список всех таблиц
PGPASSWORD="$DB_PASSWORD" psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -t -c "
SELECT tablename 
FROM pg_tables 
WHERE schemaname = 'public'
ORDER BY tablename;
" > current_tables.txt

echo "Таблицы в БД сохранены в current_tables.txt"
echo "Сравните с таблицами в baseline миграции"

Скрипт применения baseline

#!/bin/bash
# apply_baseline.sh - Безопасное применение baseline

set -e

DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-5432}
DB_USER=${DB_USER:-playeng}
DB_PASSWORD=${DB_PASSWORD:-playeng}
DB_NAME=${DB_NAME:-playeng}

DATABASE_URL="postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$DB_NAME?sslmode=disable"

echo "⚠️  ВНИМАНИЕ: Это применит baseline миграцию!"
echo "База данных: $DB_NAME"
echo "Хост: $DB_HOST:$DB_PORT"
read -p "Вы уверены? (yes/no): " confirm

if [ "$confirm" != "yes" ]; then
    echo "Отменено"
    exit 1
fi

# Создать backup
echo "Создание backup..."
./dump-db.sh --env-file .env baseline-backup-$(date +%Y%m%d_%H%M%S)

# Применить baseline
echo "Применение baseline..."
migrate -path ./play-life-backend/migrations -database "$DATABASE_URL" force 1

echo "✅ Baseline применен успешно"
echo "Проверьте работу приложения"