458
play-life-backend/MIGRATION_RISKS_AND_SOLUTIONS.md
Normal file
458
play-life-backend/MIGRATION_RISKS_AND_SOLUTIONS.md
Normal file
@@ -0,0 +1,458 @@
|
||||
# Анализ рисков миграции на 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 "Проверьте работу приложения"
|
||||
```
|
||||
Reference in New Issue
Block a user