Skip to content

[Bug] Web 首屏并发请求耗尽 SQLAlchemy 连接池,导致空 UI 约 30 秒 #73

Description

@tedzhouhk

问题描述

已登录用户打开 Web 端(例如 /app/overview)时,应用外壳会立即显示,但首屏约 30 秒内处于近似空状态:

  • 右上角没有用户头像
  • 账本选择器和账本数据未加载
  • 约 30 秒后,头像、账本及页面数据才集中出现
  • 部分首屏 API 会在等待 30 秒后返回 500

这不是静态资源或反向代理加载慢:HTML/JS 和应用外壳均能立即显示,阻塞发生在 origin 后端 API。

环境

  • Docker 部署,使用当前 latest 镜像
  • 单个 Uvicorn worker
  • SQLite,WAL 模式
  • 数据库主文件约 824 KiB
  • 14 条 transaction projection
  • 239 条 sync change

数据量很小,因此不像是大数据量导致的慢查询。

后端日志(已匿名化)

同一轮首屏加载中可观察到:

GET /api/v1/profile/me → 200 30107.3ms
GET /api/v1/read/ledgers → 500 30086.8ms
GET /api/v1/sync/pull → 200 30371.0ms

sqlalchemy.exc.TimeoutError:
QueuePool limit of size 5 overflow 10 reached,
connection timed out, timeout 30.00

同一时刻,accounts、tags、categories、budgets、analytics 等多个请求也会等待约 30 秒;一部分返回 500,另一部分在连接释放后返回 200。拥塞解除后,同类请求通常只需数毫秒到几十毫秒。

初步分析

服务端创建 engine 时没有显式配置连接池:

engine_kwargs: dict = {"pool_pre_ping": True}
engine = create_engine(settings.database_url, **engine_kwargs)

因此使用 SQLAlchemy QueuePool 默认参数:

  • pool_size=5
  • max_overflow=10
  • pool_timeout=30

Web 首屏会并行请求 profile、ledgers、admin probe、categories、accounts、tags、budgets,以及多组 analytics;同时还可能建立 WebSocket 并执行 sync pull。并发加载时连接池被占满,后续请求恰好等待默认的 30 秒后超时。

还需要进一步确认连接被长期占用的具体原因,例如:

  • FastAPI 同步 generator dependency 的 session 释放是否在高并发下发生线程池/连接池互相等待
  • 某个请求或流式响应是否延迟释放 session
  • 首屏请求是否存在重复、无上限并发或可以合并的查询

前端目前以 ledgers=[]profileMe=null 直接渲染,数据返回前会隐藏账本选择器和头像,也没有 shell loading 状态,因此后端等待表现为“空 UI”。

复现步骤

  1. 使用 SQLite 启动 Docker 服务
  2. 已登录状态下打开或刷新 Web Overview
  3. 观察 Network 和后端日志
  4. 在问题出现时,多个数据库 API 等待约 30 秒,并伴随上述 QueuePool TimeoutError
  5. 连接释放后再次调用相同 API,响应恢复到毫秒级

期望行为

  • profile 和 ledgers 应在首屏快速返回,不能因首页其他查询而等待连接池超时
  • 首屏请求不应触发 QueuePool exhaustion 或 500
  • profile/ledgers 未完成时应显示明确的加载状态,而不是空账户/空账本 UI

建议方向

  1. 先定位并修正数据库 session/connection 的持有与释放时机
  2. 为 SQLite 选择合适的 pool 策略,而不是仅增大 pool_size
  3. 对 Web 首屏请求做去重、限流或分阶段加载,优先 profile 和 ledgers
  4. 增加并发回归测试:并行发起超过 15 个首屏读请求,断言不会等待 30 秒或返回 500
  5. 为 AppShell 增加显式 loading/error 状态

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions