# Анализ рисков миграции на golang-migrate с baseline ## Критические риски ### 1. Потеря данных при неправильном применении baseline **Риск**: При применении baseline миграции на существующую БД может произойти: - Попытка создать уже существующие таблицы (ошибка) - Потеря данных при DROP/CREATE операциях - Конфликты с существующими данными **Вероятность**: Средняя **Влияние**: Критическое **Решения**: 1. **Обязательный backup перед применением** ```bash # Создать backup перед миграцией ./dump-db.sh --env-file .env baseline-migration-backup ``` 2. **Использование `migrate force` вместо `migrate up` для существующих БД** ```bash # Для существующих БД - установить версию без применения 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. **Автоматическая проверка полноты схемы** ```bash # Создать скрипт для сравнения текущей схемы с 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 на чистой БД** ```bash # Создать тестовую БД 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** ```sql -- После создания таблицы и вставки данных SELECT setval('dictionaries_id_seq', (SELECT MAX(id) FROM dictionaries), true); ``` 2. **Использование ON CONFLICT для начальных данных** ```sql INSERT INTO dictionaries (id, name) VALUES (0, 'Все слова') ON CONFLICT (id) DO NOTHING; ``` 3. **Проверка sequences после baseline** ```sql -- Скрипт для проверки всех sequences SELECT schemaname, sequencename, last_value FROM pg_sequences; ``` --- ### 4. Проблемы с materialized views **Риск**: - Materialized view может не создаться корректно - Зависимости от таблиц могут быть нарушены - Данные в MV могут быть неактуальными **Вероятность**: Средняя **Влияние**: Среднее **Решения**: 1. **Создание MV после всех таблиц** - Убедиться, что все таблицы созданы до создания MV - Использовать `DROP MATERIALIZED VIEW IF EXISTS` перед созданием 2. **Обновление данных после создания** ```sql -- После создания MV REFRESH MATERIALIZED VIEW weekly_report_mv; ``` 3. **Проверка зависимостей** ```sql -- Проверить зависимости MV SELECT * FROM pg_depend WHERE objid = 'weekly_report_mv'::regclass; ``` --- ### 5. Конфликты версий миграций **Риск**: - Таблица `schema_migrations` может быть в неправильном состоянии - Версия может быть установлена неправильно - Конфликт между старой и новой системой миграций **Вероятность**: Средняя **Влияние**: Высокое **Решения**: 1. **Проверка состояния schema_migrations перед применением** ```go // Проверить, существует ли таблица schema_migrations // Если да - проверить текущую версию var version uint err := db.QueryRow("SELECT version FROM schema_migrations LIMIT 1").Scan(&version) ``` 2. **Очистка старой таблицы (если была)** ```sql -- Если была старая таблица миграций DROP TABLE IF EXISTS old_migrations_table; ``` 3. **Использование `migrate force` только для существующих БД** - Новые БД должны использовать `migrate up` - Существующие БД - `migrate force 1` --- ### 6. Проблемы с окружениями (dev/prod различия) **Риск**: - Различия в схемах между dev и prod - Разные версии PostgreSQL - Разные настройки БД **Вероятность**: Средняя **Влияние**: Высокое **Решения**: 1. **Проверка версии PostgreSQL** ```sql 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. **Обработка ошибок миграций** ```go 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. **Таймауты для миграций** ```go // Установить таймаут для миграций ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() ``` 4. **Логирование процесса миграций** - Логировать каждую применяемую миграцию - Логировать ошибки с деталями --- ### 9. Проблемы с Docker и путями к миграциям **Риск**: - Миграции могут не найтись в контейнере - Неправильные пути к файлам миграций - Проблемы с правами доступа **Вероятность**: Низкая **Влияние**: Среднее **Решения**: 1. **Проверка путей в Dockerfile** ```dockerfile # Убедиться, что миграции копируются COPY play-life-backend/migrations /migrations ``` 2. **Использование абсолютных путей** ```go migrationsPath := "/migrations" if _, err := os.Stat(migrationsPath); os.IsNotExist(err) { // Fallback для локальной разработки migrationsPath = "play-life-backend/migrations" } ``` 3. **Проверка доступности миграций при старте** ```go // Проверить, что папка миграций существует 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. **Проверка версии перед применением** ```go 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) - [ ] План отката подготовлен (если что-то пойдет не так) --- ## Скрипты для проверки ### Скрипт проверки схемы ```bash #!/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 ```bash #!/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 "Проверьте работу приложения" ```