一个 RAG Demo 往往只有几步:上传文件,切成 Chunk,写进向量库,然后在同一个接口里提问并生成答案。
本机演示时,这样很直观。
可一旦真的有用户上传文档,问题就出现了。复杂 PDF 解析可能持续很久,某一页 OCR 失败,部分 Chunk 已经写入,用户却不知道任务进行到哪一步。与此同时,在线问答要求尽快返回,不能被后台解析占满资源。
入库和问答使用不同资源,面对不同失败,追求的目标也不同。
这就是 RAG 系统设计面试最常问的一类题:
一个可上线的 RAG 系统应该怎样拆?离线入库、在线问答、数据存储、权限、降级和监控怎样形成闭环?
只画“文档、向量库、大模型”三格图,通常接不住后续追问:
- 一份文档只解析了一半,怎样恢复?
- 用户权限应该在哪一层生效?
- 向量服务或 Rerank 超时,系统怎样降级?
- 线上答案错了,怎样定位证据在哪一步丢失?
为什么离线入库和在线问答不能混在一起?
离线入库追求的是完整、可恢复和可追踪。
它要面对文件格式差异、OCR、表格、跨页内容、Chunk、Embedding 和批量写入。失败后需要知道停在哪一步,能不能从中间继续,已经写入的数据是否需要清理。
在线问答追求的是低延迟、权限正确和证据可靠。
它要在一次请求里完成 Query 理解、过滤、检索、排序和生成。用户已经在等待,任何排队和重试都会直接影响首字响应。
如果两条链路运行在同一个同步接口里,上传一份复杂文档可能占用应用进程,在线问答跟着变慢。解析失败也只能返回一个模糊错误,无法继续处理。
应用内部也要分层。以 FastAPI 为例,入口注册不同路由,router 负责接收请求和返回响应,service 负责组织文件解析、检索和聊天流程,database 模块负责知识库、会话与任务状态等持久化操作。HTTP 层、业务流程和数据访问各自有边界,出错时才能知道该在哪一层重试或终止。

离线链路优先保证完整和可恢复,在线链路优先保证边界正确、低延迟和可降级。
两条链路可以共享文档 ID、知识库 ID、Chunk Schema 和模型配置,却应该有独立的任务队列、资源配额和监控指标。
离线入库应该怎样拆?
一条清楚的入库链路可以分成九步。
第一步,上传校验。检查身份、知识库权限、文件类型、大小和重复提交。
第二步,保存原文件。先把原始内容写入稳定存储,再创建文档记录和处理任务。
第三步,格式路由。PDF、Word、PPT、纯文本和扫描页进入各自解析器。
第四步,结构解析。提取文本、表格、图片、标题、页码和位置。
第五步,清洗与 Chunk。过滤无用内容,保留语义边界和来源 metadata。
第六步,计算 Embedding。模型与维度要记录版本,避免以后混用不同向量空间。
第七步,批量写入检索引擎。每个 Chunk 使用稳定 ID,收集逐项错误。
第八步,完整性校验。核对预期 Chunk 数、实际写入数、失败页和索引状态。
第九步,发布文档版本。只有校验通过的新版本才对在线查询可见。

解析成功不等于入库完成,只有写入、校验和发布全部完成,在线链路才应看到新版本。
批量写入 Elasticsearch 时,每个 Chunk 应带稳定的业务 id,写入时映射成 ES _id,批量操作结束后还要逐项收集错误。稳定 ID 能帮助识别同一条内容,但完整幂等还要回答:重复上传怎样识别,旧版本 Chunk 何时删除,新旧版本切换是否会出现半成品,重试是否会重复写入,发布失败能否回滚。
更稳的设计是把“处理版本”和“当前发布版本”分开。新版本在后台写完并校验,通过一次状态切换对外发布;失败时旧版本继续服务。这样即使一次写入只完成部分 Chunk,半成品也不会进入在线检索。
每个任务阶段要记录输入版本、开始与结束时间、输出数量、错误类型和重试次数。确定性错误,比如不支持的格式或字段映射错误,不应该反复重试;暂时性网络超时才适合有限重试。
文档任务需要怎样的状态机?
只有“处理中”和“已完成”两个状态,很难描述真实入库过程。
一份文档可以经历已接收、等待解析、解析中、等待向量化、写入中、校验中、待发布、已发布、失败和已停用。状态不一定使用这些名字,但必须能回答当前停在哪一步、哪些产物已经生成、下一次从哪里继续。
状态变化要由完成对应工作的服务写入,不能在接口收到请求后就提前标成成功。每次变化记录任务 ID、文档版本、执行者、错误和时间,重复回调时使用幂等条件避免状态倒退。
失败也需要分类。
可重试失败包括暂时网络错误、上游限流和短暂服务不可用。不可重试失败包括格式不支持、文件损坏、权限不足和 Schema 无法解析。前者进入有限重试队列,后者直接等待人工处理或用户修正。
取消任务时,要清理尚未发布的临时 Chunk 和缓存,但不能误删当前正在服务的旧版本。发布动作最好只切换一个明确的“当前版本”指针,而不是边写边让在线流量读取。
任务恢复还要验证依赖版本。解析完成后系统升级了 Embedding 模型,恢复时不能直接把旧解析任务接到不兼容索引,必须根据任务记录决定继续、重算还是废弃。
状态机的价值不是多几个枚举值,而是把恢复、重试、取消和发布变成可以验证的行为。没有它,后台任务一旦中断,团队只能靠查日志和手工删数据收尾。
对用户而言,状态还要转换成可理解的进度。可以告诉他正在解析、等待校验或处理失败,并给出能否重试,而不是展示内部服务名和堆栈。内部状态足够精细,外部表达足够清楚,两者通过稳定映射连接。
在线问答链路应该怎样走?
在线请求的第一步不是 Embedding,而是身份与范围。
服务端从登录状态得到用户和租户,确定可访问的知识库、文档和版本。权限条件要进入检索请求内部,不能先搜全库,再由前端隐藏不该看的结果。
接下来才是 Query 处理。
单轮问题可以直接进入检索,多轮问题可能需要从历史中补全实体。无论是否改写,都要保留原问题,以便回退和审计。
然后并行执行关键词与向量召回。一条完整的调用链可以这样拆:get_filters 把知识库 ID、文档 ID 和可用状态转成搜索条件,get_vector 生成向量表达式,search 组合文本与向量候选,外层 retrieval 再完成重排、阈值筛选、分页和结果组装。这样拆的价值不是函数名好看,而是权限、向量化、召回和后处理都有可记录、可测试的输入输出。
检索结果不是最终答案。
系统还要判断证据是否充分,控制上下文数量,保留来源,再把问题与证据交给模型。模型生成后通过流式响应返回答案和引用。
在线链路的每一步都应该在同一个 Trace 下留下输入与输出。出现错答时,能够看到原 Query、改写 Query、权限过滤、候选原文、各类分数、最终上下文、模型版本和引用映射。
数据应该分别放在哪里?
不同数据有不同访问方式,不适合全部塞进向量库。
关系数据库适合保存用户、租户、知识库、文档元数据、版本、任务状态和权限关系。这些数据需要明确约束、事务和按字段查询。
对象存储或受控文件系统适合保存原始 PDF、PPT、图片和解析产物。原文件是重新解析和引用回跳的基础。
检索引擎负责 Chunk 内容、分词字段、向量、来源 metadata 和可过滤字段。关键词、向量和结构化过滤都围绕它展开。
缓存保存可重新计算、允许失效的数据,比如 Query Embedding 或特定范围内的检索结果。缓存不是事实主存,清空后系统仍然要能正常工作。
日志与追踪系统保存处理过程和诊断数据,但要控制敏感内容和访问权限。

原文件、业务状态、检索内容、缓存和 Trace 的生命周期不同,不能因为都与 RAG 有关就放在同一套存储里。
所有存储之间需要稳定 ID 关联。文档版本改变时,检索 Chunk、引用、缓存和评测样本才能知道自己对应哪一版资料。
失败和降级应该怎样设计?
系统投入运行时,失败不会只发生在大模型。
Query 改写可能选错实体。可以回退原问题,或者向用户澄清。
Embedding 服务可能超时。如果系统同时有关键词路线,可以在明确标记降级的情况下只使用 BM25,但要重新应用证据门槛。
向量检索可能失败。不能把空候选直接交给模型,让模型凭自身知识回答内部资料问题。
Rerank 可能超时。可以使用融合后的初排结果,但要记录本次未经过精排,并检查候选是否仍满足最低质量。
模型服务可能限流或中断。系统可以返回已检索到的原文,或者明确提示稍后重试,不能伪造完整答案。
引用核验失败时,高风险事实应该删除、降级或拒答。
这些降级动作要在响应和日志中可见。否则团队只看到“接口成功”,不知道用户拿到的是完整链路结果还是某种备选结果。
重试也必须有边界。
网络抖动、临时限流适合有限重试,并配合退避。格式不支持、权限不足、Schema 错误属于确定性问题,重复执行只会制造更多日志和压力。
写操作还要带幂等 ID。客户端超时后重复提交,系统应能识别同一个任务,而不是生成两份文档和两套 Chunk。
五个系统故障,分别暴露哪条边界?
第一种,文档显示“已完成”,其实只写入了一部分
解析得到一批 Chunk,批量写入时某些项失败,任务状态仍被直接改成完成。
应核对预期数量、成功数量和逐项错误,校验通过后才能发布。失败版本不进入在线搜索。
第二种,先搜全库,再在前端隐藏
后端检索已经拿到了其他租户内容,即使前端不显示,这些内容仍可能进入 Rerank、Prompt 或日志。
权限必须在检索过滤里生效,缓存键也要包含租户和知识库范围。
第三种,向量服务失败后直接让模型回答
模型返回一段通顺答案,接口状态还是成功,用户无法知道它没有使用知识库。
降级应该明确选择可用检索路线,证据不足就拒答,并在结果中标记本次链路状态。
第四种,只监控接口存活,不监控答案
服务没有报错,延迟也正常,但正确证据长期召回不到,用户持续得到错误答案。
系统指标和质量指标必须同时存在。固定回归集、线上 bad case 和引用检查负责发现“稳定地答错”。
第五种,发布新版本后缓存仍返回旧内容
检索已经指向新文档,答案缓存却没有版本信息,用户继续看到旧规定。
文档发布要触发缓存失效,或把知识库与文档版本纳入缓存键。性能组件不能脱离数据生命周期。
监控怎样覆盖系统和答案?
系统层要观察任务积压、解析失败、批量写入错误、各阶段延迟、模型 TTFT、超时、错误率、连接池、队列和资源使用。
质量层要观察正确证据召回、第一条正确证据排名、无答案错误、引用是否支持陈述,以及用户反馈。
Trace 连接两层。
每次请求使用一个 Trace ID,从网关一路串到 Query 处理、过滤、检索、重排、Prompt、模型和引用。线上出现错误答案时,可以先确认允许范围,再看正确证据是否进入候选,之后检查它有没有被截断,最后检查模型怎样使用原文。
如果正确证据根本没找到,bad case 进入解析或检索回归集。如果证据已经在前面,模型仍然错答,则进入生成和引用回归集。

线上 Trace 负责定位,bad case 回归负责防止复发,评测集再验证下一次改动是否真的有效。
这才叫闭环。监控不是只画一张资源仪表盘,而是让线上错误能够回到可执行测试。
怎么验收一个“可上线”版本?
功能验收覆盖文档上传、解析、版本发布、检索、拒答、引用和多轮问答。
故障验收主动注入解析失败、部分写入、检索超时、Rerank 超时、模型限流和客户端断线,检查任务状态、降级行为和资源释放。
权限验收准备不同用户和知识库,确认检索候选、缓存、日志和引用都不会越界。
性能验收分别测试离线任务吞吐与在线 TTFT,观察稳定并发、突发流量和冷启动,不使用没有压测依据的容量承诺。
质量验收运行固定评测集,比较 Recall@K、MRR、无答案控制和引用正确性,并按问题类型查看失败。
恢复验收检查任务中断后能否从正确阶段继续,新版本失败时旧版本是否仍然服务,重试是否重复写入。
所有验收都要绑定代码、模型、知识库和 Schema 版本。没有版本,结果以后无法复现。
系统设计追问怎么接:先说职责和退路
这组问题不是考基础设施名词,而是检查每类数据由谁负责、一次失败会停在哪,以及旧版本或降级链路能否继续服务。
为什么不用一套数据库保存所有内容?
用户权限、任务状态需要结构化约束,原文件需要稳定保存,Chunk 需要文本与向量检索,缓存需要快速失效。访问方式和生命周期不同,拆开更容易保证职责与恢复。
文档更新时怎样避免半成品被搜索到?
新版本先在后台解析、写入并校验,只有通过后才切换当前发布版本。失败时保持旧版本可用,同时记录失败任务。
向量服务挂了,是否可以只用 BM25?
可以作为明确降级,但需要重新检查证据门槛和适用问题。不能假设单路一定能覆盖所有 Query,也不能隐藏本次降级状态。
权限为什么不能在前端过滤?
因为未授权内容已经进入后端候选、排序、Prompt 或缓存。权限必须在数据访问和检索条件中生效,前端只负责展示。
怎么证明系统能扛住多少流量?
用与真实请求结构接近的压测,报告硬件、数据规模、并发、TTFT、P95、P99、错误率和质量变化。没有压测记录时,只能说明扩展设计,不能承诺容量数字。
架构变更还需要明确发布单元。解析器、Chunk 规则、Embedding、融合、Rerank、Prompt 和权限策略并不是互不相关的开关;其中一项变化就可能让旧评测结论失效。我会把代码、配置、模型、索引与评测报告绑定到版本,先离线回归,再影子或小流量验证。异常时回退整套兼容组合,而不是临时拼接一个从未验证过的新旧混合版本。
跨服务调用还要统一请求标识与超时预算。每一层只能消耗自己的预算并把剩余时间传给下游,避免入口已经超时,检索或生成任务仍在后台继续占用资源。Trace 则用同一请求标识串起队列等待、召回、重排和生成阶段,才能区分容量不足、外部依赖抖动与模型变慢。
把离线、在线与反馈闭环串起来
面试收尾时,按“双链路、数据职责、失败退路、反馈回归”把系统连起来:
我会把 RAG 拆成离线入库和在线问答两条链路。离线侧从上传、格式路由、解析、清洗、Chunk、Embedding、批量写入、完整性校验到版本发布,每一步都有任务状态和可恢复边界。新版本只有校验通过后才对外可见,失败时旧版本继续服务。在线侧先做身份和知识库范围校验,再进行 Query 处理、过滤、关键词与向量召回、融合、Rerank、证据门槛、上下文构建和流式生成。
数据职责也会拆开。关系库存用户、权限、文档版本和任务状态,对象存储保存原文件,检索引擎保存 Chunk、向量和来源 metadata,缓存只保存可重建数据并带租户与版本。失败时按模块有限重试或降级,证据不足就拒答,不能在检索失败后让模型凭自身知识冒充正常结果。
每次请求用 Trace 串联原 Query、改写、过滤、候选、分数、最终上下文、模型版本和引用。系统监控看任务、延迟和错误,质量监控看 Recall、无答案和引用,线上 bad case 再进入回归集。这样才能让功能、权限、恢复、性能和答案质量一起形成闭环。
最后给出可上线的四条底线:数据职责清楚,新版本失败不留下可见半成品,任何降级都不越权或伪装正常,以及线上一次错答能够被 Trace 复现并沉淀为回归样本。基础设施名称只有在解释这些边界时才有意义。