Files
planet/docs/technical/zh/backend-datasources-api-performance.md
2026-04-29 17:27:44 +08:00

3.6 KiB
Raw Blame History

DataSources 列表接口性能优化

背景

GET /api/v1/datasources 是数据源管理页面的核心接口,响应慢会直接阻塞页面渲染。

优化前的查询链路

_load_datasource_list_context 按顺序执行以下查询:

序号 函数 查询内容 瓶颈
1 _load_latest_running_tasks collection_tasks 窗口函数stale check 依赖此结果 必须串行
2 _load_latest_completed_tasks collection_tasks 窗口函数(最近完成任务) 串行等待
3 _load_datasource_data_counts COUNT(*) GROUP BY source on collected_data 最慢,全表扫描
4 _load_datasource_endpoint_overrides datasource_configs 简单 SELECT 串行等待

第一阶段:并行化

将 2/3/4 三个互不依赖的查询改为 asyncio.gather + 独立 session 并行执行:

async def _fetch_completed():
    async with async_session_factory() as s:
        return await _load_latest_completed_tasks(s, datasource_ids)

async def _fetch_counts():
    async with async_session_factory() as s:
        return await _load_datasource_data_counts(s, sources)

async def _fetch_overrides():
    async with async_session_factory() as s:
        return await _load_datasource_endpoint_overrides(s, sources)

completed_tasks, data_counts, endpoint_overrides = await asyncio.gather(
    _fetch_completed(), _fetch_counts(), _fetch_overrides(),
)

注意SQLAlchemy AsyncSession 不支持在同一 session 上并发,每个协程必须独立开 session。

第二阶段:删除重量级查询

删除 _load_datasource_data_counts

data_count 字段仅用于前端在"最近采集"列显示 (0条) 的边缘提示,不值得为此维持一次 COUNT(*) GROUP BY 全表扫描。

  • 前端同步移除 (0条) 显示逻辑
  • 移除 BuiltInDataSource 接口中的 data_count 字段

删除 _load_latest_completed_tasks

last_statuslast_run_at 已由 collector 在任务完成时直接更新到 DataSource 模型字段,不需要再 JOIN collection_tasks 获取:

# 优化前:需要查 completed_tasks
last_run_at = datasource.last_run_at or (last_task.completed_at if last_task else None)
last_status = datasource.last_status or (last_task.status if last_task else None)

# 优化后:直接读模型字段
last_run_at = datasource.last_run_at
last_status = datasource.last_status

同步移除 last_records_processed 字段(来源是 completed_tasks列表不显示此字段

优化后的查询链路

datasources SELECT          → 主数据,必须
_load_latest_running_tasks  → 必须(进行中状态 + stale check
_load_datasource_endpoint_overrides → 必须endpoint 覆盖,列表详情和采集器设置需要显示当前有效地址)

3 个查询(原来 5 个后两个顺序执行running tasks 先完成用于 stale checkendpoint overrides 轻量)。

前端 triggerDatasource 双调修复

triggerDatasource 中存在双重 fetchData() 调用:

// 修复前
} else {
    window.setTimeout(() => { fetchData() }, 800)  // 无 task_id 时延迟刷
}
fetchData()  // 总是立即刷 → 与上面的延迟刷重叠

// 修复后(二者互斥)
if (res.data.task_id) {
    fetchData()           // 有 task_id立即刷一次
} else {
    window.setTimeout(fetchData, 800)  // 无 task_id等 800ms 再刷一次
}

相关文件

  • backend/app/api/v1/datasources.py_load_datasource_list_contextlist_datasources
  • frontend/src/pages/DataSources/DataSources.tsxBuiltInDataSource interface、triggerDatasource