mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix(pool): 优化批量删除性能,使用 SQL 批量删除替代逐条 ORM 删除
- 后端批量删除改用 sa_delete 直接执行 SQL,避免逐条加载和删除 - 前端删除操作批次大小从 2000 减小到 50,防止大批量删除超时 - 增加前端批量操作失败时的错误日志输出
This commit is contained in:
@@ -620,9 +620,8 @@ async function executeAction(): Promise<void> {
|
|||||||
progressDone.value = Math.min(i + BATCH_SIZE, targetIds.length)
|
progressDone.value = Math.min(i + BATCH_SIZE, targetIds.length)
|
||||||
}
|
}
|
||||||
} else if (['delete', 'enable', 'disable', 'clear_proxy', 'set_proxy'].includes(selectedAction.value)) {
|
} else if (['delete', 'enable', 'disable', 'clear_proxy', 'set_proxy'].includes(selectedAction.value)) {
|
||||||
// 使用 batch-action API,每批最多 2000 个
|
|
||||||
const targetIds = selectedKeys.map((key) => key.key_id)
|
const targetIds = selectedKeys.map((key) => key.key_id)
|
||||||
const BATCH_SIZE = 2000
|
const BATCH_SIZE = selectedAction.value === 'delete' ? 50 : 2000
|
||||||
const totalBatches = Math.ceil(targetIds.length / BATCH_SIZE)
|
const totalBatches = Math.ceil(targetIds.length / BATCH_SIZE)
|
||||||
|
|
||||||
for (let i = 0; i < targetIds.length; i += BATCH_SIZE) {
|
for (let i = 0; i < targetIds.length; i += BATCH_SIZE) {
|
||||||
@@ -643,7 +642,9 @@ async function executeAction(): Promise<void> {
|
|||||||
...(payload ? { payload } : {}),
|
...(payload ? { payload } : {}),
|
||||||
})
|
})
|
||||||
successCount += result.affected
|
successCount += result.affected
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`batch ${selectedAction.value} failed (batch ${batchIndex}/${totalBatches}):`, err)
|
||||||
failedCount += batch.length
|
failedCount += batch.length
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ from datetime import datetime, timezone
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Query, Request
|
from fastapi import APIRouter, Depends, Query, Request
|
||||||
from sqlalchemy import case, func
|
from sqlalchemy import case
|
||||||
|
from sqlalchemy import delete as sa_delete
|
||||||
|
from sqlalchemy import func
|
||||||
from sqlalchemy.orm import Session, load_only
|
from sqlalchemy.orm import Session, load_only
|
||||||
|
|
||||||
from src.api.base.admin_adapter import AdminApiAdapter
|
from src.api.base.admin_adapter import AdminApiAdapter
|
||||||
@@ -1005,76 +1007,96 @@ class AdminBatchActionKeysAdapter(AdminApiAdapter):
|
|||||||
pid = str(provider.id)
|
pid = str(provider.id)
|
||||||
affected = 0
|
affected = 0
|
||||||
|
|
||||||
keys = (
|
if self.body.action == "delete":
|
||||||
db.query(ProviderAPIKey)
|
# 批量 SQL 删除,避免逐条 ORM delete
|
||||||
.filter(
|
key_ids = self.body.key_ids
|
||||||
ProviderAPIKey.provider_id == pid,
|
|
||||||
ProviderAPIKey.id.in_(self.body.key_ids),
|
|
||||||
)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
kid = str(key.id)
|
|
||||||
|
|
||||||
if self.body.action == "enable":
|
|
||||||
key.is_active = True
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "disable":
|
|
||||||
key.is_active = False
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "delete":
|
|
||||||
db.delete(key)
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "clear_cooldown":
|
|
||||||
await pool_redis.clear_cooldown(pid, kid)
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "reset_cost":
|
|
||||||
await pool_redis.clear_cost(pid, kid)
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "clear_proxy":
|
|
||||||
key.proxy = None
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "set_proxy":
|
|
||||||
key.proxy = self.body.payload
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
elif self.body.action == "regenerate_fingerprint":
|
|
||||||
key.fingerprint = generate_fingerprint(seed=None)
|
|
||||||
affected += 1
|
|
||||||
|
|
||||||
if self.body.action in {
|
|
||||||
"enable",
|
|
||||||
"disable",
|
|
||||||
"delete",
|
|
||||||
"regenerate_fingerprint",
|
|
||||||
"clear_proxy",
|
|
||||||
"set_proxy",
|
|
||||||
}:
|
|
||||||
try:
|
try:
|
||||||
|
result = db.execute(
|
||||||
|
sa_delete(ProviderAPIKey).where(
|
||||||
|
ProviderAPIKey.provider_id == pid,
|
||||||
|
ProviderAPIKey.id.in_(key_ids),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
affected = result.rowcount # type: ignore[assignment]
|
||||||
db.commit()
|
db.commit()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
logger.error("batch action commit failed: {}", exc)
|
logger.error("batch delete commit failed: {}", exc)
|
||||||
return BatchActionResponse(affected=0, message=f"commit failed: {exc}")
|
return BatchActionResponse(affected=0, message=f"commit failed: {exc}")
|
||||||
|
|
||||||
if self.body.action == "delete" and affected > 0:
|
if affected > 0:
|
||||||
from src.services.provider_keys.key_side_effects import run_delete_key_side_effects
|
from src.services.provider_keys.key_side_effects import (
|
||||||
|
run_delete_key_side_effects,
|
||||||
try:
|
|
||||||
await run_delete_key_side_effects(
|
|
||||||
db=db,
|
|
||||||
provider_id=pid,
|
|
||||||
deleted_key_allowed_models=None,
|
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
|
||||||
logger.error("batch delete side effects failed: {}", exc)
|
try:
|
||||||
|
await run_delete_key_side_effects(
|
||||||
|
db=db,
|
||||||
|
provider_id=pid,
|
||||||
|
deleted_key_allowed_models=None,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error("batch delete side effects failed: {}", exc)
|
||||||
|
|
||||||
|
admin_name = context.user.username if context.user else "admin"
|
||||||
|
affected_ids = [kid[:8] for kid in key_ids[:20]]
|
||||||
|
else:
|
||||||
|
keys = (
|
||||||
|
db.query(ProviderAPIKey)
|
||||||
|
.filter(
|
||||||
|
ProviderAPIKey.provider_id == pid,
|
||||||
|
ProviderAPIKey.id.in_(self.body.key_ids),
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
kid = str(key.id)
|
||||||
|
|
||||||
|
if self.body.action == "enable":
|
||||||
|
key.is_active = True
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "disable":
|
||||||
|
key.is_active = False
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "clear_cooldown":
|
||||||
|
await pool_redis.clear_cooldown(pid, kid)
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "reset_cost":
|
||||||
|
await pool_redis.clear_cost(pid, kid)
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "clear_proxy":
|
||||||
|
key.proxy = None
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "set_proxy":
|
||||||
|
key.proxy = self.body.payload
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
elif self.body.action == "regenerate_fingerprint":
|
||||||
|
key.fingerprint = generate_fingerprint(seed=None)
|
||||||
|
affected += 1
|
||||||
|
|
||||||
|
if self.body.action in {
|
||||||
|
"enable",
|
||||||
|
"disable",
|
||||||
|
"regenerate_fingerprint",
|
||||||
|
"clear_proxy",
|
||||||
|
"set_proxy",
|
||||||
|
}:
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
except Exception as exc:
|
||||||
|
db.rollback()
|
||||||
|
logger.error("batch action commit failed: {}", exc)
|
||||||
|
return BatchActionResponse(affected=0, message=f"commit failed: {exc}")
|
||||||
|
|
||||||
|
admin_name = context.user.username if context.user else "admin"
|
||||||
|
affected_ids = [str(k.id)[:8] for k in keys]
|
||||||
|
|
||||||
action_labels = {
|
action_labels = {
|
||||||
"enable": "enabled",
|
"enable": "enabled",
|
||||||
@@ -1086,9 +1108,6 @@ class AdminBatchActionKeysAdapter(AdminApiAdapter):
|
|||||||
"clear_proxy": "proxy cleared",
|
"clear_proxy": "proxy cleared",
|
||||||
"set_proxy": "proxy set",
|
"set_proxy": "proxy set",
|
||||||
}
|
}
|
||||||
|
|
||||||
admin_name = context.user.username if context.user else "admin"
|
|
||||||
affected_ids = [str(k.id)[:8] for k in keys]
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Pool batch action by {}: provider={}, action={}, affected={}, key_ids={}",
|
"Pool batch action by {}: provider={}, action={}, affected={}, key_ids={}",
|
||||||
admin_name,
|
admin_name,
|
||||||
|
|||||||
Reference in New Issue
Block a user