21f16e6756
- Split summarizer into summary_generator and summary_persister modules - Refactor pdf_image_extractor to two-phase pipeline with PicoDet layout detection - Add layout_detector service for PicoDet-S_layout_3cls integration - Add exceptions module with ConflictError and NotFoundError - Improve admin dashboard with better statistics and task management - Add design review document with system optimization suggestions - Add new tests for crawler, pdf_downloader, pipeline, and summary_utils - Update dependencies and configuration - Clean up dead code and improve error handling
469 lines
14 KiB
Markdown
469 lines
14 KiB
Markdown
# 项目设计审查与优化建议
|
||
|
||
本文档汇总对当前项目的系统设计、流程设计和代码结构的审查结论。重点不在局部代码风格,而在后续稳定运行、失败恢复、数据一致性、可维护性和扩展性。
|
||
|
||
## 总体评价
|
||
|
||
项目当前结构整体清晰:FastAPI 路由层较薄,主要业务放在 `app/services/`;抓取、总结、搜索、个人数据、管理后台等能力已有模块化拆分;SQLite + FTS5 对本地个人论文导览站是合理选择。
|
||
|
||
主要风险集中在以下几类:
|
||
|
||
- 长任务生命周期没有统一抽象。
|
||
- Pipeline 不是显式状态机,阶段结果难以恢复和追踪。
|
||
- 数据库、文件系统、FTS5、ChromaDB 之间存在多处手工同步。
|
||
- Web、CLI、Scheduler 三个入口的业务策略存在分叉风险。
|
||
- 失败恢复和可观测性不足。
|
||
- 部分同步阻塞任务混入 async Web 流程。
|
||
|
||
## 高优先级问题
|
||
|
||
### 1. 缺少统一的任务系统
|
||
|
||
当前有三套入口都能触发重任务:
|
||
|
||
- Web 管理后台:`/admin/trigger-pipeline`、`/admin/summarize`
|
||
- APScheduler:每日自动 pipeline
|
||
- CLI:`app.cli crawl/summarize`
|
||
|
||
它们共享部分 service,但没有统一的“任务创建、排队、运行、重试、取消、状态查询、失败恢复”模型。
|
||
|
||
影响:
|
||
|
||
- Web 请求会长时间挂住。
|
||
- CLI、Web、Scheduler 的行为可能逐渐分叉。
|
||
- 任务卡死后只能看到粗粒度的 running lock。
|
||
- 前端难以展示阶段进度。
|
||
- 后续增加重跑、取消、恢复、并发限制会越来越复杂。
|
||
|
||
建议:
|
||
|
||
引入统一 Job 模型:
|
||
|
||
```text
|
||
入口层 -> 创建 Job -> Worker 执行 Job -> JobEvent 记录进度 -> 页面/API 查询状态
|
||
```
|
||
|
||
建议任务类型:
|
||
|
||
- `crawl_daily`
|
||
- `summarize_one`
|
||
- `summarize_batch`
|
||
- `pipeline_daily`
|
||
- `refresh_upvotes`
|
||
- `delete_range`
|
||
- `reindex_fts`
|
||
- `reindex_chroma`
|
||
- `extract_images`
|
||
|
||
建议表结构方向:
|
||
|
||
- `jobs`: 任务主记录,包含 `id/type/status/owner/created_at/started_at/completed_at/error`
|
||
- `job_events`: 阶段事件,包含 `job_id/stage/status/message/payload_json/created_at`
|
||
- `paper_processing_runs`: 单篇论文处理记录,适合跟踪总结、图片提取、索引状态
|
||
|
||
### 2. Pipeline 不是显式状态机
|
||
|
||
当前 pipeline 基本是线性函数:
|
||
|
||
```text
|
||
crawl -> summarize -> cleanup
|
||
```
|
||
|
||
阶段输出没有被建模成可查询、可重放、可补偿的状态。
|
||
|
||
影响:
|
||
|
||
- crawl 成功但 summarize 部分失败时,整体状态表达不够精确。
|
||
- cleanup 失败是否影响 pipeline 成败语义不清。
|
||
- “今天无数据回退昨天”是隐式逻辑,不是可配置策略。
|
||
- 图片提取、ChromaDB 索引失败被吞掉,用户无法知道哪些增强能力缺失。
|
||
|
||
建议:
|
||
|
||
将 pipeline 改为显式阶段状态:
|
||
|
||
```text
|
||
created
|
||
-> crawling
|
||
-> crawled
|
||
-> summarizing
|
||
-> summarized | summarized_partial
|
||
-> postprocessing
|
||
-> completed | failed | cancelled
|
||
```
|
||
|
||
每个阶段记录:
|
||
|
||
- 输入参数
|
||
- 输出统计
|
||
- 错误类型
|
||
- 错误摘要
|
||
- 开始/结束时间
|
||
- 是否可重试
|
||
|
||
这样可以支持只重跑失败阶段,例如只重跑 ChromaDB 索引、只重跑图片提取、只重跑失败论文总结。
|
||
|
||
### 3. 数据生命周期没有清晰分层
|
||
|
||
项目中至少有四类数据:
|
||
|
||
- 主数据:`papers`、`authors`、`tags`、`summaries`、用户笔记/收藏/阅读状态
|
||
- 派生索引:FTS5、ChromaDB
|
||
- 长期资产:`data/papers/{arxiv_id}` 下的 summary、raw output、images
|
||
- 临时资产:`data/tmp/{arxiv_id}` 下的 PDF、源码、中间文件
|
||
|
||
现在这些数据由不同流程手动维护。删除、总结、重建索引、清理临时目录都分散在不同模块中。
|
||
|
||
影响:
|
||
|
||
- DB 与文件系统可能不一致。
|
||
- DB 与 FTS5/ChromaDB 可能不一致。
|
||
- 删除流程中一部分资源删除成功、一部分失败时难以恢复。
|
||
- 很难判断某篇论文是否“完整可用”。
|
||
|
||
建议:
|
||
|
||
明确数据权威源:
|
||
|
||
- DB 是权威源。
|
||
- FTS5、ChromaDB、summary.json、raw_output.txt、images 都应视为可重建派生物。
|
||
- 删除主数据优先保证 DB 状态正确,派生物清理可以异步补偿。
|
||
- 提供派生数据重建任务。
|
||
|
||
建议增加命令或任务:
|
||
|
||
```text
|
||
rebuild-derived --fts
|
||
rebuild-derived --chroma
|
||
rebuild-derived --files
|
||
rebuild-derived --images
|
||
```
|
||
|
||
### 4. 数据库与文件系统存在双写一致性风险
|
||
|
||
总结持久化流程会写文件、写 DB、更新状态、提取图片、写 ChromaDB。任一步失败都可能留下不一致状态。
|
||
|
||
典型风险:
|
||
|
||
- 文件已写入,但 DB 未提交。
|
||
- DB 标记 done,但 summary.json 缺失。
|
||
- DB done,但图片未提取或 ChromaDB 未索引。
|
||
- 重新总结时旧图片或旧 figures 残留。
|
||
|
||
建议:
|
||
|
||
- 将结构化总结以 DB 为准,JSON 文件作为导出/缓存。
|
||
- 文件写入使用临时文件加原子 rename。
|
||
- DB 中记录派生物状态,例如:
|
||
- `summary_persisted_at`
|
||
- `fts_indexed_at`
|
||
- `chroma_indexed_at`
|
||
- `images_extracted_at`
|
||
- `derived_error`
|
||
- 提供一致性检查任务,扫描 DB 与文件/索引差异并修复。
|
||
|
||
### 5. 失败恢复设计偏弱
|
||
|
||
当前失败主要依赖:
|
||
|
||
- `summary_status.status`
|
||
- `retry_count`
|
||
- `task_locks`
|
||
- `crawl_logs`
|
||
|
||
但对以下情况支持不足:
|
||
|
||
- 进程崩溃后 `processing` 永久残留。
|
||
- `task_locks` 中 running lock 永久残留。
|
||
- 任务执行到一半,部分文件或索引已经写入。
|
||
- 某些后处理失败但主任务显示成功。
|
||
|
||
建议:
|
||
|
||
- `task_locks` 增加 `heartbeat_at` 或 `expires_at`。
|
||
- 应用启动时扫描 stale running task,标记为 `failed/stale`。
|
||
- `summary_status` 增加更细阶段字段,例如:
|
||
- `downloading_pdf`
|
||
- `generating`
|
||
- `validating`
|
||
- `persisting`
|
||
- `extracting_images`
|
||
- `indexing`
|
||
- 区分主流程失败和派生增强失败。
|
||
|
||
### 6. 长任务直接跑在 Web 请求里
|
||
|
||
管理端触发 pipeline、批量总结、刷新 upvotes、删除数据时,会在请求生命周期内直接执行重任务。
|
||
|
||
影响:
|
||
|
||
- HTTP 请求容易超时。
|
||
- 用户关闭页面后任务状态不清晰。
|
||
- Web 进程被重任务占用。
|
||
- 后续很难做取消、进度展示和失败恢复。
|
||
|
||
建议:
|
||
|
||
- Web 只创建 job 并返回 `task_id`。
|
||
- 后台 worker 执行任务。
|
||
- 管理页面轮询 `/admin/jobs/{id}` 或使用 SSE 展示进度。
|
||
|
||
## 中优先级问题
|
||
|
||
### 7. FTS5 索引手工维护,容易漂移
|
||
|
||
`papers_fts` 是独立虚拟表。插入论文、更新总结、删除论文时手动同步。
|
||
|
||
影响:
|
||
|
||
- 已有论文的 title、abstract、authors、tags 更新时可能不同步。
|
||
- AI 标签新增后,如果没有统一重建,搜索结果可能缺字段。
|
||
- 删除或回滚失败后索引可能残留。
|
||
|
||
建议:
|
||
|
||
- 封装统一的 `reindex_paper(db, paper_id)`。
|
||
- 所有修改论文、标签、总结的路径最后调用它。
|
||
- 提供全量 `reindex_fts` 任务。
|
||
- 或用 SQLite trigger 维护基础字段,但总结字段仍建议通过统一函数更新。
|
||
|
||
### 8. ChromaDB 索引缺少系统级一致性策略
|
||
|
||
ChromaDB 当前是可选增强能力,索引失败会被日志吞掉,不影响总结主流程。
|
||
|
||
影响:
|
||
|
||
- 语义搜索可用性不透明。
|
||
- 某些论文 DB done,但未进入 ChromaDB。
|
||
- 删除或重建时可能出现残留索引。
|
||
|
||
建议:
|
||
|
||
- 将 ChromaDB 明确视为派生索引。
|
||
- DB 中记录 `chroma_indexed_at` 和 `chroma_error`。
|
||
- 提供 `reindex_chroma` 任务。
|
||
- 搜索页或管理后台展示语义索引健康状态。
|
||
|
||
### 9. 同步阻塞操作混入 async 主流程
|
||
|
||
部分 async 函数内部调用同步阻塞操作,例如 PDF 下载使用 `requests`,图片提取和 PDF 解析也都是同步重任务。
|
||
|
||
影响:
|
||
|
||
- FastAPI event loop 可能被阻塞。
|
||
- 多篇并发总结时吞吐不可控。
|
||
- Web 服务响应受后台任务影响。
|
||
|
||
建议:
|
||
|
||
- 最佳方案:重任务移到 worker 进程。
|
||
- 过渡方案:对同步阻塞段使用 `asyncio.to_thread()`。
|
||
- PDF 下载统一使用 `httpx.AsyncClient` 或明确放入同步 worker。
|
||
|
||
### 10. 调度策略和业务流程耦合
|
||
|
||
Scheduler 固定每日 pipeline 加 30 分钟 upvote refresh;pipeline 内部固定“今天无数据回退昨天”。
|
||
|
||
影响:
|
||
|
||
- 策略不易测试和调整。
|
||
- CLI、Web、Scheduler 可能各自实现不同 fallback。
|
||
- 后续支持日期范围、补抓、只总结新增论文会变复杂。
|
||
|
||
建议将策略配置化:
|
||
|
||
- `crawl_target_policy`: `today` / `today_then_yesterday` / `date_range`
|
||
- `summarize_scope`: `new_only` / `pending_and_failed` / `date_range`
|
||
- `cleanup_policy`: `after_success` / `always` / `manual`
|
||
- `upvote_refresh_window_days`
|
||
- `pipeline_on_partial_failure`: `continue` / `stop`
|
||
|
||
### 11. 内嵌 APScheduler 对部署形态敏感
|
||
|
||
当前调度器嵌入 Web 进程,且多 worker 时只是打印警告。
|
||
|
||
影响:
|
||
|
||
- 多 worker、多进程、reload 模式下可能重复或漏跑任务。
|
||
- Web 进程重启会影响调度可靠性。
|
||
- 调度状态和任务执行状态耦合。
|
||
|
||
建议:
|
||
|
||
- 本地单机可以保留内嵌调度器。
|
||
- 长期运行建议拆为独立 scheduler 进程。
|
||
- scheduler 只负责创建 job,不直接执行重任务。
|
||
- Web 管理后台只展示 scheduler/job 状态。
|
||
|
||
### 12. 运行时迁移能力不足
|
||
|
||
当前 `_migrate()` 只支持补列。
|
||
|
||
影响:
|
||
|
||
- 无法可靠处理索引、约束、字段改名、数据回填。
|
||
- 数据库结构演进不可追踪。
|
||
- 生产或长期本地数据升级风险高。
|
||
|
||
建议:
|
||
|
||
- 引入 Alembic。
|
||
- 将 FTS5、索引、部分约束和数据回填纳入迁移脚本。
|
||
- 启动时不要静默做复杂迁移,改为显式执行迁移命令。
|
||
|
||
## 低到中优先级问题
|
||
|
||
### 13. 配置校验不足
|
||
|
||
当前配置字段主要是裸类型,缺少合法值和组合校验。
|
||
|
||
风险示例:
|
||
|
||
- `SUMMARY_BACKEND` 填错。
|
||
- `SUMMARY_PDF_MODE` 填错。
|
||
- `SCHEDULE_MINUTE + 30` 超过 59。
|
||
- `APP_WORKERS > 1` 且 `SCHEDULER_ENABLED=true`。
|
||
- `CHROMA_ENABLED=true` 但 embedding 配置缺失。
|
||
|
||
建议:
|
||
|
||
- 使用 Pydantic validator 校验枚举值和组合条件。
|
||
- 对危险组合启动时报错,而不是只 warning。
|
||
|
||
### 14. 私有函数跨模块调用说明模块边界不稳定
|
||
|
||
例如 `summarizer.py` 调用 `_generate_with_retry`、`_persist_summary` 等下划线函数。
|
||
|
||
影响:
|
||
|
||
- 模块公开边界不清晰。
|
||
- 后续重构容易误伤。
|
||
- 测试和替换后端不方便。
|
||
|
||
建议:
|
||
|
||
- 为 summary 生成、持久化、后处理定义明确公开 API。
|
||
- 下划线函数保留为模块内部实现细节。
|
||
- 引入更明确的 service 类或函数边界,例如:
|
||
- `SummaryGenerator.generate()`
|
||
- `SummaryRepository.save()`
|
||
- `DerivedAssetBuilder.extract_images()`
|
||
|
||
### 15. 外部依赖缺少统一 adapter
|
||
|
||
项目依赖多个外部系统:
|
||
|
||
- HuggingFace API
|
||
- arXiv PDF/source 下载
|
||
- `pi` CLI
|
||
- `claude` CLI
|
||
- Embedding API
|
||
- ChromaDB
|
||
- ONNX layout detector
|
||
|
||
建议:
|
||
|
||
- 为每类外部依赖定义 adapter。
|
||
- 统一 timeout、retry、error classification、metrics。
|
||
- 测试中替换 adapter,而不是 patch 深层函数。
|
||
|
||
### 16. 删除流程事务边界不理想
|
||
|
||
删除流程中同时删除 FTS5、ChromaDB、本地文件、临时文件、ORM 数据。
|
||
|
||
影响:
|
||
|
||
- 文件删除成功后 DB rollback,会造成数据仍在但文件丢失。
|
||
- DB 删除成功后 ChromaDB 删除失败,会造成残留索引。
|
||
- 单篇失败时 rollback 可能影响之前 flush 的状态,逻辑不直观。
|
||
|
||
建议:
|
||
|
||
- 删除主数据与删除派生物分两阶段。
|
||
- DB 删除成功后创建派生清理 job。
|
||
- 派生物清理失败可重试,不影响主数据一致性。
|
||
|
||
## 建议目标架构
|
||
|
||
如果项目定位是个人本地工具,可以采用轻量目标架构:
|
||
|
||
```text
|
||
FastAPI Web
|
||
- 页面和 API
|
||
- 管理后台
|
||
- 创建 job
|
||
- 查询 job 状态
|
||
|
||
Scheduler
|
||
- 按策略创建 job
|
||
- 不直接跑重任务
|
||
|
||
Worker
|
||
- crawl
|
||
- summarize
|
||
- PDF download
|
||
- image extraction
|
||
- FTS/Chroma reindex
|
||
- cleanup/delete
|
||
|
||
Storage/Repository
|
||
- DB 权威数据
|
||
- 文件资产管理
|
||
- 派生索引重建
|
||
- 一致性检查
|
||
```
|
||
|
||
如果暂时不想引入独立 worker,也可以先在单进程内实现 Job 表和后台任务执行器。这样至少能统一任务状态和恢复机制。
|
||
|
||
## 推荐实施顺序
|
||
|
||
### 第一阶段:先解决任务与状态
|
||
|
||
目标:降低长任务不可控风险。
|
||
|
||
- 新增 `jobs` 和 `job_events` 表。
|
||
- Web 管理动作改为创建 job 并返回 task_id。
|
||
- Scheduler 改为创建 job。
|
||
- CLI 复用同一套 job runner。
|
||
- 加 stale running job 恢复逻辑。
|
||
|
||
### 第二阶段:统一派生数据管理
|
||
|
||
目标:降低 DB、文件、FTS、Chroma 不一致风险。
|
||
|
||
- 明确 DB 为权威源。
|
||
- 封装 `reindex_paper()`。
|
||
- 增加 `reindex_fts`、`reindex_chroma` 任务。
|
||
- 增加派生数据状态字段或健康检查。
|
||
- 删除流程改为 DB 主删除 + 派生清理 job。
|
||
|
||
### 第三阶段:改造 pipeline 为状态机
|
||
|
||
目标:让任务可恢复、可补偿、可观测。
|
||
|
||
- 拆分 pipeline stages。
|
||
- 每个 stage 记录输入/输出/错误/耗时。
|
||
- 支持从失败阶段重跑。
|
||
- 将 fallback 策略配置化。
|
||
|
||
### 第四阶段:提升运行可靠性
|
||
|
||
目标:长期运行更稳。
|
||
|
||
- 引入 Alembic。
|
||
- 配置加 validator。
|
||
- 同步阻塞操作移出 Web event loop。
|
||
- 外部依赖 adapter 化。
|
||
- 管理后台展示任务、派生索引、失败类型、耗时统计。
|
||
|
||
## 最小可行改造方案
|
||
|
||
如果只做最小但收益最大的改动,建议优先做这三项:
|
||
|
||
1. 增加统一 Job 表和 job runner。
|
||
2. DB 作为权威源,FTS/Chroma/文件全部按派生物处理。
|
||
3. 增加 stale task 恢复和派生数据重建命令。
|
||
|
||
这三项能显著降低后续复杂度,也不会强迫项目马上拆成多个服务。
|
||
|