Files
Aether/_deprecated_py_src/services/user/bulk_cleanup.py
fawney19 1d9c77522a refactor: 移除 Python 后端源码,全面迁移至 Rust gateway 架构
- 删除全部 Python 源码 (src/) 及 Alembic 迁移脚本,归档至 _deprecated_py_src/
- 重构 Rust gateway ai_pipeline: 拆分 planner/finalize 模块,新增 contracts/adaptation 层
- 重组 handlers 模块为 admin/public/proxy/internal/shared 子模块结构
- 新增 executor 模块,引入 Rust 原生数据库迁移 (aether-data/migrations)
- 简化 CI/Docker 构建流程,移除 base image 二级构建,统一为单一 app image
- 移除 Python 相关基础设施文件 (entrypoint.sh, gunicorn_conf.py, Dockerfile.base)
2026-04-03 16:26:16 +08:00

112 lines
3.0 KiB
Python

from __future__ import annotations
import time
from typing import Any
from sqlalchemy.orm import Session
from src.core.logger import logger
from src.models.database import RequestCandidate, Usage
_POSTGRES_BATCH_SIZE = 2000
_SQLITE_BATCH_SIZE = 900
def _resolve_batch_size(db: Session) -> int:
try:
bind = db.get_bind()
dialect_name = str(getattr(getattr(bind, "dialect", None), "name", "") or "").lower()
except Exception:
dialect_name = ""
if dialect_name == "sqlite":
return _SQLITE_BATCH_SIZE
return _POSTGRES_BATCH_SIZE
def batch_nullify_fk(
db: Session,
model: type[Any],
column_name: str,
entity_id: str | None,
) -> int:
"""分批将大表外键置空,避免单个长事务阻塞删除流程。"""
if not entity_id:
return 0
column = getattr(model, column_name)
primary_key_column = next(iter(model.__table__.primary_key.columns))
batch_size = _resolve_batch_size(db)
total_updated = 0
batch_index = 0
started_at = time.monotonic()
while True:
batch_ids = [
row[0]
for row in db.query(primary_key_column)
.filter(column == entity_id)
.limit(batch_size)
.all()
]
if not batch_ids:
break
batch_index += 1
batch_started_at = time.monotonic()
updated = int(
db.query(model)
.filter(primary_key_column.in_(batch_ids))
.update({column: None}, synchronize_session=False)
or 0
)
db.commit()
total_updated += updated
elapsed_ms = int((time.monotonic() - batch_started_at) * 1000)
logger.info(
"批量清理 {}.{}: batch={}, updated={}, entity_id={}, elapsed_ms={}",
model.__tablename__,
column_name,
batch_index,
updated,
entity_id,
elapsed_ms,
)
if len(batch_ids) < batch_size:
break
if total_updated > 0:
total_elapsed_ms = int((time.monotonic() - started_at) * 1000)
logger.info(
"批量清理完成 {}.{}: total_updated={}, entity_id={}, elapsed_ms={}",
model.__tablename__,
column_name,
total_updated,
entity_id,
total_elapsed_ms,
)
return total_updated
def pre_clean_api_key(db: Session, api_key_id: str | None) -> int:
"""预清理 API Key 在大表中的外键引用,减少后续删除锁竞争。"""
if not api_key_id:
return 0
usage_rows = batch_nullify_fk(db, Usage, "api_key_id", api_key_id)
candidate_rows = batch_nullify_fk(db, RequestCandidate, "api_key_id", api_key_id)
total_rows = usage_rows + candidate_rows
if total_rows > 0:
logger.info(
"API Key 预清理完成: api_key_id={}, usage={}, request_candidates={}",
api_key_id,
usage_rows,
candidate_rows,
)
return total_rows