Skip to content

Database Migration Guide

This guide provides comprehensive information on handling database migrations for the Meta Agent Platform.

Table of Contents

  1. Introduction
  2. Migration Tools
  3. Migration Workflow
  4. Migration Command Reference
  5. Best Practices
  6. Multi-Tenancy Considerations
  7. Edge Deployment Considerations
  8. Common Scenarios
  9. Troubleshooting
  10. Monitoring & Validation

Introduction

Database migrations are essential for evolving the database schema as the application develops. The Meta Agent Platform uses a migration-based approach to manage schema changes across all environments (development, staging, and production) in a controlled, versioned manner.

Migration Philosophy

The platform follows these principles for database migrations:

  1. Versioned: All schema changes are versioned and tracked in source control
  2. Incremental: Changes are applied incrementally, building on previous versions
  3. Automated: Migrations can be applied automatically during deployment
  4. Reversible: Where possible, migrations include both "up" and "down" operations
  5. Consistent: Migrations ensure consistent schema across all environments
  6. Safe: Migrations are designed to minimize risk and avoid data loss

Migration Tools

The Meta Agent Platform uses the following tools for database migrations:

SQLAlchemy ORM

SQLAlchemy provides the Object-Relational Mapping (ORM) layer that defines the Python data models representing database tables and relationships.

Key components: - Models: Python classes that define database tables and their relationships - MetaData: Contains the table definitions and their structure - Engine: Connection to the database - Session: Unit of work for interacting with the database

Alembic

Alembic is a database migration tool for SQLAlchemy that manages schema changes over time.

Key components: - Revisions: Individual migration scripts containing schema changes - Version Control: Tracks which migrations have been applied - Migration Context: Provides information about the current state of migrations - Operations: Methods for schema changes (create_table, add_column, etc.)

Migration Workflow

Initial Setup

If you're setting up migrations for the first time:

  1. Initialize Alembic:

    cd backend
    alembic init migrations
    

  2. Configure Alembic:

  3. Update alembic.ini with database connection string
  4. Configure env.py to use application models

  5. Create Initial Migration:

    alembic revision --autogenerate -m "Initial schema"
    

Regular Migration Workflow

The standard workflow for implementing database changes:

  1. Update Models:
  2. Modify SQLAlchemy models to reflect desired schema changes
  3. Ensure model changes align with business requirements

  4. Generate Migration Script:

    alembic revision --autogenerate -m "Description of changes"
    

  5. Review Migration Script:

  6. Alembic auto-generates a migration script in migrations/versions/
  7. Review the script to ensure it accurately captures intended changes
  8. Modify if necessary (add data migrations, fix complex changes)

  9. Test Migration:

  10. Apply migration to development database:
    alembic upgrade head
    
  11. Verify changes work as expected
  12. Test downgrade if needed:

    alembic downgrade -1
    

  13. Commit Migration:

  14. Commit both model changes and migration script to version control

  15. Deploy:

  16. During deployment, run alembic upgrade head to apply pending migrations

Migration Command Reference

Creating Migrations

# Auto-generate migration based on model changes
alembic revision --autogenerate -m "Description"

# Create empty migration script
alembic revision -m "Description"

Applying Migrations

# Upgrade to latest version
alembic upgrade head

# Upgrade to specific version
alembic upgrade <revision_id>

# Upgrade by relative steps
alembic upgrade +2

# Downgrade to specific version
alembic downgrade <revision_id>

# Downgrade by relative steps
alembic downgrade -1

# Downgrade to base (before any migrations)
alembic downgrade base

Information Commands

# Show current migration version
alembic current

# Show migration history
alembic history

# Show pending migrations
alembic history --indicate-current

# Show SQL that would be executed (without running it)
alembic upgrade head --sql

Best Practices

Writing Migration Scripts

  1. Keep Migrations Focused:
  2. Each migration should handle a specific, coherent change
  3. Avoid combining unrelated changes in a single migration

  4. Make Migrations Reversible:

  5. Implement both upgrade() and downgrade() methods
  6. Test downgrades to ensure they work correctly

  7. Handle Data Migrations Carefully:

  8. Add explicit data transformation steps when changing column types
  9. Consider performance for large tables

  10. Comment Complex Operations:

  11. Add comments explaining the reasoning behind complex changes
  12. Document any assumptions or prerequisites

  13. Avoid Raw SQL When Possible:

  14. Use Alembic operations instead of raw SQL for better database agnosticism
  15. When raw SQL is necessary, clearly comment and consider database-specific variants

Migration Workflow Practices

  1. Regular Small Migrations:
  2. Prefer frequent, small migrations over infrequent, large ones
  3. Easier to review, test, and troubleshoot

  4. Test on a Copy:

  5. Test migrations on a copy of production data before applying to production
  6. Identify potential performance or data issues

  7. Maintain Backward Compatibility:

  8. When possible, maintain code compatibility with both old and new schema during transitions
  9. Allows for safer rollbacks if issues are discovered after deployment

  10. Version Control:

  11. Always commit migration scripts to version control
  12. Keep migrations in sync with corresponding model changes

  13. CI/CD Integration:

  14. Run migrations as part of CI/CD pipeline
  15. Include migration tests in automated testing

Multi-Tenancy Considerations

The Meta Agent Platform uses a multi-tenant database architecture, which requires special consideration for migrations.

Row-Level Security

  1. Policy Creation:
  2. Include RLS policy creation in migrations for new tables
  3. Example:

    def upgrade():
        # Create table
        op.create_table('my_table', ...)
    
        # Add RLS policy
        op.execute("""
            ALTER TABLE my_table ENABLE ROW LEVEL SECURITY;
            CREATE POLICY tenant_isolation_policy ON my_table
               USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
        """)
    

  4. Policy Updates:

  5. When changing table structure, ensure RLS policies are updated accordingly

Tenant-Specific Migrations

  1. Global vs. Tenant-Specific:
  2. Determine if a migration affects all tenants or is tenant-specific
  3. For tenant-specific migrations, consider using a migration wrapper that iterates through tenants

  4. Migration Function:

  5. Example for tenant-specific data migration:
    def upgrade():
        # Schema changes first (global)
        op.add_column('table', sa.Column('new_column', sa.String(), nullable=True))
    
        # Tenant-specific data migration
        connection = op.get_bind()
        tenants = connection.execute("SELECT id FROM tenants").fetchall()
    
        for tenant_id in tenants:
            connection.execute(
                f"SET app.current_tenant_id = '{tenant_id[0]}';"
                "UPDATE table SET new_column = 'default' WHERE new_column IS NULL;"
            )
    

Edge Deployment Considerations

The Meta Agent Platform supports edge deployments with SQLite, which requires special handling for migrations.

SQLite Limitations

  1. Limited ALTER TABLE Support:
  2. SQLite doesn't support all ALTER TABLE operations (like dropping columns)
  3. Workaround: recreate tables with new schema

  4. Migration Strategy:

  5. Use conditional logic in migrations based on database type
  6. Example:
    def upgrade():
        if op.get_context().dialect.name == 'sqlite':
            # SQLite-specific approach
            with op.batch_alter_table('my_table') as batch_op:
                batch_op.add_column(sa.Column('new_column', sa.String()))
        else:
            # PostgreSQL approach
            op.add_column('my_table', sa.Column('new_column', sa.String()))
    

Edge Synchronization

  1. Migration State Tracking:
  2. Track migration state for each edge device
  3. Sync migration state during connection to central system

  4. Offline Migration:

  5. Package relevant migrations with edge deployment
  6. Apply migrations during edge runtime updates

Common Scenarios

Adding a New Table

def upgrade():
    op.create_table(
        'new_table',
        sa.Column('id', sa.UUID(), nullable=False),
        sa.Column('name', sa.String(), nullable=False),
        sa.Column('description', sa.Text(), nullable=True),
        sa.Column('created_at', sa.DateTime(), server_default=sa.text('now()'), nullable=False),
        sa.Column('tenant_id', sa.UUID(), nullable=False),
        sa.PrimaryKeyConstraint('id'),
        sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ondelete='CASCADE')
    )

    # Add indexes
    op.create_index('ix_new_table_tenant_id', 'new_table', ['tenant_id'])

    # Add RLS policy
    op.execute("""
        ALTER TABLE new_table ENABLE ROW LEVEL SECURITY;
        CREATE POLICY tenant_isolation_policy ON new_table
            USING (tenant_id = current_setting('app.current_tenant_id')::UUID);
    """)

def downgrade():
    op.drop_table('new_table')

Adding a Column

def upgrade():
    op.add_column('table_name', sa.Column('new_column', sa.String(50), nullable=True))

    # Set default values for existing rows
    op.execute("UPDATE table_name SET new_column = 'default'")

    # Make column non-nullable after setting defaults
    op.alter_column('table_name', 'new_column', nullable=False)

def downgrade():
    op.drop_column('table_name', 'new_column')

Changing Column Type

def upgrade():
    # For PostgreSQL
    op.alter_column('table_name', 'column_name',
                    type_=sa.String(100),
                    existing_type=sa.String(50),
                    postgresql_using='column_name::varchar(100)')

def downgrade():
    op.alter_column('table_name', 'column_name',
                    type_=sa.String(50),
                    existing_type=sa.String(100))

Data Migrations

def upgrade():
    # Add new column
    op.add_column('users', sa.Column('full_name', sa.String(255), nullable=True))

    # Migrate data
    op.execute("UPDATE users SET full_name = first_name || ' ' || last_name")

    # Make column non-nullable
    op.alter_column('users', 'full_name', nullable=False)

    # Optionally, remove old columns
    op.drop_column('users', 'first_name')
    op.drop_column('users', 'last_name')

def downgrade():
    # Restore old columns
    op.add_column('users', sa.Column('first_name', sa.String(100), nullable=True))
    op.add_column('users', sa.Column('last_name', sa.String(100), nullable=True))

    # Migrate data back
    op.execute("UPDATE users SET first_name = split_part(full_name, ' ', 1), last_name = split_part(full_name, ' ', 2)")

    # Drop new column
    op.drop_column('users', 'full_name')

Troubleshooting

Common Migration Issues

  1. Migration Conflicts:
  2. Symptom: Multiple developers create migrations that conflict
  3. Solution: Rebase migrations, creating a new migration that combines changes

  4. Failed Migrations:

  5. Symptom: Migration fails partway through execution
  6. Solution: Fix the issue, then either:

    • Fix and rerun the migration
    • Manually fix the database state, then mark migration as complete
    • Restore from backup and retry
  7. Missing Dependencies:

  8. Symptom: Migration depends on a table/column created in another migration that hasn't run
  9. Solution: Ensure migrations are applied in the correct order, check branch dependencies

  10. SQLite Limitations:

  11. Symptom: Migration works in PostgreSQL but fails in SQLite (edge deployment)
  12. Solution: Use batch_alter_table or create database-specific migration paths

Fixing a Failed Migration

  1. Identify the Issue:

    # Check current migration state
    alembic current
    

  2. Options for Recovery:

    # Option 1: Fix the migration script and retry
    alembic upgrade head
    
    # Option 2: Manually fix database and mark as complete
    alembic stamp <revision_id>
    
    # Option 3: Downgrade and retry
    alembic downgrade <previous_working_revision>
    # Fix the script
    alembic upgrade head
    

Monitoring & Validation

Migration Validation

  1. Pre-Migration Checks:
  2. Validate that migration scripts can be generated without errors
  3. Check for potential data consistency issues
  4. Estimate migration duration for large tables

  5. Post-Migration Validation:

  6. Verify schema changes
  7. Run basic application tests
  8. Check data integrity
  9. Monitor performance of affected queries

Migration Metrics

Track key metrics during migrations:

  1. Execution Time:
  2. How long migrations take to complete

  3. Table Size Changes:

  4. How migrations affect database size

  5. Error Rate:

  6. Number of failed migrations vs. successful ones

  7. Application Impact:

  8. Performance metrics before and after migration
  9. Error rates during and after migration

Conclusion

Effective database migration management is crucial for the successful evolution of the Meta Agent Platform. By following the practices outlined in this guide, you can ensure smooth schema transitions while maintaining data integrity and application stability.

Remember that migrations are not just about changing the database schema—they're about safely evolving your data model while preserving existing data and functionality.