LightRAG 所有接口返回 503 recovery_required 怎么排查半提交存储与恢复顺序
【免费下载链接】LightRAG[EMNLP2025] LightRAG: Simple and Fast Retrieval-Augmented Generation项目地址: https://gitcode.com/GitHub_Trending/li/LightRAG
当 LightRAG Server 的 upload / text / scan / 手动重试 / delete / clear 等所有写操作统一开始返回 HTTP 503,而查询类接口不受影响时,通常是管道抬起了recovery_required围栏(fence):某些故障会让工作区处于"继续跑下去只能靠猜"的状态,管道宁可拒绝一切写入也不猜测,直到操作者显式解除围栏。本文给出排查围栏原因、按原因选择恢复路径、以及修复半提交存储的完整操作顺序。
该机制的权威说明在 File Processing Pipeline 文档 §8.5,两个离线修复工具的使用说明分别在 README_KG_INTEGRITY_REPAIR.md 和 README_REBUILD_VDB.md。
先确认这就是围栏,而不是别的限流
503 围栏和另外两类限流错误要区分开:
- 上传返回413 / 429是请求准入限制(admission limits),与管道忙闲和围栏都无关;
POST /documents/reprocess_failed在"围栏已抬起,或有 clear/delete 正在运行"时也会返回 503,但这是该接口自身的忙闲语义,不一定是全局围栏。
判断是否全局围栏,看两个信号:所有写接口(而不只是某一个)都 503,且GET /documents/pipeline_status返回的recovery_required为true。
第一步:用 pipeline_status 读出围栏原因
GET /documents/pipeline_status报告三个围栏字段:
recovery_required(布尔值):围栏是否抬起;recovery_kind(粗粒度原因):区分下面哪一类;recovery_message:与 503 响应相同的文字说明,部分原因还会附带阻塞文档 id 的有界样本。
围栏由三种情况触发,其中第 1 和第 3 种依赖跨进程死主进程检测,只发生在 Linux 多 worker Gunicorn 部署下(单进程 Uvicorn 随进程死亡丢失协调状态,不会出现):
- worker 在
custom_chunks/delete/clear中途死亡。这些操作可能是半提交的,不能简单重跑。(对比:processing/scan的 owner 死亡是可重跑的,会被静默回收,不抬围栏。) - 手动重试的 drain 无法到达空闲。两种卡法:同一批文档反复出现且状态无变化(重查只会空转),或 drain 永远无法推进的行——持有未完成的 custom-chunk 操作的行,只有
/documents/scan的回滚能解决。两种情况下重置都不会执行,重试请求保持未确认状态。recovery_kind用manual_drain_stalled和manual_drain_blocked区分它们。 - owner 存活与否无法判定。回收某 owner 的持有需要先证明其进程确实死亡;没有进程身份的持有记录永远无法证明这一点,管道不做猜测,围栏就是出口。
第二步:按 recovery_kind 选恢复路径
| recovery_kind / 原因 | 操作顺序 |
|---|---|
manual_drain_blocked | POST /documents/recovery/force_reset,然后POST /documents/scan——scan 既回滚未完成的操作,又自行执行FAILED重置,不需要单独的重试调用 |
manual_drain_stalled | POST /documents/recovery/force_reset,然后排查recovery_message里点名的文档——围栏无法说明它们卡住的原因;解决后重新发起POST /documents/reprocess_failed |
worker 死于custom_chunks/delete/clear中途 | 不要用force_reset——按下面"半提交存储修复"的顺序操作 |
force_reset 的副作用与语义
POST /documents/recovery/force_reset这是一个不安全的手工覆盖——它本身不修复任何东西。除了清围栏,它还会取消该工作区排队的全部手动重试请求,而这是必需的:只要还有排队请求,/documents/scan就拒绝运行(scan 自带独占的FAILED重置,不能插队),只清围栏会让恢复路径同样被堵住。响应中的cancelled_manual_retries报告取消数量,status为reset或no_recovery_required。没有文档会丢:失败文档保持FAILED,由下一次重试请求或 scan 自身的重置处理。
两个注意点:
- 接口要求请求体带
confirm: true才执行(工作区可能处于部分提交状态,需先核实/修复再重置); - 该调用是all-or-nothing:如果排队请求无法被取消,端点返回 503 且围栏保持抬起,你可以重试完成恢复,而不是让 API 报告一次实际未发生的恢复。
重启服务能否替代?
判断标准只有一个:阻塞项在内存里还是在存储里?重启只清除运行时协调状态(pipeline_status里的围栏与 owner 记录、ingress 里排队的重试请求;这些都不落盘)。写入doc_status/full_docs/ 各存储的内容原样存活。
| 原因 | 重启能修好吗 | 原因 |
|---|---|---|
| owner 存活无法判定 | 能 | 卡住的预留记录在跨进程共享状态里,重启进程组即移除,且存储从未被碰过——最干净的修法 |
manual_drain_stalled | 通常能 | 围栏和排队请求随重启消失;如果反复出现的活动行是死进程的PROCESSING/PARSING/ANALYZING孤儿,重启后会自动重置为PENDING并在下次触发时重跑。但重启不做诊断:若它们因别的原因卡在相同状态,下一次/documents/reprocess_failed还会再次卡住 |
manual_drain_blocked | 不能 | 阻塞项是doc_status.metadata里的未完成 custom-chunk 日志,已持久化、重启原样存活。重启只清围栏和排队请求(这仍然必要——排队请求会让/scan拒绝自己的预留),真正的回滚必须来自POST /documents/scan |
worker 死于custom_chunks/delete/clear | 不能 | 半提交的是存储本身,见下 |
所以重启相当于"更温和的 force_reset":两者都清围栏和排队请求、都不修复任何东西,只是重启额外把中断文档重置为PENDING(这正是 stall 常常自愈的原因),代价是停机。能安排停机就优先重启;不能停机且确认存储从未被碰(原因 2 和 3)时才用force_reset。
半提交存储修复:离线工具与恢复顺序
围栏本身不落盘(住在pipeline_status里),所以重启服务会连围栏和排队请求一起清掉。这决定了force_reset的定位:原因 2 和 3 用它(重置从未执行、存储未被碰过);原因 1 避免用它——它只清一个标志位,清掉之后所有写入(包括并发上传)立刻对可能半提交的存储恢复运行。下面两个离线工具都要求服务已停止,而停止本身就会移除围栏,所以force_reset在此路径中根本不出现。
两个离线工具的能力边界:
| 工具 | 数据真源与修复内容 | 修不了什么 | 代价 |
|---|---|---|---|
python -m lightrag.tools.kg_integrity_repair | 沿 graph → chunksource_id→text_chunks→full_doc_id重建缺失或不可用的full_entities/full_relations锚点行;可为确实不拥有任何东西的文档写空锚点行 | graph↔VDB 漂移、doc_status/full_docs本身、custom-chunk 日志、围栏本身 | 从不调用 LLM 或 embedder——基本免费 |
lightrag-rebuild-vdb | 丢弃并重建entities_vdb/relationships_vdb(来自 graph)和chunks_vdb(来自text_chunks);还能清除反向孤儿(向量库有、graph 里没有),增量修复做不到 | 锚点行、doc_status、graph 本身是否正确、围栏本身 | 全量重新 embedding——真实金钱成本 |
两个顺序陷阱:搞反会把可修数据变成不可修
kg_integrity_repair必须在任何进一步删除之前运行。它只能从幸存的chunk 溯源重建锚点;如果半截的 delete 已经删掉了text_chunks行,这些贡献就变成不可恢复的孤儿,工具只能报告、永远不会自行修改。先跑报告模式(不带--apply)没有任何理由不跑。lightrag-rebuild-vdb必须最后运行。它以 graph 和text_chunks为真源:半截 delete 本应删掉的 graph 对象会被忠实地重新 embed 回向量库。它保证向量与 graph一致,不保证 graph正确。所以先解决 graph 侧,再考虑重建向量。
原因 1 的完整操作顺序
- 停止服务(围栏随之消失——不要碰
force_reset)。 - 先跑报告:
python -m lightrag.tools.kg_integrity_repair --verbose,查看锚点缺口,以及已经救不回来的不可恢复孤儿。仅在确实有缺口时才加--apply。 - 启动服务,重跑未完成的操作——整文档 purge 有日志、会从已完成的阶段之后续跑;
clear直接重跑;custom_chunks的回滚由POST /documents/scan触发。 - 稳定后如果怀疑 graph↔VDB 漂移,再次停止服务并运行
lightrag-rebuild-vdb,先用它的只读一致性检查(菜单选项 1)判断这笔 embedding 成本是否值得付。
两个工具的硬性前提:服务必须停止、工作区必须空闲(并发写入会让它们读到一个移动的目标);lightrag-rebuild-vdb必须与服务使用同一份.env,否则重建的向量落在另一个 embedding 空间。
验证恢复完成
GET /documents/pipeline_status中recovery_required回到false(recovery_kind/recovery_message随之为空);- 原本 503 的写接口(上传、
/documents/scan、删除等)不再返回 503,能正常受理; - 走 offline 路径时,
kg_integrity_repair --apply的报告字段repaired_docs列出实际写入的锚点修复(missing_entity_anchors/missing_relation_anchors是诊断项,修复后不会清空);lightrag-rebuild-vdb的一致性检查通过。
限制与边界
- 围栏只影响写操作;查询接口不受管道准入约束、不会被 503,但在 clear/delete 期间查询结果不保证一致(可能看到空结果、部分结果或存储错误)。
- 503 围栏机制中的"worker 死亡中途"与"owner 存活无法判定"两类只在Linux 多 worker Gunicorn下出现;单进程 Uvicorn 或 Windows 不会出现这两类。
- 若删除文档时收到的是 409(缺少 recovery anchor)而非 503,那是另一条路径:原样重试会被再次拒绝,需要先运行
audit_kg_integrity(..., apply=True)(即上面的kg_integrity_repair --apply)。
【免费下载链接】LightRAG[EMNLP2025] LightRAG: Simple and Fast Retrieval-Augmented Generation项目地址: https://gitcode.com/GitHub_Trending/li/LightRAG
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考