Skip to content

Dashboard 后端稳定性:修两处连接/线程泄漏 + 首页指标悬停说明 #32

Description

@Protocol-zero-0

Dashboard 后端稳定性:修两处连接/线程泄漏 + 首页指标加悬停说明

部署上线后,看板在真实访问下会"数据转圈加载不出、整页卡死"。定位下来是两处后端泄漏,前端 Phase 1(#31)未触及。本任务修这两处,并顺带给首页 9 张指标卡加一句话悬停说明(纯前端、已逐项核对字段含义)。

代码定位若行号漂移,以函数名/字段名/语义为准。


Part 1 · P0 卡死(后端,必须修)

泄漏 A — SSE 占满工作线程

  • web/app.py:1149 /api/eventswhile True: yield ...; time.sleep(2) 的同步生成器,以 text/event-stream 返回。连接存活期间一直占住一个 waitress 线程,永不释放。
  • 前端 web/static/js/app.js:188 new EventSource('/api/events') 加载即开;断线自动重连。
  • waitress 线程池有限(main.py:92,已临时从 8 调到 64,仅缓解)。每个标签页/重连占 1 线程,池满后所有请求永久排队 → 卡死。

改法(推荐:短轮询,根治):

  • 后端:把 /api/events 改成立即返回即关闭的普通接口 GET /api/events?since=<seq>,一次性返回 since 之后的新事件 JSON,不再 while True
  • 前端:去掉 EventSource,改 setInterval(fetchEvents, 2000),带上次 seq
  • main.pythreads 可恢复到合理值(修好后不应再依赖 64 这个临时缓解);若保留 SSE 方案则必须保证 SSE 不占数据库连接、且标签页隐藏时 eventSource.close()、重连指数退避——但短轮询更省心,优先短轮询。

泄漏 B — 读接口不结束事务,连接卡在 idle in transaction

这是 /api/stats 实测 3/3 超时 25s 的真正原因,和泄漏 A 是同一类问题的两个面。

  • db/database.py:118 连接 autocommit=False,每线程一条、长期复用、从不关闭(get_conn() 线程局部)。
  • db/database.py:825/835fetchone/fetchallexecute、从不 commit/rollback。纯读请求(看板绝大多数接口)跑完第一个 SELECT 后,事务一直开着不关。
  • 后果:postgres 里堆出一批 idle in transaction 连接(线上实测约 12 条),占连接槽、持旧快照。线程调到 64 后最多能攒 64 条卡住的连接 → 顶满连接数/卡锁 → 落到冷线程的 /api/stats 永久排队。

改法(推荐:请求结束统一释放事务):

  • web/app.py@app.teardown_request,请求结束时 db.rollback()(包 try/except,鲁棒)。读路径本就没东西可丢,写路径返回前已 commit(),此处 rollback 是空操作。一个钩子即可清掉所有 idle-in-transaction。
  • 连接异常时也要 rollback,避免坏连接被复用(get_conn() 复用前可加一次健康检查或异常时重建)。

Part 2 · 首页指标悬停说明(前端,小改)

首页 9 张指标卡只有名字没有解释,用户会误读(尤其"文献"显示的是已处理数不是入库总数;"研究洞见"和"深度发现"是两张不同的表)。给每张卡加 title 悬停说明,走现有 i18n(data-i18n-title,applyI18n 已支持)。

字段含义已逐项核对(db/database.py:get_stats() + app.js:165-173 绑定),按下表加 tip(新增 i18n 键,en/zh 键集合必须相等):

卡片 (id) 绑定字段 zh tip en tip
文献 statPapers papers_processed 已抽取/解析完成的文献数(不含仅入库未处理的) Papers extracted/parsed (excludes fetched-but-unprocessed)
基准结果 statResults results_total 基准实验产出的结果记录条数 Benchmark result records produced by experiments
研究领域 statTaxonomy taxonomy_nodes_total 研究领域分类树的节点数 Nodes in the research-area taxonomy tree
矛盾 statContradictions contradictions_total 从文献中发现的互相矛盾的论断对数 Pairs of conflicting claims found across papers
研究洞见 statInsights insights_total 研究洞见条数(insights 表;与"深度发现"是不同的表) Research insights (insights table; distinct from Discoveries)
Token statTokens tokens_consumed 处理文献累计消耗的 LLM token 总量 Total LLM tokens consumed processing papers
实验运行 statExperiments experiment_runs_total 实验运行的总次数 Total experiment runs
深度发现 statDeepDiscoveries deep_insights_total 可发展成论文的深度发现条数(deep_insights 表;与"研究洞见"是不同的表) Discoveries that can grow into papers (deep_insights table; distinct from Research Insights)
投稿包 statCompletePapers submission_bundles_total 打包完成、可投稿的完整论文数 Completed paper bundles ready for submission

实现:在 web/templates/index.html 每张 .stat-card 上加 data-i18n-title="overview.xxx.tip"(或在卡内 label 上),在 web/static/js/i18n.js 的 en/zh 各加对应 *.tip 键。


Allowed / Forbidden

  • Allowed: web/app.py(加 teardown、改 /api/events)、main.py(threads 值)、db/database.py(连接健康/rollback 相关,仅限本任务所需)、web/static/js/app.js(SSE→轮询)、web/templates/index.html + web/static/js/i18n.js(tip)、相关测试。
  • Forbidden: 不改其它后端 API 的返回字段/语义;不改统计口径(各 COUNT 的 SQL 不动,只解释含义);不动 agents//orchestrator//contracts/ 的业务逻辑;不引前端框架;不删 tab/功能;不碰数据库 schema。

执行约定

  1. 先加失败/回归测试,再改代码。
  2. 每部分最小必要改动,不顺手重构。
  3. 不弱化现有测试与门禁。
  4. PR 描述列:changed files / tests added / tests run / before-after / non-goals。

Test Execution Protocol

  1. 基线:pytest tests/test_web_app.py -q
  2. 新增本任务测试(见验收)
  3. 实现
  4. 跑本任务测试 + 再跑基线
  5. PR 贴实际命令与结果

验收(可机验 + 线上复核)

Part 1:

  1. 加压一批只读请求后,SELECT count(*) FROM pg_stat_activity WHERE state='idle in transaction' → 归零(或个位且不随请求数增长)。
  2. /api/stats 在有其它长连接/并发读时,p95 < 1s、稳定 200(不再 25s 超时)。
  3. SSE 改造后:持续打开页面/多标签不再单调吃光 waitress 线程;若保留 SSE,需证明其不占数据库连接。
  4. python -c "import web.app" ok;pytest tests/test_web_app.py -q 全绿;Flask 起来后 //api/stats/api/events(或新 ?since=)均 200。

Part 2:
5. i18n.js 新增 9 个 *.tip 键,en/zh 键集合仍完全相等(沿用现有 parity 测试)。
6. 9 张卡都有可见 title/tooltip,文案与上表一致;切换 EN/ZH 时 tip 跟随。

实现红线: idle-in-transaction 必须真归零(不是把阈值调大蒙混);/api/stats 必须真快(不是加 cache 把旧值返回了事——除非明确实现并说明 cache 策略);tip 的 en/zh 键必须对齐。

Non-goals

重部署

host 上 git fetch && git checkout <分支>sudo systemctl restart deepgraph-web → 复核 /api/stats 秒回、多标签不卡死、指标卡有悬停说明。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions