perf: 记账失败列表优化

- 去掉跨库JOIN,改用内存批量ID映射
- 不查failed_reason大TEXT字段
- handled状态直接后台转文本,前端不依赖data映射
This commit is contained in:
yumoqing 2026-06-26 17:08:58 +08:00
parent 329260435c
commit a6efef56e9
2 changed files with 74 additions and 66 deletions

View File

@ -16,10 +16,6 @@ try:
filters['start_date'] = params_kw.get('start_date')
if params_kw.get('end_date'):
filters['end_date'] = params_kw.get('end_date')
if params_kw.get('filter_userid'):
filters['filter_userid'] = params_kw.get('filter_userid')
if params_kw.get('filter_llmid'):
filters['filter_llmid'] = params_kw.get('filter_llmid')
try:
page = int(params_kw.get('page', 1))
@ -34,52 +30,37 @@ try:
ns = {}
if filters.get('userorgid'):
conditions.append("f.userorgid=${userorgid}$")
conditions.append("userorgid=${userorgid}$")
ns['userorgid'] = filters['userorgid']
if filters.get('llmid'):
conditions.append("f.llmid=${llmid}$")
conditions.append("llmid=${llmid}$")
ns['llmid'] = filters['llmid']
if filters.get('handled') is not None:
conditions.append("f.handled=${handled}$")
conditions.append("handled=${handled}$")
ns['handled'] = filters['handled']
if filters.get('start_date'):
conditions.append("f.use_date>=${start_date}$")
conditions.append("use_date>=${start_date}$")
ns['start_date'] = filters['start_date']
if filters.get('end_date'):
conditions.append("f.use_date<=${end_date}$")
conditions.append("use_date<=${end_date}$")
ns['end_date'] = filters['end_date']
if filters.get('filter_userid'):
conditions.append("(u.username LIKE ${filter_userid}$ OR u.name LIKE ${filter_userid}$)")
ns['filter_userid'] = '%' + filters['filter_userid'] + '%'
if filters.get('filter_llmid'):
conditions.append("(f.llmid LIKE ${filter_llmid}$ OR l.name LIKE ${filter_llmid}$)")
ns['filter_llmid'] = '%' + filters['filter_llmid'] + '%'
where = ""
if conditions:
where = "WHERE " + " AND ".join(conditions)
# 跨库JOIN获取名称
# 不查 failed_reason大 TEXT只查列表需要的字段
sql = f"""
SELECT f.id, f.llmusageid, f.llmid, f.userid, f.userorgid, f.use_date, f.use_time,
f.amount, f.cost, f.failed_reason, f.failed_time, f.retry_count, f.handled,
u.username as userid_text,
o.orgname as userorgid_text,
l.name as llmid_text
FROM llmusage_accounting_failed f
LEFT JOIN {sage_db}.users u ON f.userid = u.id
LEFT JOIN {sage_db}.organization o ON f.userorgid = o.id
LEFT JOIN {llmage_db}.llm l ON f.llmid = l.id
SELECT id, llmusageid, llmid, userid, userorgid, use_date, use_time,
amount, cost, failed_time, retry_count, handled
FROM llmusage_accounting_failed
{where}
ORDER BY f.failed_time DESC
ORDER BY failed_time DESC
"""
count_sql = f"""
SELECT count(*) as cnt
FROM llmusage_accounting_failed f
LEFT JOIN {sage_db}.users u ON f.userid = u.id
LEFT JOIN {sage_db}.organization o ON f.userorgid = o.id
LEFT JOIN {llmage_db}.llm l ON f.llmid = l.id
FROM llmusage_accounting_failed
{where}
"""
count_recs = await sor.sqlExe(count_sql, ns)
@ -89,16 +70,60 @@ LEFT JOIN {llmage_db}.llm l ON f.llmid = l.id
query_sql = sql + f" LIMIT {page_size} OFFSET {offset}"
recs = await sor.sqlExe(query_sql, ns)
rows = []
for r in (recs or []):
d = dict(r)
rows.append(d)
if recs:
# 收集所有需要转换的 ID
llm_ids = list(set(r.llmid for r in recs if r.llmid))
user_ids = list(set(r.userid for r in recs if r.userid))
org_ids = list(set(r.userorgid for r in recs if r.userorgid))
result['rows'] = rows
result['total'] = total
result['page'] = page
result['page_size'] = page_size
result['success'] = True
# 批量查询映射(内存中完成)
llm_map = {}
if llm_ids:
llm_recs = await sor.sqlExe(
"SELECT id, name FROM llm WHERE id IN ${ids}$",
{'ids': tuple(llm_ids)}
)
if llm_recs:
llm_map = {r.id: r.name for r in llm_recs}
user_map = {}
if user_ids:
async with db.sqlorContext(sage_db) as sage_sor:
user_recs = await sage_sor.sqlExe(
"SELECT id, username FROM users WHERE id IN ${ids}$",
{'ids': tuple(user_ids)}
)
if user_recs:
user_map = {r.id: r.username for r in user_recs}
org_map = {}
if org_ids:
async with db.sqlorContext(sage_db) as sage_sor:
org_recs = await sage_sor.sqlExe(
"SELECT id, orgname FROM organization WHERE id IN ${ids}$",
{'ids': tuple(org_ids)}
)
if org_recs:
org_map = {r.id: r.orgname for r in org_recs}
# 转换
handled_map = {'0': '未处理', '1': '已处理'}
rows = []
for r in recs:
d = dict(r)
d['llmid_text'] = llm_map.get(r.llmid, r.llmid)
d['userid_text'] = user_map.get(r.userid, r.userid)
d['userorgid_text'] = org_map.get(r.userorgid, r.userorgid)
d['handled_text'] = handled_map.get(r.handled, r.handled)
rows.append(d)
result['rows'] = rows
result['total'] = total
result['page'] = page
result['page_size'] = page_size
result['success'] = True
else:
result['success'] = True
except Exception as e:
result['error'] = str(e)

View File

@ -69,8 +69,7 @@
"id",
"failed_reason"
],
"alters": {
}
"alters": {}
},
"fields": [
{
@ -83,36 +82,30 @@
"label": "使用记录ID"
},
{
"name": "llmid",
"name": "llmid_text",
"title": "模型",
"type": "str",
"length": 32,
"cwidth": 12,
"uitype": "code",
"textField": "llmid_text",
"valueField": "llmid",
"uitype": "str",
"label": "模型"
},
{
"name": "userid",
"name": "userid_text",
"title": "用户",
"type": "str",
"length": 32,
"cwidth": 10,
"uitype": "code",
"valueField": "userid",
"textField": "userid_text",
"uitype": "str",
"label": "用户"
},
{
"name": "userorgid",
"name": "userorgid_text",
"title": "机构",
"type": "str",
"length": 32,
"cwidth": 10,
"uitype": "code",
"valueField": "userorgid",
"textField": "userorgid_text",
"uitype": "str",
"label": "机构"
},
{
@ -158,22 +151,12 @@
"label": "重试"
},
{
"name": "handled",
"name": "handled_text",
"title": "状态",
"type": "str",
"length": 1,
"cwidth": 6,
"uitype": "code",
"data": [
{
"value": "0",
"text": "未处理"
},
{
"value": "1",
"text": "已处理"
}
],
"uitype": "str",
"label": "状态"
}
]
@ -202,4 +185,4 @@
"script": "var dv = bricks.getWidgetById('failed_table', bricks.app.root); if(!dv || !dv.select_row || !dv.select_row.user_data) { alert('请先选中一条记录'); return; } var row = dv.select_row.user_data; if(!row.llmusageid) { alert('记录缺少llmusageid'); return; } var resp = await fetch('{{entire_url('/llmage/api/retry_accounting.dspy')}}?id=' + row.llmusageid); var d = await resp.json(); if(d.success) { alert(d.message); await dv.render({}); } else { alert('失败: ' + d.message); }"
}
]
}
}