perf: CTE任务树加载器 + MOM跨库查询缓存

消除两个核心N+1性能瓶颈:

1. CTE任务树加载器 (task_tree_loader.py)
   - PostgreSQL Recursive CTE一次性加载完整任务树
   - 无论树深度多大,仅2条SQL(CTE + records selectinload)
   - set_committed_value安全注入,避免Session脏数据
   - 修复add_task_record双重加载问题
   - 移除get_all_tasks中冗余的selectinload(child_tasks)

2. MOM跨库查询缓存 (mom_cache.py)
   - 零依赖TTL内存缓存(threading.RLock + time.monotonic)
   - 参数化ANY(:user_ids)替代OR拼接LIKE(防注入)
   - get_all_products中3次调用共享缓存,2h TTL内零跨库查询
This commit is contained in:
2026-08-12 12:03:02 +08:00
parent 991d713777
commit cc199081f9
4 changed files with 343 additions and 91 deletions

View File

@ -125,39 +125,9 @@ async def _get_task_or_404(db: AsyncSession, task_id: uuid.UUID) -> Task:
async def _get_task_with_children_recursive(db: AsyncSession, task_id: uuid.UUID) -> Task:
"""递归加载任务及其所有子孙任务"""
result = await db.execute(
select(Task)
.options(
selectinload(Task.child_tasks),
selectinload(Task.records),
)
.where(Task.id == task_id)
)
task = result.scalar_one_or_none()
if not task:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"任务不存在: {task_id}",
)
# 递归加载每一层子任务
async def _load_children(t: Task):
for child in t.child_tasks:
child_result = await db.execute(
select(Task)
.options(
selectinload(Task.child_tasks),
selectinload(Task.records),
)
.where(Task.id == child.id)
)
refreshed_child = child_result.scalar_one()
t.child_tasks[t.child_tasks.index(child)] = refreshed_child
await _load_children(refreshed_child)
await _load_children(task)
return task
"""使用 PostgreSQL Recursive CTE 一次性加载任务及其所有子孙任务(消除 N+1"""
from app.services.task_tree_loader import load_task_tree_by_root
return await load_task_tree_by_root(db, task_id)
def _to_flat_response(task: Task) -> TaskResponse:
@ -326,7 +296,6 @@ async def get_all_tasks(
) -> TaskListResponse:
"""获取任务列表,可按产品/负责人筛选"""
stmt = select(Task).options(
selectinload(Task.child_tasks),
selectinload(Task.records),
selectinload(Task.product),
)
@ -1085,5 +1054,6 @@ async def add_task_record(
await db.commit()
await db.refresh(record)
# 重新加载 task 带上新 record
return await get_task(db, task_id)
# 手动追加新 record避免二次加载整棵树task 已在 L1051 由 CTE 完整加载)
task.records.append(record)
return _to_response(task)