- Add comprehensive health check system with multiple endpoints - Add Prometheus metrics endpoint - Add production logging configurations (5 strategies) - Add complete deployment documentation suite: * QUICKSTART.md - 30-minute deployment guide * DEPLOYMENT_CHECKLIST.md - Printable verification checklist * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference * production-logging.md - Logging configuration guide * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation * README.md - Navigation hub * DEPLOYMENT_SUMMARY.md - Executive summary - Add deployment scripts and automation - Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment - Update README with production-ready features All production infrastructure is now complete and ready for deployment.
776 lines
20 KiB
Markdown
776 lines
20 KiB
Markdown
# Production Database Migration Strategy
|
||
|
||
Sichere und zuverlässige Database Migration Strategies für Production Deployment des Custom PHP Frameworks.
|
||
|
||
## Migration System Overview
|
||
|
||
Das Framework nutzt ein **Safe Rollback Architecture** System:
|
||
|
||
```
|
||
Migration Interface (Forward-Only)
|
||
↓
|
||
└─→ SafelyReversible Interface (Optional - nur bei safe rollback)
|
||
↓
|
||
└─→ MigrationRunner
|
||
↓
|
||
├─→ Apply (up)
|
||
└─→ Rollback (down) - nur wenn SafelyReversible
|
||
```
|
||
|
||
**Core Principle**: Migrations sind **forward-only by default**. Rollback nur wenn SICHER (no data loss).
|
||
|
||
## Safe vs Unsafe Migrations
|
||
|
||
### ✅ Safe for Rollback (implement SafelyReversible)
|
||
|
||
Diese Migrations können sicher rückgängig gemacht werden:
|
||
|
||
- **Creating new tables** (can be dropped without data loss)
|
||
- **Adding nullable columns** (can be removed)
|
||
- **Creating/dropping indexes** (no data affected)
|
||
- **Renaming columns** (data preserved)
|
||
- **Adding/removing foreign keys** (constraints only)
|
||
- **Adding CHECK constraints** (can be removed)
|
||
- **Creating empty tables** (no data to lose)
|
||
|
||
### ❌ Unsafe for Rollback (only Migration interface)
|
||
|
||
Diese Migrations können NICHT sicher zurückgerollt werden:
|
||
|
||
- **Dropping columns with data** (data is LOST)
|
||
- **Transforming data formats** (original format lost)
|
||
- **Changing column types** (data loss risk)
|
||
- **Merging/splitting tables** (data restructured)
|
||
- **Deleting data** (information cannot be restored)
|
||
- **Complex data migrations** (multiple steps, state changes)
|
||
|
||
## Migration Implementation
|
||
|
||
### 1. Safe Migration Example
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Domain\User\Migrations;
|
||
|
||
use App\Framework\Database\Migration\{Migration, SafelyReversible};
|
||
use App\Framework\Database\ConnectionInterface;
|
||
use App\Framework\Database\Schema\{Blueprint, Schema};
|
||
use App\Framework\Database\ValueObjects\{TableName, ColumnName, IndexName};
|
||
use App\Framework\Database\Migration\ValueObjects\MigrationVersion;
|
||
|
||
/**
|
||
* Create user_profiles table
|
||
*
|
||
* SAFE: New table can be dropped without data loss
|
||
*/
|
||
final readonly class CreateUserProfilesTable implements Migration, SafelyReversible
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
|
||
$schema->create(TableName::fromString('user_profiles'), function (Blueprint $table) {
|
||
// Primary Key
|
||
$table->string(ColumnName::fromString('ulid'), 26)->primary();
|
||
|
||
// Foreign Key to users
|
||
$table->string(ColumnName::fromString('user_id'), 26);
|
||
|
||
// Profile Data (nullable - safe to drop)
|
||
$table->string(ColumnName::fromString('bio'))->nullable();
|
||
$table->string(ColumnName::fromString('avatar_url'))->nullable();
|
||
$table->string(ColumnName::fromString('website'))->nullable();
|
||
|
||
// Timestamps
|
||
$table->timestamps();
|
||
|
||
// Foreign Key Constraint
|
||
$table->foreign(ColumnName::fromString('user_id'))
|
||
->references(ColumnName::fromString('ulid'))
|
||
->on(TableName::fromString('users'))
|
||
->onDelete(ForeignKeyAction::CASCADE);
|
||
|
||
// Index
|
||
$table->index(
|
||
ColumnName::fromString('user_id'),
|
||
IndexName::fromString('idx_user_profiles_user_id')
|
||
);
|
||
});
|
||
|
||
$schema->execute();
|
||
}
|
||
|
||
/**
|
||
* Rollback is SAFE because:
|
||
* - Table is new (no existing data to lose)
|
||
* - If table has data, it's from testing/staging only
|
||
* - Production: Use fix-forward migration instead
|
||
*/
|
||
public function down(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
$schema->dropIfExists(TableName::fromString('user_profiles'));
|
||
$schema->execute();
|
||
}
|
||
|
||
public function getVersion(): MigrationVersion
|
||
{
|
||
return MigrationVersion::fromString('2024_01_15_143000');
|
||
}
|
||
|
||
public function getDescription(): string
|
||
{
|
||
return 'Create user_profiles table';
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. Unsafe Migration Example
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Domain\User\Migrations;
|
||
|
||
use App\Framework\Database\Migration\Migration;
|
||
use App\Framework\Database\ConnectionInterface;
|
||
use App\Framework\Database\Schema\{Blueprint, Schema};
|
||
use App\Framework\Database\Migration\ValueObjects\MigrationVersion;
|
||
|
||
/**
|
||
* Remove deprecated legacy_id column
|
||
*
|
||
* UNSAFE: Dropping column with data - NOT reversible
|
||
*/
|
||
final readonly class RemoveLegacyIdColumn implements Migration
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
|
||
$schema->table('users', function (Blueprint $table) {
|
||
// Data is LOST after this operation!
|
||
$table->dropColumn('legacy_id');
|
||
});
|
||
|
||
$schema->execute();
|
||
}
|
||
|
||
// NO down() method - data cannot be recovered
|
||
// Use fix-forward migration if needed
|
||
|
||
public function getVersion(): MigrationVersion
|
||
{
|
||
return MigrationVersion::fromString('2024_01_15_150000');
|
||
}
|
||
|
||
public function getDescription(): string
|
||
{
|
||
return 'Remove deprecated legacy_id column from users table';
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. Fix-Forward Migration Example
|
||
|
||
Statt unsicheren Rollback: Neue Forward-Migration erstellen.
|
||
|
||
```php
|
||
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
/**
|
||
* Restore accidentally removed deprecated_field
|
||
*
|
||
* Fix-Forward Strategy: Create new migration to undo changes
|
||
*/
|
||
final readonly class RestoreDeprecatedField implements Migration, SafelyReversible
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
|
||
$schema->table('users', function (Blueprint $table) {
|
||
// Restore column (data cannot be recovered, but column structure can)
|
||
$table->string('deprecated_field')->nullable();
|
||
});
|
||
|
||
$schema->execute();
|
||
}
|
||
|
||
/**
|
||
* SAFE: Column is empty after restoration, can be dropped
|
||
*/
|
||
public function down(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->dropColumn('deprecated_field');
|
||
});
|
||
|
||
$schema->execute();
|
||
}
|
||
|
||
public function getVersion(): MigrationVersion
|
||
{
|
||
return MigrationVersion::fromString('2024_01_15_160000');
|
||
}
|
||
|
||
public function getDescription(): string
|
||
{
|
||
return 'Restore deprecated_field column to users table';
|
||
}
|
||
}
|
||
```
|
||
|
||
## Migration Commands
|
||
|
||
### Console Commands
|
||
|
||
```bash
|
||
# Create new migration
|
||
php console.php make:migration CreateUsersTable [Domain]
|
||
|
||
# Run all pending migrations
|
||
php console.php db:migrate
|
||
|
||
# Check migration status
|
||
php console.php db:status
|
||
|
||
# Rollback last migration (only if SafelyReversible)
|
||
php console.php db:rollback [steps]
|
||
|
||
# Test migration (dry-run)
|
||
php console.php db:migrate --dry-run
|
||
|
||
# Force migration (skip confirmation)
|
||
php console.php db:migrate --force
|
||
```
|
||
|
||
### Production Migration Workflow
|
||
|
||
```bash
|
||
# 1. Backup database before migration
|
||
./scripts/backup-database.sh
|
||
|
||
# 2. Check migration status
|
||
docker exec php php console.php db:status
|
||
|
||
# 3. Test migration (dry-run)
|
||
docker exec php php console.php db:migrate --dry-run
|
||
|
||
# 4. Apply migrations
|
||
docker exec php php console.php db:migrate
|
||
|
||
# 5. Verify migration success
|
||
docker exec php php console.php db:status
|
||
|
||
# 6. Run application health check
|
||
curl -f https://your-domain.com/health || exit 1
|
||
```
|
||
|
||
## Migration Rollback Safety Check
|
||
|
||
Der `MigrationRunner` prüft automatisch, ob eine Migration sicher rollbar ist:
|
||
|
||
```bash
|
||
$ php console.php db:rollback 1
|
||
|
||
🔄 Rolling back migrations...
|
||
⚠️ Safety Check: Only migrations implementing SafelyReversible will be rolled back.
|
||
|
||
❌ Rollback failed: Migration 2024_01_15_150000 does not support safe rollback
|
||
|
||
ℹ️ This migration cannot be safely rolled back.
|
||
Reason: Data loss would occur during rollback.
|
||
|
||
💡 Recommendation:
|
||
Create a new forward migration to undo the changes instead:
|
||
php console.php make:migration FixYourChanges
|
||
|
||
📖 See docs/deployment/database-migration-strategy.md for guidelines.
|
||
```
|
||
|
||
## Production Migration Best Practices
|
||
|
||
### 1. Pre-Migration Checklist
|
||
|
||
- [ ] **Test in Staging**: Run migration in staging environment first
|
||
- [ ] **Backup Database**: Create full database backup before migration
|
||
- [ ] **Review SQL**: Inspect generated SQL for correctness
|
||
- [ ] **Check Dependencies**: Verify migration dependencies are applied
|
||
- [ ] **Monitor Resources**: Ensure sufficient disk space and memory
|
||
- [ ] **Schedule Maintenance**: Plan migration during low-traffic window
|
||
- [ ] **Prepare Rollback**: Have rollback plan ready (fix-forward migration)
|
||
- [ ] **Team Notification**: Inform team of deployment window
|
||
- [ ] **Health Checks**: Verify health check endpoints before migration
|
||
|
||
### 2. Migration Execution Strategy
|
||
|
||
**Option A: Zero-Downtime Migration** (Recommended)
|
||
|
||
```bash
|
||
# 1. Deploy new code (migration not yet applied)
|
||
./scripts/deploy-production.sh --skip-migrations
|
||
|
||
# 2. Verify application works with old schema
|
||
curl -f https://your-domain.com/health
|
||
|
||
# 3. Apply backward-compatible migration
|
||
docker exec php php console.php db:migrate
|
||
|
||
# 4. Verify application works with new schema
|
||
curl -f https://your-domain.com/health
|
||
|
||
# 5. Complete deployment
|
||
```
|
||
|
||
**Option B: Maintenance Window Migration**
|
||
|
||
```bash
|
||
# 1. Enable maintenance mode
|
||
./scripts/maintenance-mode.sh enable
|
||
|
||
# 2. Backup database
|
||
./scripts/backup-database.sh
|
||
|
||
# 3. Apply migrations
|
||
docker exec php php console.php db:migrate
|
||
|
||
# 4. Verify health
|
||
curl -f https://your-domain.com/health
|
||
|
||
# 5. Disable maintenance mode
|
||
./scripts/maintenance-mode.sh disable
|
||
```
|
||
|
||
### 3. Backward-Compatible Migrations
|
||
|
||
**Guidelines for Zero-Downtime**:
|
||
|
||
✅ **Safe Operations**:
|
||
- Adding nullable columns
|
||
- Creating new tables
|
||
- Adding indexes (CONCURRENTLY in PostgreSQL)
|
||
- Renaming columns (in two-step process)
|
||
|
||
❌ **Unsafe Operations**:
|
||
- Dropping columns (old code will fail)
|
||
- Renaming columns (one-step process)
|
||
- Changing column types (old code may fail)
|
||
- Adding NOT NULL columns (without default)
|
||
|
||
**Two-Step Column Rename Example**:
|
||
|
||
```php
|
||
// Step 1: Add new column (nullable)
|
||
final readonly class AddNewEmailColumn implements Migration, SafelyReversible
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->string('email_address')->nullable();
|
||
});
|
||
$schema->execute();
|
||
|
||
// Copy data from old column
|
||
$connection->execute("UPDATE users SET email_address = email WHERE email_address IS NULL");
|
||
}
|
||
}
|
||
|
||
// Deploy new code (uses email_address, falls back to email)
|
||
|
||
// Step 2: Drop old column (after code deployed)
|
||
final readonly class DropOldEmailColumn implements Migration
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->dropColumn('email');
|
||
});
|
||
$schema->execute();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. Performance Considerations
|
||
|
||
**Long-Running Migrations**:
|
||
```php
|
||
// ❌ Blocking operation (can timeout)
|
||
$table->addIndex(['email']); // Locks table during index creation
|
||
|
||
// ✅ Non-blocking operation (PostgreSQL)
|
||
$connection->execute("CREATE INDEX CONCURRENTLY idx_users_email ON users (email)");
|
||
```
|
||
|
||
**Large Table Migrations**:
|
||
```php
|
||
// ❌ Single UPDATE for millions of rows
|
||
$connection->execute("UPDATE users SET status = 'active' WHERE status IS NULL");
|
||
|
||
// ✅ Batch processing
|
||
$batchSize = 10000;
|
||
$offset = 0;
|
||
|
||
do {
|
||
$affected = $connection->execute(
|
||
"UPDATE users SET status = 'active'
|
||
WHERE status IS NULL
|
||
AND ulid IN (
|
||
SELECT ulid FROM users WHERE status IS NULL LIMIT ? OFFSET ?
|
||
)",
|
||
[$batchSize, $offset]
|
||
);
|
||
$offset += $batchSize;
|
||
|
||
// Small delay to reduce load
|
||
usleep(100000); // 100ms
|
||
} while ($affected > 0);
|
||
```
|
||
|
||
### 5. Data Migration Patterns
|
||
|
||
**Option 1: In-Migration Data Transform**:
|
||
```php
|
||
final readonly class MigrateUserRoles implements Migration
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
// Schema change
|
||
$schema = new Schema($connection);
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->string('role')->default('user');
|
||
});
|
||
$schema->execute();
|
||
|
||
// Data migration
|
||
$connection->execute("
|
||
UPDATE users
|
||
SET role = CASE
|
||
WHEN is_admin THEN 'admin'
|
||
WHEN is_moderator THEN 'moderator'
|
||
ELSE 'user'
|
||
END
|
||
");
|
||
|
||
// Drop old columns
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->dropColumn('is_admin', 'is_moderator');
|
||
});
|
||
$schema->execute();
|
||
}
|
||
}
|
||
```
|
||
|
||
**Option 2: Separate Data Migration Script**:
|
||
```php
|
||
// Migration: Schema only
|
||
final readonly class AddRoleColumn implements Migration, SafelyReversible
|
||
{
|
||
public function up(ConnectionInterface $connection): void
|
||
{
|
||
$schema = new Schema($connection);
|
||
$schema->table('users', function (Blueprint $table) {
|
||
$table->string('role')->nullable();
|
||
});
|
||
$schema->execute();
|
||
}
|
||
}
|
||
|
||
// Separate script: Data migration
|
||
// scripts/data-migrations/migrate-user-roles.php
|
||
$repository = $container->get(UserRepository::class);
|
||
$users = $repository->findAll();
|
||
|
||
foreach ($users as $user) {
|
||
$role = $user->isAdmin() ? 'admin'
|
||
: ($user->isModerator() ? 'moderator' : 'user');
|
||
$user->setRole($role);
|
||
$repository->save($user);
|
||
}
|
||
```
|
||
|
||
## Migration Testing
|
||
|
||
### 1. Local Testing
|
||
|
||
```bash
|
||
# Reset database and re-run all migrations
|
||
php console.php db:fresh
|
||
|
||
# Run specific migration
|
||
php console.php db:migrate --step=1
|
||
|
||
# Test rollback (if SafelyReversible)
|
||
php console.php db:rollback --step=1
|
||
|
||
# Re-apply migration
|
||
php console.php db:migrate --step=1
|
||
```
|
||
|
||
### 2. Staging Environment Testing
|
||
|
||
```bash
|
||
# Copy production data to staging (anonymized)
|
||
./scripts/anonymize-production-data.sh
|
||
|
||
# Apply migrations in staging
|
||
ssh staging "cd /app && php console.php db:migrate"
|
||
|
||
# Run integration tests
|
||
ssh staging "cd /app && ./vendor/bin/pest --testsuite=Integration"
|
||
|
||
# Verify application health
|
||
curl -f https://staging.your-domain.com/health
|
||
```
|
||
|
||
### 3. Migration Test Checklist
|
||
|
||
- [ ] **Migration applies successfully** (no errors)
|
||
- [ ] **Schema matches expectations** (inspect database)
|
||
- [ ] **Data integrity preserved** (no data loss)
|
||
- [ ] **Application health checks pass** (all endpoints work)
|
||
- [ ] **Performance acceptable** (migration completes in reasonable time)
|
||
- [ ] **Rollback works** (if SafelyReversible)
|
||
- [ ] **Idempotency verified** (can run migration multiple times)
|
||
- [ ] **Foreign key constraints intact** (referential integrity)
|
||
- [ ] **Indexes created properly** (query performance maintained)
|
||
|
||
## Monitoring & Validation
|
||
|
||
### Post-Migration Health Checks
|
||
|
||
```bash
|
||
# 1. Database connection test
|
||
docker exec php php console.php db:test-connection
|
||
|
||
# 2. Table integrity check
|
||
docker exec php php console.php db:check-integrity
|
||
|
||
# 3. Application health check
|
||
curl -f https://your-domain.com/health
|
||
|
||
# 4. Check for errors in logs
|
||
docker-compose logs php | grep -i error
|
||
|
||
# 5. Verify data consistency
|
||
docker exec php php console.php db:verify-data
|
||
```
|
||
|
||
### Migration Metrics
|
||
|
||
**Track These Metrics**:
|
||
- Migration execution time
|
||
- Database size before/after
|
||
- Number of affected rows
|
||
- Query performance (slow query log)
|
||
- Error rate (application logs)
|
||
- Health check failures
|
||
|
||
**Example Monitoring**:
|
||
```php
|
||
// Log migration metrics
|
||
$startTime = microtime(true);
|
||
$sizeBeforeMB = $this->getDatabaseSize();
|
||
|
||
$migration->up($connection);
|
||
|
||
$executionTimeMs = (microtime(true) - $startTime) * 1000;
|
||
$sizeAfterMB = $this->getDatabaseSize();
|
||
|
||
$this->logger->info('Migration completed', [
|
||
'migration' => $migration->getVersion()->value,
|
||
'execution_time_ms' => $executionTimeMs,
|
||
'size_before_mb' => $sizeBeforeMB,
|
||
'size_after_mb' => $sizeAfterMB,
|
||
'size_delta_mb' => $sizeAfterMB - $sizeBeforeMB,
|
||
]);
|
||
```
|
||
|
||
## Rollback Strategy
|
||
|
||
### When to Rollback vs Fix-Forward
|
||
|
||
**Rollback (if SafelyReversible)**:
|
||
- Migration applied in last 5 minutes
|
||
- No production traffic affected yet
|
||
- Schema change only (no data migration)
|
||
- Quick fix available
|
||
|
||
**Fix-Forward (Recommended for Production)**:
|
||
- Migration applied > 5 minutes ago
|
||
- Production traffic affected
|
||
- Data migrations applied
|
||
- Complex schema changes
|
||
- Uncertain about data loss
|
||
|
||
### Rollback Execution
|
||
|
||
```bash
|
||
# Check if migration supports rollback
|
||
docker exec php php console.php db:status
|
||
|
||
# Rollback last migration (if safe)
|
||
docker exec php php console.php db:rollback --step=1
|
||
|
||
# Verify rollback success
|
||
docker exec php php console.php db:status
|
||
|
||
# Check application health
|
||
curl -f https://your-domain.com/health
|
||
```
|
||
|
||
### Fix-Forward Execution
|
||
|
||
```bash
|
||
# 1. Create fix-forward migration
|
||
docker exec php php console.php make:migration FixBrokenMigration
|
||
|
||
# 2. Implement fix in new migration
|
||
# Edit: src/.../Migrations/2024_XX_XX_XXXXXX_FixBrokenMigration.php
|
||
|
||
# 3. Test in staging
|
||
ssh staging "cd /app && php console.php db:migrate"
|
||
|
||
# 4. Apply in production
|
||
docker exec php php console.php db:migrate
|
||
|
||
# 5. Verify fix
|
||
curl -f https://your-domain.com/health
|
||
```
|
||
|
||
## Disaster Recovery
|
||
|
||
### Database Backup Before Migration
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# scripts/backup-database.sh
|
||
|
||
BACKUP_DIR="/backups/database"
|
||
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||
DB_NAME="michaelschiemer_prod"
|
||
|
||
# Create backup directory
|
||
mkdir -p "$BACKUP_DIR"
|
||
|
||
# Backup database
|
||
docker exec db pg_dump -U postgres "$DB_NAME" | \
|
||
gzip > "$BACKUP_DIR/backup_${TIMESTAMP}.sql.gz"
|
||
|
||
# Verify backup
|
||
if [ $? -eq 0 ]; then
|
||
echo "✅ Backup successful: $BACKUP_DIR/backup_${TIMESTAMP}.sql.gz"
|
||
else
|
||
echo "❌ Backup failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Keep only last 30 days of backups
|
||
find "$BACKUP_DIR" -name "backup_*.sql.gz" -mtime +30 -delete
|
||
```
|
||
|
||
### Database Restore
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# scripts/restore-database.sh
|
||
|
||
BACKUP_FILE="$1"
|
||
|
||
if [ -z "$BACKUP_FILE" ]; then
|
||
echo "Usage: $0 /path/to/backup.sql.gz"
|
||
exit 1
|
||
fi
|
||
|
||
# Confirm restore
|
||
echo "⚠️ This will REPLACE the current database with backup: $BACKUP_FILE"
|
||
read -p "Are you sure? (yes/no): " confirm
|
||
|
||
if [ "$confirm" != "yes" ]; then
|
||
echo "Restore cancelled"
|
||
exit 0
|
||
fi
|
||
|
||
# Drop and recreate database
|
||
docker exec db psql -U postgres -c "DROP DATABASE IF EXISTS michaelschiemer_prod"
|
||
docker exec db psql -U postgres -c "CREATE DATABASE michaelschiemer_prod"
|
||
|
||
# Restore backup
|
||
gunzip -c "$BACKUP_FILE" | docker exec -i db psql -U postgres michaelschiemer_prod
|
||
|
||
echo "✅ Database restored from backup"
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### Problem: Migration Times Out
|
||
|
||
**Cause**: Long-running operation on large table
|
||
|
||
**Solution**:
|
||
```php
|
||
// Increase timeout for migration
|
||
$connection->execute("SET statement_timeout = '300s'");
|
||
|
||
// Or split into smaller batches
|
||
$batchSize = 10000;
|
||
// ... batch processing code
|
||
```
|
||
|
||
### Problem: Migration Fails Midway
|
||
|
||
**Cause**: Error during schema change or data migration
|
||
|
||
**Solution**:
|
||
```bash
|
||
# Check migration status
|
||
docker exec php php console.php db:status
|
||
|
||
# If migration is partially applied:
|
||
# 1. Manual cleanup (if needed)
|
||
docker exec db psql -U postgres michaelschiemer_prod
|
||
|
||
# 2. Mark migration as failed
|
||
docker exec php php console.php db:reset [version]
|
||
|
||
# 3. Fix migration code
|
||
|
||
# 4. Re-run migration
|
||
docker exec php php console.php db:migrate
|
||
```
|
||
|
||
### Problem: Foreign Key Constraint Violation
|
||
|
||
**Cause**: Data inconsistency or missing referenced rows
|
||
|
||
**Solution**:
|
||
```sql
|
||
-- Find orphaned rows
|
||
SELECT * FROM child_table
|
||
WHERE parent_id NOT IN (SELECT id FROM parent_table);
|
||
|
||
-- Fix data before migration
|
||
DELETE FROM child_table
|
||
WHERE parent_id NOT IN (SELECT id FROM parent_table);
|
||
|
||
-- Then run migration
|
||
```
|
||
|
||
## See Also
|
||
|
||
- **Production Prerequisites**: `docs/deployment/production-prerequisites.md`
|
||
- **Database Patterns**: `docs/claude/database-patterns.md`
|
||
- **Deployment Guide**: `docs/deployment/deployment-guide.md` (TODO)
|
||
- **Rollback Guide**: `docs/deployment/rollback-guide.md` (TODO)
|