mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
fix(alembic,pipeline,resilience): 数据库迁移与异常处理健壮性修复
- alembic env: 改用事务级 advisory lock (pg_advisory_xact_lock),事务结束自动释放 - 迁移脚本: 使用原生 SQL IF NOT EXISTS/IF EXISTS 替代运行时列检查 - pipeline: SQLAlchemy 异常后先回滚事务再写审计,防止 aborted 状态二次报错 - resilience: ProgrammingError 标记为不可恢复,缩减 DB 重试异常范围 - 前端: 端点规则支持拖拽排序
This commit is contained in:
@@ -51,7 +51,8 @@ if config.config_file_name is not None:
|
||||
target_metadata = Base.metadata
|
||||
|
||||
# PostgreSQL 全局迁移锁,避免多进程并发执行 Alembic 导致竞态(重复加列/索引等)
|
||||
# ID 由 crc32("aether-alembic-migration") 拼接生成,仅需全局唯一即可
|
||||
# 使用事务级 advisory lock(pg_advisory_xact_lock),在迁移事务结束后自动释放。
|
||||
# ID 由 crc32("aether-alembic-migration") 拼接生成,仅需全局唯一即可。
|
||||
MIGRATION_ADVISORY_LOCK_ID = 582694137405821
|
||||
|
||||
|
||||
@@ -89,31 +90,20 @@ def run_migrations_online() -> None:
|
||||
)
|
||||
|
||||
with connectable.connect() as connection:
|
||||
lock_acquired = False
|
||||
try:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True, # 比较列类型变更
|
||||
compare_server_default=True, # 比较默认值变更
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
if connection.dialect.name == "postgresql":
|
||||
connection.execute(
|
||||
text("SELECT pg_advisory_lock(:lock_id)"),
|
||||
{"lock_id": MIGRATION_ADVISORY_LOCK_ID},
|
||||
)
|
||||
lock_acquired = True
|
||||
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True, # 比较列类型变更
|
||||
compare_server_default=True, # 比较默认值变更
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
if lock_acquired:
|
||||
connection.rollback()
|
||||
connection.execute(
|
||||
text("SELECT pg_advisory_unlock(:lock_id)"),
|
||||
text("SELECT pg_advisory_xact_lock(:lock_id)"),
|
||||
{"lock_id": MIGRATION_ADVISORY_LOCK_ID},
|
||||
)
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
# 根据模式选择运行方式
|
||||
|
||||
@@ -9,9 +9,6 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import inspect
|
||||
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
@@ -21,26 +18,12 @@ branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def _column_exists(table_name: str, column_name: str) -> bool:
|
||||
bind = op.get_bind()
|
||||
insp = inspect(bind)
|
||||
columns = [c["name"] for c in insp.get_columns(table_name)]
|
||||
return column_name in columns
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
if not _column_exists("proxy_nodes", "proxy_metadata"):
|
||||
op.add_column(
|
||||
"proxy_nodes",
|
||||
sa.Column(
|
||||
"proxy_metadata",
|
||||
sa.JSON(),
|
||||
nullable=True,
|
||||
comment="aether-proxy 上报元数据(版本等)",
|
||||
),
|
||||
)
|
||||
op.execute("ALTER TABLE public.proxy_nodes ADD COLUMN IF NOT EXISTS proxy_metadata json")
|
||||
op.execute(
|
||||
"COMMENT ON COLUMN public.proxy_nodes.proxy_metadata IS 'aether-proxy 上报元数据(版本等)'"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
if _column_exists("proxy_nodes", "proxy_metadata"):
|
||||
op.drop_column("proxy_nodes", "proxy_metadata")
|
||||
op.execute("ALTER TABLE public.proxy_nodes DROP COLUMN IF EXISTS proxy_metadata")
|
||||
|
||||
Reference in New Issue
Block a user