夜雨聆风学习资料网

ARTICLE · 1029695

33.长任务状态管理:如何让用户看到文档解析进度?

33.长任务状态管理:如何让用户看到文档解析进度?

长任务状态管理:如何让用户看到文档解析进度?

码海寻道 · 大模型、智能体与 RAG 工程组件系列第 33 篇

在这里插入图片描述

文件上传后,用户最关心的不是后台使用了 RabbitMQ 还是 Kafka,而是“现在处理到哪一步了,什么时候可以搜索”。长任务状态管理的目标,是让后台任务可追踪、可重试、可恢复,让前端展示可信进度。

长任务状态应以 PostgreSQL 等持久化事实源为准,Redis 可以缓存进度,SSE/WebSocket 只负责实时通知。通知连接断开后,客户端仍应通过查询接口恢复最新状态,不能把进度只放在推送连接内存中。

一、不要只保存一个 processing

uploaded  → queued  → parsing  → ocr  → chunking  → embedding  → indexing  → ready

失败状态也要明确:

parse_failedocr_failedembedding_failedindex_failedcancelled

只有状态足够细,系统才能知道从哪里重试,也才能给用户准确提示。

每个状态转换都应记录 job_idattemptworker_idupdated_at 和错误摘要,并使用乐观锁或版本号防止旧 Worker 覆盖新状态。

二、数据库状态模型

CREATE TABLE document_jobs (    job_id          uuid PRIMARY KEY,    document_id     uuid NOT NULL,    version         integerNOT NULL,    status          text NOT NULL,    current_stage   text NOT NULL,    progress        numeric(5,2NOT NULLDEFAULT0,    attempt         integerNOT NULLDEFAULT0,    error_code      text,    error_message   text,    started_at      timestamptz,    finished_at     timestamptz,    updated_at      timestamptz NOT NULLDEFAULT now());

progress 是展示信息,status 和 current_stage 才是任务事实。不要只依赖百分比判断任务是否成功。

三、进度怎么计算?

最简单的是阶段加权:

解析 20%OCR  25%切分 15%向量化 25%索引 15%

如果一个阶段包含 1000 页,可以进一步根据已处理数量计算子进度:

阶段进度 = 已完成页数 / 总页数总进度 = 已完成阶段权重 + 当前阶段权重 × 阶段进度

进度不必承诺精确剩余时间。解析器、OCR 和模型调用耗时差异很大,宁可展示“正在处理第 3/5 阶段”,也不要让百分比长时间停在 99%。

四、前端如何获取状态?

轮询

前端每隔几秒请求:

GET /api/documents/{document_id}/jobs/{job_id}

实现简单,适合低频后台任务。

SSE

服务端通过 Server-Sent Events 推送状态更新,适合单向进度通知:

event: progressdata: {"stage":"embedding","progress":65}

WebSocket

适合需要双向实时交互的系统,但连接管理复杂度更高。无论哪种方式,数据库状态都应是最终事实来源,不能只保存在 WebSocket 内存里。

五、任务重试从哪里开始?

不同阶段的重试策略不同:

  • • 解析失败:检查格式后重试或转人工;
  • • OCR 超时:切分图片、降低批次或更换引擎;
  • • Embedding 限流:指数退避并限制并发;
  • • 向量写入失败:使用批次 ID 重试;
  • • 权限错误:通常不应无限重试。

建议记录 attempt、错误类型和最后一次失败时间,避免用户只能看到“处理失败”而无法判断原因。

六、如何保证状态与实际数据一致?

一个常见错误是先把状态写成 ready,再异步写向量。用户看到完成后立即搜索,却找不到内容。

更稳妥的顺序是:

解析完成  ↓Chunk 和向量批次写入成功  ↓检查索引可搜索状态  ↓写入发布版本  ↓任务状态 = ready

如果某一步成功、下一步失败,应保持中间状态并允许补偿,而不是把整个任务伪装成成功。

ready 应是一个发布承诺:解析结果存在、向量写入成功、索引可搜索、权限和版本过滤已经生效。只有满足这些条件,前端才应该显示“可搜索”。

七、取消任务怎么做?

取消不是简单地把数据库状态改成 cancelled。Worker 可能正在运行,还要处理:

  • • 检查取消标记;
  • • 中断后续阶段;
  • • 清理临时文件;
  • • 删除未发布的向量;
  • • 释放外部模型调用;
  • • 防止取消后的旧任务覆盖新版本状态。

任务应使用版本和 job_id 校验更新权限,旧任务不能修改新任务的状态。

八、长任务状态接口示例

{"job_id":"job-001","document_id":"doc-001","status":"running","stage":"embedding","progress":65,"processed":130,"total":200,"can_cancel":true,"updated_at":"2026-08-12T10:02:00Z","error":null}

前端可以根据 status 决定按钮和提示,不能只根据 progress=100 判断完成。

九、监控长任务健康度

记录:

  • • 各阶段耗时;
  • • 队列等待时间;
  • • 当前运行任务数;
  • • 失败率和重试率;
  • • 卡住任务数量;
  • • 最老任务年龄;
  • • 每个租户的任务配额。

设置超时检测:如果任务长时间没有更新时间,就标记为 stalled,由恢复程序检查 Worker 是否仍然存活并决定重试或人工介入。

恢复程序需要区分“Worker 仍在处理”和“Worker 已经丢失心跳”。可以使用租约、心跳时间和拥有者 ID,避免两个 Worker 同时接管同一个任务;接管后仍要依靠阶段幂等保证重复执行安全。

十、上线检查清单

  • • 状态机有明确的合法流转;
  • • 每个阶段可单独观测和重试;
  • • job_id、document_id 和 version 有稳定关联;
  • • ready 只在数据真正可检索后设置;
  • • 前端可以轮询或订阅状态;
  • • 取消不会被旧任务覆盖;
  • • 错误码和提示不会泄露敏感信息;
  • • 有卡住任务检测和恢复机制。
  • • SSE/WebSocket 断线后可以通过查询接口恢复;
  • • 状态更新带版本控制,旧 Worker 不能覆盖新状态;
  • • stalled 任务有心跳、租约和接管策略;
下面是ai交流群,可以分享或者交流一些ai相关技术,感兴趣的可以加入:

结语

长任务状态管理的核心不是显示一个漂亮的进度条,而是让任务拥有可验证的状态、可恢复的阶段和明确的失败边界。对于 RAG 知识库,只有当向量和发布状态都准备好后,文件才真正“处理完成”。

至此,第六篇章“缓存、文件与异步任务”全部完成。下一篇章将进入 Agent 与工作流编排,从《什么是 AI Agent?》开始。

参考资料

  1. 1. Redis 官方文档:Job Queue
  2. 2. RabbitMQ 官方文档:Work Queues
  3. 3. MDN:Server-sent events
  4. 4. PostgreSQL 官方文档:Concurrency Control

本文为“码海寻道”原创技术文章。任务状态机和重试策略应结合业务数据一致性、取消语义与故障恢复要求设计。

相关学习资料

返回首页浏览学习资料