# Database Migrations Guide This guide covers advanced database migration workflows for developers working on schema changes. ## Overview Our project uses Alembic for database migrations. All schema changes must go through the migration system to ensure: - Reproducible deployments - Team synchronization - Production safety - Rollback capability ## Migration Commands Reference ### Creating Migrations ```bash # Auto-generate migration from model changes make migrate-create message="add_user_profile_table" # Create empty migration template for manual changes make migrate-create-manual message="add_custom_indexes" ``` ### Applying Migrations ```bash # Apply all pending migrations make migrate-up # Rollback last migration make migrate-down # Rollback to specific revision make migrate-down-to revision="abc123" ``` ### Migration Status ```bash # Show current migration status make migrate-status # Show detailed migration history alembic history --verbose # Show specific migration details make migrate-show revision="abc123" ``` ### Backup and Safety ```bash # Create database backup before major changes make backup-db # Verify database setup make verify-setup ``` ## Alembic Commands Explained Understanding what each Alembic command does is essential for managing database migrations effectively. ### Core Commands Reference | Command | Description | |---------|-------------| | `alembic upgrade head` | Apply **all** pending migrations to get to the latest version | | `alembic upgrade +1` | Apply only the **next** single migration (one step forward) | | `alembic downgrade -1` | Roll back the **last** applied migration (one step back) | | `alembic downgrade base` | Roll back **all** migrations (returns to empty schema) | | `alembic current` | Show which migration the database is currently at | | `alembic history` | List all migrations in the chain | | `alembic heads` | Show the latest migration revision(s) | ### Visual Example ``` Migration Chain: [base] → [A] → [B] → [C] → [D] (head) ↑ └── current database state (at revision B) Command Result ───────────────────────────────────────────────────────── alembic upgrade head → Database moves to [D] alembic upgrade +1 → Database moves to [C] alembic downgrade -1 → Database moves to [A] alembic downgrade base → Database returns to [base] (empty schema) alembic current → Shows "B" (current revision) ``` ### Makefile Shortcuts | Make Command | Alembic Equivalent | Description | |--------------|-------------------|-------------| | `make migrate-up` | `alembic upgrade head` | Apply all pending migrations | | `make migrate-down` | `alembic downgrade -1` | Roll back last migration | | `make migrate-status` | `alembic current` + `alembic history` | Show current state and history | ### Additional Useful Commands ```bash # Show detailed migration history alembic history --verbose # Upgrade/downgrade to a specific revision alembic upgrade abc123def456 alembic downgrade abc123def456 # Show what SQL would be generated (without executing) alembic upgrade head --sql # Mark database as being at a specific revision (without running migrations) alembic stamp head alembic stamp abc123def456 # Show the current revision ID only alembic current --verbose ``` ### Database Initialization Workflow For setting up a new database: ```bash # Option 1: Empty schema only (just tables, no data) make migrate-up # Option 2: Schema + production essentials (admin user, settings, CMS, email templates) make init-prod # Option 3: Full development setup (schema + production data + demo data) make db-setup # Or step by step: make init-prod make seed-demo ``` ### Reset Workflow For completely resetting the database: ```bash # Nuclear reset: deletes database file, recreates schema, seeds all data make db-reset ``` > **Note**: `make db-reset` rolls back all migrations and recreates the schema from scratch. !!! note "PostgreSQL Required" This project uses PostgreSQL exclusively. SQLite is not supported. Start the development database with: `make docker-up` ## Development Workflows ### Adding New Database Fields 1. **Modify your SQLAlchemy model**: ```python # In models/database/user.py class User(Base): # ... existing fields profile_image = Column(String, nullable=True) # NEW FIELD ``` 2. **Generate migration**: ```bash make migrate-create message="add_profile_image_to_users" ``` 3. **Review generated migration**: ```python # Check alembic/versions/xxx_add_profile_image_to_users.py def upgrade() -> None: op.add_column('users', sa.Column('profile_image', sa.String(), nullable=True)) def downgrade() -> None: op.drop_column('users', 'profile_image') ``` 4. **Apply migration**: ```bash make migrate-up ``` ### Adding Database Indexes 1. **Create manual migration**: ```bash make migrate-create-manual message="add_performance_indexes" ``` 2. **Edit the migration file**: ```python def upgrade() -> None: # Add indexes for better performance op.create_index('idx_products_marketplace_shop', 'products', ['marketplace', 'shop_name']) op.create_index('idx_users_email_active', 'users', ['email', 'is_active']) def downgrade() -> None: op.drop_index('idx_users_email_active', table_name='users') op.drop_index('idx_products_marketplace_shop', table_name='products') ``` 3. **Apply migration**: ```bash make migrate-up ``` ### Complex Schema Changes For complex changes that require data transformation: 1. **Create migration with data handling**: ```python def upgrade() -> None: # Create new column op.add_column('products', sa.Column('normalized_price', sa.Numeric(10, 2))) # Migrate data connection = op.get_bind() connection.execute( text("UPDATE products SET normalized_price = CAST(price AS NUMERIC) WHERE price ~ '^[0-9.]+$'") ) # Make column non-nullable after data migration op.alter_column('products', 'normalized_price', nullable=False) def downgrade() -> None: op.drop_column('products', 'normalized_price') ``` ## Production Deployment ### Pre-Deployment Checklist - [ ] All migrations tested locally - [ ] Database backup created - [ ] Migration rollback plan prepared - [ ] Team notified of schema changes ### Deployment Process ```bash # 1. Pre-deployment checks make pre-deploy-check # 2. Backup production database make backup-db # 3. Deploy with migrations make deploy-prod # This includes migrate-up ``` ### Rollback Process ```bash # If deployment fails, rollback make rollback-prod # This includes migrate-down ``` ## Best Practices ### Migration Naming Use clear, descriptive names: ```bash # Good examples make migrate-create message="add_user_profile_table" make migrate-create message="remove_deprecated_product_fields" make migrate-create message="add_indexes_for_search_performance" # Avoid vague names make migrate-create message="update_database" # Too vague make migrate-create message="fix_stuff" # Not descriptive ``` ### Safe Schema Changes **Always Safe**: - Adding nullable columns - Adding indexes - Adding new tables - Increasing column size (varchar(50) → varchar(100)) **Potentially Unsafe** (require careful planning): - Dropping columns - Changing column types - Adding non-nullable columns without defaults - Renaming tables or columns **Multi-Step Process for Unsafe Changes**: ```python # Step 1: Add new column def upgrade() -> None: op.add_column('users', sa.Column('email_new', sa.String(255))) # Step 2: Migrate data (separate migration) def upgrade() -> None: connection = op.get_bind() connection.execute(text("UPDATE users SET email_new = email")) # Step 3: Switch columns (separate migration) def upgrade() -> None: op.drop_column('users', 'email') op.alter_column('users', 'email_new', new_column_name='email') ``` ### Testing Migrations 1. **Test on copy of production data**: ```bash # Restore production backup to test database # Run migrations on test database # Verify data integrity ``` 2. **Test rollback process**: ```bash make migrate-up # Apply migration # Test application functionality make migrate-down # Test rollback # Verify rollback worked correctly ``` ## Advanced Features ### Environment-Specific Migrations Use migration context to handle different environments: ```python from alembic import context def upgrade() -> None: # Only add sample data in development if context.get_x_argument(as_dictionary=True).get('dev_data', False): # Add development sample data pass # Always apply schema changes op.create_table(...) ``` Run with environment flag: ```bash alembic upgrade head -x dev_data=true ``` ### Data Migrations For large data transformations, use batch processing: ```python def upgrade() -> None: connection = op.get_bind() # Process in batches to avoid memory issues batch_size = 1000 offset = 0 while True: result = connection.execute( text(f"SELECT id, old_field FROM products LIMIT {batch_size} OFFSET {offset}") ) rows = result.fetchall() if not rows: break for row in rows: # Transform data new_value = transform_function(row.old_field) connection.execute( text("UPDATE products SET new_field = :new_val WHERE id = :id"), {"new_val": new_value, "id": row.id} ) offset += batch_size ``` ## Troubleshooting ### Common Issues **Migration conflicts**: ```bash # When multiple developers create migrations simultaneously # Resolve by creating a merge migration alembic merge -m "merge migrations" head1 head2 ``` **Failed migration**: ```bash # Check current state make migrate-status # Manually fix database if needed # Then mark migration as applied alembic stamp head ``` **Out of sync database**: ```bash # Reset to known good state make backup-db alembic downgrade base make migrate-up ``` ### Recovery Procedures 1. **Database corruption**: Restore from backup, replay migrations 2. **Failed deployment**: Use rollback process, investigate issue 3. **Development issues**: Reset local database, pull latest migrations ## Integration with CI/CD Our deployment pipeline automatically: 1. Runs migration checks in CI 2. Creates database backups before deployment 3. Applies migrations during deployment 4. Provides rollback capability Migration failures will halt deployment to prevent data corruption. ## Further Reading - [Alembic Official Documentation](https://alembic.sqlalchemy.org/) - [Database Seeder Documentation](../database-seeder/database-seeder-documentation.md) - [Database Init Guide](../database-seeder/database-init-guide.md)