# Database: Миграции и Schema Management **Контекст**: Миграции в `backend/migrations/` используют `golang-migrate`. Запускаются в проде через sidecar контейнер `migrate/migrate`. ## Суть **Миграции работают, но есть риски**: 1. Sidecar паттерн — нет гарантии порядка запуска перед backend 2. Нет rollback стратегии в проде 3. Нет проверки миграций в CI 4. Down-миграции могут быть опасны в проде ## Текущая схема (хорошо) ``` backend/migrations/ 000001_create_extensions.up.sql -- pgcrypto, postgis 000002_create_users.up.sql -- users, refresh_tokens 000003_create_tags_features.up.sql -- справочники 000004_create_places.up.sql -- places, images, tags, features 000005_create_services_reviews_bookings.up.sql 000006_create_moderation_payments_visitors.up.sql ``` Каждая миграция имеет `.down.sql` — это правильно. ## Проблема: Порядок запуска в docker-compose.prod.yml ```yaml # deploy/docker-compose.prod.yml:48-57 migrations: image: migrate/migrate:v4.17 command: - -path=/migrations - -database "postgres://photoplaces:${DB_PASSWORD}@postgres:5432/photoplaces?sslmode=disable" - up depends_on: postgres: condition: service_healthy backend: depends_on: migrations: condition: service_completed_successfully -- Хорошо, но... ``` **Риск**: Если миграция падает, backend не стартует (хорошо), но нет retry логики. `migrate/migrate` не умеет ретраить подключение к БД. ## Решение: Entrypoint скрипт с retry ```dockerfile # backend/Dockerfile — добавить entrypoint FROM golang:1.22-alpine AS builder # ... build stages ... FROM alpine:3.19 RUN apk add --no-cache ca-certificates tzdata postgresql-client COPY --from=builder /photoplaces-api /photoplaces-api COPY --from=builder /app/migrations /migrations COPY docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh EXPOSE 8080 ENTRYPOINT ["/docker-entrypoint.sh"] CMD ["/photoplaces-api"] ``` ```bash # docker-entrypoint.sh #!/bin/sh set -e # Ждём БД с таймаутом echo "Waiting for database..." for i in $(seq 1 30); do if pg_isready -h postgres -U photoplaces -q; then break fi sleep 2 done # Накатываем миграции echo "Running migrations..." migrate -path /migrations -database "$DATABASE_URL" up # Стартуем приложение exec /photoplaces-api ``` **Плюсы**: Миграции накатываются в том же контейнере, гарантированно перед стартом, с retry. ## CI/CD: Проверка миграций ```yaml # .github/workflows/ci.yml - name: Test migrations run: | docker compose -f docker-compose.yml up -d postgres sleep 5 docker run --rm --network host \ -v $(pwd)/backend/migrations:/migrations \ migrate/migrate:v4.17 \ -path=/migrations \ -database "postgres://photoplaces:photoplaces_dev@localhost:5432/photoplaces?sslmode=disable" \ up # Тест down/up migrate ... down 1 migrate ... up ``` ## Best Practices для миграций | Правило | Почему | |---------|--------| | **Одна миграция = одно логическое изменение** | Легче откатить, понять историю | | **Никогда не редактировать закоммиченные миграции** | Нарушает воспроизводимость | | **Идемпотентность где возможно** | `CREATE TABLE IF NOT EXISTS`, `DROP INDEX IF EXISTS` | | **Тестировать down миграции** | В проде может понадобиться откат | | **Не делать DDL в транзакции** | PostGIS, CREATE INDEX CONCURRENTLY не работают в tx | ## Связанные заметки - [[deploy-production-readiness]] — entrypoint вместо sidecar - [[architecture-overview]] ## Источник Задача: Code review PhotoPlaces — миграции ## Теги #database #migrations #golang-migrate #docker #best-practice