Database Migrations¶
This document describes how to work with database migrations in the Byte Bot project after the microservices migration.
Overview¶
Database migrations are managed using Alembic through Advanced Alchemy, which provides async-aware migration support integrated with SQLAlchemy 2.0.
Location¶
Migration files:
services/api/src/byte_api/lib/db/migrations/versions/Alembic config:
services/api/src/byte_api/lib/db/alembic.iniEnvironment:
services/api/src/byte_api/lib/db/migrations/env.py
Migration History¶
The project contains the following migrations (in order):
initial.py (
43165a559e89) - Initial schema with guild, user, GitHub, and SO tags configuration002_simplify_models.py (
feebdacfdd91) - Simplified model structure, consolidated configuration tables003_forum_models.py (
73a26ceab2c4) - Added forum configuration support004_snowflake_fixes.py (
f32ee278015d) - Fixed Discord snowflake ID handling and foreign key relationships
Running Migrations¶
Prerequisites¶
Before running migrations, ensure:
All required environment variables are configured (see
.env.example)Database connection string is valid (
DB_URL)GitHub App credentials are configured (required by settings validation)
Minimal .env for migrations:
DB_URL=postgresql+asyncpg://byte:bot@localhost:5432/byte
SECRET_KEY=your-secret-key-here
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY=/path/to/private-key.pem
GITHUB_APP_CLIENT_ID=Iv1.xxxxx
GITHUB_APP_CLIENT_SECRET=xxxxx
Database Setup¶
PostgreSQL database running (use docker-compose):
docker compose -f docker-compose.infra.yml up -d db
Upgrade to Latest¶
To apply all pending migrations:
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
cmds = AlembicCommands(sqlalchemy_config=config)
cmds.upgrade()
Or from shell:
uv run python -c "
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
AlembicCommands(sqlalchemy_config=config).upgrade()
"
Check Current Version¶
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
cmds = AlembicCommands(sqlalchemy_config=config)
cmds.current()
Downgrade¶
To rollback one migration:
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
cmds = AlembicCommands(sqlalchemy_config=config)
cmds.downgrade(revision="-1")
To rollback to a specific version:
cmds.downgrade(revision="<revision_id>")
Creating New Migrations¶
Auto-generate from Model Changes¶
When you modify models in byte-common/src/byte_common/models.py:
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
cmds = AlembicCommands(sqlalchemy_config=config)
cmds.revision(message="description of changes", autogenerate=True)
Manual Migration¶
For data migrations or complex schema changes:
cmds.revision(message="description of changes", autogenerate=False)
Then edit the generated file in services/api/src/byte_api/lib/db/migrations/versions/.
Migration File Structure¶
Each migration file follows this pattern:
# type: ignore
"""Revision ID: <id>
Revises: <parent_id>
Create Date: <timestamp>
"""
from __future__ import annotations
import warnings
import sqlalchemy as sa
from advanced_alchemy.types import GUID, ORA_JSONB, DateTimeUTC
from alembic import op
# revision identifiers, used by Alembic
revision = "<id>"
down_revision = "<parent_id>"
branch_labels = None
depends_on = None
def upgrade() -> None:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
with op.get_context().autocommit_block():
schema_upgrades()
data_upgrades()
def downgrade() -> None:
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=UserWarning)
with op.get_context().autocommit_block():
data_downgrades()
schema_downgrades()
def schema_upgrades() -> None:
"""Schema upgrade migrations go here."""
# ### commands auto generated by Alembic ###
pass
def schema_downgrades() -> None:
"""Schema downgrade migrations go here."""
# ### commands auto generated by Alembic ###
pass
def data_upgrades() -> None:
"""Add any optional data upgrade migrations here!"""
pass
def data_downgrades() -> None:
"""Add any optional data downgrade migrations here!"""
pass
Configuration¶
Alembic.ini¶
The Alembic configuration uses %(here)s to resolve paths relative to the config file location:
[alembic]
prepend_sys_path = .
script_location = %(here)s/migrations
file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(slug)s_%%(rev)s
timezone = UTC
truncate_slug_length = 40
Database Settings¶
Migration paths are configured in services/api/src/byte_api/lib/settings.py:
class DatabaseSettings(BaseSettings):
MIGRATION_CONFIG: str = f"{BASE_DIR}/lib/db/alembic.ini"
MIGRATION_PATH: str = f"{BASE_DIR}/lib/db/migrations"
MIGRATION_DDL_VERSION_TABLE: str = "ddl_version"
Model Location¶
All database models are defined in the byte-common package:
Location:
packages/byte-common/src/byte_common/models/(directory with separate model files)Import:
from byte_common.models import Guild, User, GitHubConfig, ForumConfig, SOTagsConfig, AllowedUsersConfig
Models are organized as follows:
guild.py- Guild configurationuser.py- User modelgithub_config.py- GitHub integration settingsforum_config.py- Forum channel configurationsotags_config.py- Stack Overflow tagsallowed_users_config.py- User permissions
This allows models to be shared between the API service and bot service.
Troubleshooting¶
“Path doesn’t exist” Error¶
If you see this error when running alembic commands directly, it’s because alembic.ini uses relative paths. Use the Python API shown above instead.
“Cannot import name ‘get_settings’”¶
Use load_settings() instead, which returns a tuple of settings objects.
Database Connection Refused¶
Ensure PostgreSQL is running:
docker compose -f docker-compose.infra.yml ps
Import Errors for Models¶
Ensure you’re importing from byte_common.models, not the old byte_bot.server.domain.db.models path.
Production Deployment¶
On Railway, migrations are run programmatically before the service starts using the migration script:
# From railway.json or nixpacks.toml
uv run python -m byte_api.scripts.migrate && uv run litestar run --app byte_api.app:create_app --host 0.0.0.0 --port $PORT
The migration script (services/api/src/byte_api/scripts/migrate.py) uses the Advanced Alchemy Python API:
from advanced_alchemy.alembic.commands import AlembicCommands
from byte_api.lib.db.base import config
if __name__ == "__main__":
print("Running database migrations...")
AlembicCommands(sqlalchemy_config=config).upgrade()
print("Migrations complete!")
Note
The monolithic app CLI was removed in Phase 1.4 of the microservices migration.
All migrations now use the Advanced Alchemy Python API directly.
Rollback Plan¶
In case of migration issues:
Identify the problem revision:
cmds.current() # Shows current versionDowngrade to previous stable version:
cmds.downgrade(revision="<previous_revision_id>")For complete rollback:
cmds.downgrade(revision="base") # WARNING: Drops all tablesRe-apply migrations after fixes:
cmds.upgrade()
Best Practices¶
Always test migrations locally before deploying to production
Use transactions - Alembic automatically wraps migrations in transactions
Backup production data before running migrations
Write reversible migrations - Always implement both
upgrade()anddowngrade()Test rollback - Verify that downgrade works as expected
Document data migrations - Add comments explaining complex transformations
Avoid destructive changes - Use separate migrations for adding and dropping columns
Keep migrations small - One logical change per migration file