From 9fefe289a7f70520f49b305a40ba7fe39ed784c2 Mon Sep 17 00:00:00 2001 From: Fusion Date: Mon, 18 May 2026 22:35:42 +0200 Subject: [PATCH] fix: prevent concurrent migrations in multi-worker setup Add migration version check before running alembic upgrade to prevent multiple uvicorn workers from running migrations simultaneously. - Check current vs head revision before running migrations - Skip migration if already at latest version - Log current and head revision for debugging --- .../api/alembic/versions/0003_user_configs.py | 2 +- apps/api/src/database.py | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/apps/api/alembic/versions/0003_user_configs.py b/apps/api/alembic/versions/0003_user_configs.py index 9df718d..18acfd9 100644 --- a/apps/api/alembic/versions/0003_user_configs.py +++ b/apps/api/alembic/versions/0003_user_configs.py @@ -11,7 +11,7 @@ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. -revision: str = '0003' +revision: str = '0003_user_configs' down_revision: Union[str, None] = '0002_refresh_tokens' branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/apps/api/src/database.py b/apps/api/src/database.py index cd07de1..2fc1434 100644 --- a/apps/api/src/database.py +++ b/apps/api/src/database.py @@ -3,6 +3,7 @@ import logging from alembic import command from alembic.config import Config +from alembic.runtime.migration import MigrationContext from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.pool import NullPool @@ -48,6 +49,26 @@ async def init_database( logger.info("Database connection established.") + # Check if migrations are already complete + def _check_migrations(): + alembic_cfg = Config(alembic_ini_path) + script = command.ScriptDirectory.from_config(alembic_cfg) + with engine.connect() as conn: + context = MigrationContext.configure(conn) + current_rev = context.get_current_revision() + head_rev = script.get_current_head() + return current_rev == head_rev, current_rev, head_rev + + is_current, current_rev, head_rev = await asyncio.get_event_loop().run_in_executor( + None, _check_migrations + ) + + if is_current: + logger.info("Database is already at the latest migration (%s).", head_rev) + return True + + logger.info("Current revision: %s, Head revision: %s. Running migrations...", current_rev, head_rev) + # Run alembic migrations (sync call in executor) def _run_migrations(): alembic_cfg = Config(alembic_ini_path)