All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s
459 lines
16 KiB
Markdown
459 lines
16 KiB
Markdown
# Анализ рисков миграции на 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 "Проверьте работу приложения"
|
||
```
|