Skip to content

后端资源管理问题:LLM 连接不复用 / WAL 无限增长 / 事件循环反复创建 / 内存泄漏 #152

@liurunlin010507-ship-it

Description

来源

后端资源管理问题调研报告 (benchmark/docs/backend_resource_investigation.md)


问题一:LLM 调用越来越慢

用户体验

连续运行中,每次 LLM 调用耗时逐步增长,到第 50-100 次后额外多出 50-200ms 网络延迟。Windows 上可能出现 WSAENOBUFS(端口耗尽)导致请求直接失败。

根因

1. LLM Provider 每次请求重建,HTTP 连接池不复用 严重度:严重

DynamicLLMServiceprovider_factory.py:74-75)在每次 generate()stream_generate() 调用时通过 _resolve_provider() 创建全新 provider 实例。每个 provider 构造时创建自己的 httpx.AsyncClient,每次调用都经历完整 TCP + TLS 握手。调用结束后 provider 变为不可达对象,httpx.AsyncClient 未被显式关闭(aclose()),内部连接延迟释放。100+ 测试中累计创建 100-300 个 httpx client 实例。

2. Anthropic 流式端点每次创建临时 httpx client 严重度:中

anthropic_provider.py:212-215 中每次流式请求通过 async with httpx.AsyncClient(...) 创建新 client,连接无法跨请求复用。

3. Gemini Provider 每次调用创建临时 client 严重度:中

gemini_provider.py:40-41 同上,每次调用创建并销毁 httpx.AsyncClient

修复方向

  • Provider 实例应缓存复用,不要每次调用重建
  • httpx.AsyncClient 应作为单例或长生命周期对象管理
  • 提供统一的 close() / aclose() 清理接口

问题二:数据库操作延迟逐渐增大

用户体验

前半段数据库操作 <1s,后半段增长到 5s、10s 甚至超时。.wal 文件从几 KB 膨胀到数十 MB。

根因

1. WAL 文件无限增长 严重度:高

WAL checkpoint 仅在应用启动和关闭时触发。长时间运行中,主进程和守护进程并发读写,WAL 持续增长。WAL 越大读性能越差。

2. 守护进程与主进程的 SQLite 并发写竞争 严重度:高

两个进程共享同一 SQLite 数据库文件(WAL 模式),写事务不能并发,后来的写操作被阻塞等待。

3. busy_timeout=30000 导致写排队 严重度:中

写竞争时后来的操作最多等待 30 秒,高负载下延迟逐渐增加。

4. 数据库连接不关闭 严重度:高

DatabaseConnection.close() 只关闭当前线程连接,应用关闭时未调用 close()。长连接可能持有 WAL 锁,阻止 checkpoint 完成。

修复方向

  • 增加定期 WAL checkpoint(如每隔 N 次写操作或定时触发)
  • 应用关闭时确保所有连接正确关闭
  • 考虑写操作的批量化以减少竞争

问题三:章后分析偶尔丢失

用户体验

保存章节后有时能看到分析结果,有时查不到。连续保存多个章节时问题更频繁。

根因

1. BackgroundTaskService 单线程瓶颈 严重度:中

background_task_service.py:76-79 只有一个工作线程处理所有任务。LLM 调用耗时长时后续任务被阻塞。队列满时(maxsize=200)直接丢弃新任务。

2. BackgroundTasks 串行排队 严重度:高

FastAPI 的 BackgroundTasks 在同一进程内串行。Pipeline 中 LLM 调用耗时 10-30 秒,多章节同时保存时后台任务排队堆积。

3. 每次保存章节都创建全新 Pipeline 实例 严重度:高

get_chapter_aftermath_pipeline() 未被 @lru_cache 装饰,每次 PUT 请求创建全新 Pipeline + 5 个 Repository 实例,高并发下 GC 压力显著增加。

4. TripleRepository 裸构造导致重复对象分配 严重度:高

dependencies.py 中出现 5 次以上 TripleRepository() 裸构造,每次创建新实例和额外的 SqliteKnowledgeRepository

修复方向

  • 对 Pipeline 和 Repository 工厂函数加 @lru_cache
  • 增加后台任务工作线程数
  • 队列满时持久化任务而非丢弃

问题四:Autopilot 写作速度越来越慢

用户体验

前几章速度可接受,写到第 20-30 章后每章生成时间明显变长。

根因

1. 守护进程每轮为每个小说创建并销毁事件循环 严重度:高

asyncio.run(daemon._process_novel(novel)) 每轮创建新事件循环。导致 TCP 连接无法复用、SSL 上下文重复创建、DNS 缓存无法利用。

2. 后台工作线程反复创建事件循环 严重度:高

background_task_service.py:157 使用 asyncio.run() 执行异步代码,每次都重新初始化 httpx 资源。

3. 守护进程高频短连接 严重度:中

StoryNodeRepository 每个方法都执行 connect → execute → commit → close 循环,连续写 100+ 章时累计创建/关闭连接数千次。

4. 后台任务重试阻塞工作线程 严重度:低

time.sleep(2 ** attempt) 阻塞工作线程,多个失败任务重试叠加阻塞队列。

修复方向

  • 守护进程使用持久事件循环而非 asyncio.run()
  • 数据库连接复用(连接池或长连接)
  • 重试逻辑改为异步等待而非阻塞 sleep

问题五:SSE 事件流偶尔卡住或堆积

用户体验

前端重连 SSE 时可能看到旧事件堆积或连接无法建立,长时间运行后事件流响应越来越迟钝。

根因

SSE 连接无超时/断线检测 严重度:中

4 个 SSE 端点的 event_generator 使用无限循环,无最大存活时间和客户端断开检测。客户端断开后生成器清理依赖 GC,长时间运行累积大量悬空 asyncio 任务。

修复方向

  • 增加 SSE 连接超时和心跳检测
  • 客户端断开时主动清理资源

问题六:进程内存持续增长

用户体验

长时间运行后 Python 进程 RSS 持续增长,停止操作后内存也不回落。

根因

1. FAISS 向量索引只增不减 严重度:中

ChromaDBVectorStore 的 FAISS 索引在 delete 操作中仅从 metadata 移除记录,索引体积只增不减。

2. 临时对象反复创建 严重度:高

每次保存章节创建全新 Pipeline 和多个 Repository 实例,高并发下 GC 压力持续上升。

修复方向

  • 向量索引定期重建(compact)而非仅标记删除
  • Pipeline/Repository 实例复用

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions