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

459 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Анализ рисков миграции на 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 "Проверьте работу приложения"
```