Контекст: Миграции в backend/migrations/ используют golang-migrate. Запускаются в проде через sidecar контейнер migrate/migrate.
Миграции работают, но есть риски:
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 — это правильно.
# 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 не умеет ретраить подключение к БД.
# 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"]
# 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.
# .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
| Правило | Почему |
|---|---|
| Одна миграция = одно логическое изменение | Легче откатить, понять историю |
| Никогда не редактировать закоммиченные миграции | Нарушает воспроизводимость |
| Идемпотентность где возможно | CREATE TABLE IF NOT EXISTS, DROP INDEX IF EXISTS |
| Тестировать down миграции | В проде может понадобиться откат |
| Не делать DDL в транзакции | PostGIS, CREATE INDEX CONCURRENTLY не работают в tx |
Задача: Code review PhotoPlaces — миграции
#database #migrations #golang-migrate #docker #best-practice