Spring Boot 做 RAG 文档上传:1GB 文件会不会打爆内存? 做 RAG 系统时文档上传很容易被低估。普通系统里上传文件可能只是保存附件。但在 RAG 里上传只是第一步后面通常还有上传文档 - 保存文件 - 解析文本 - 文本分片 - 生成 embedding - 写入向量库如果用户上传一个 1GB 的 PDF 或 Word 文档问题就变得很现实Spring Boot 会不会先把 1GB 文件全部读进内存如何配置拒绝策略1. 先说结论Spring Boot 已经提供了 multipart 上传大小限制。常见配置如下spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB这两个是 Spring Boot 官方参数。max-file-size限制单个文件大小。max-request-size限制整个 multipart 请求大小包括文件和普通表单字段。重点是Spring Boot 不会为了判断 1GB 文件是否超限就先把 1GB 全部读进 JVM 堆内存。在常规 Spring MVC 上传里文件大小限制会在 multipart 解析阶段生效。很多情况下Controller 方法还没进入请求就已经因为超限失败了。例如一个普通上传接口PostMapping(value /documents/upload, consumes MediaType.MULTIPART_FORM_DATA_VALUE) public void upload(RequestPart(file) MultipartFile file) { documentService.upload(file); }这里的MultipartFile不等于文件内容已经完整在内存里。它只是业务代码访问上传文件的入口。真正影响内存的是后面怎么用它。不推荐byte[] bytes file.getBytes();推荐try (InputStream inputStream file.getInputStream()) { fileStorage.save(inputStream, file.getOriginalFilename(), file.getSize()); }简单说Spring Boot 能限制上传大小。 但业务代码自己全量读取文件仍然会造成内存压力。2. Spring Boot 上传文件的大致流程文件上传一般是multipart/form-data请求。请求头里通常会带上Content-LengthPOST /documents/upload HTTP/1.1 Content-Type: multipart/form-data; boundary----WebKitFormBoundary Content-Length: 1073741824这个Content-Length表示请求体大小。服务端在读取完整文件之前就能先知道这次请求大概有多大。整体流程可以简单理解为客户端上传文件 | v Servlet 容器接收请求 | v Spring Boot / Servlet 解析 multipart | v 检查 max-file-size / max-request-size | v 没超限生成 MultipartFile 给 Controller 超限抛出上传大小异常所以它不是先完整读入内存 - 再判断大小而是解析过程中检查限制 文件内容通过缓冲区和临时文件处理 业务代码拿到 MultipartFile 后再决定怎么读取如果希望返回更友好的错误信息可以统一处理上传超限异常RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(MaxUploadSizeExceededException.class) public ResultVoid handleUploadSizeExceeded() { return Result.fail(上传文件大小超过限制); } }3. 真正容易出问题的地方Spring Boot 的 multipart 配置解决的是上传入口问题。但它不会替业务代码兜底。最常见的问题是全量读文件byte[] bytes file.getBytes();或者byte[] bytes inputStream.readAllBytes();这些代码会把文件完整读进 JVM 堆内存。如果文件是 50MB并发 10 个请求仅这一步就可能带来几百 MB 的内存压力。RAG 系统还要注意另一个点上传阶段和解析阶段不是一回事。上传阶段可以是流式的try (InputStream inputStream file.getInputStream()) { fileStorage.save(inputStream, file.getOriginalFilename(), file.getSize()); }但后续解析阶段可能又全量读了文件try (InputStream inputStream fileStorage.openStream(fileUrl)) { byte[] bytes inputStream.readAllBytes(); ParseResult result parser.parse(bytes); }这种情况下上传接口本身可能没问题但解析任务仍然可能造成内存波动。分片阶段也一样。很多代码会先拿到完整文本String text parser.extractText(inputStream); ListDocumentChunk chunks chunker.split(text);如果文档很大完整文本和分片列表都会占用内存。所以 RAG 文件链路不能只看上传接口还要看上传是否流式 解析是否全量读 分片是否一次性生成 embedding 是否批量过大 向量库写入是否有限流4. RAG 文件上传的建议第一后端必须配置上传大小限制。spring: servlet: multipart: max-file-size: 50MB max-request-size: 100MB前端可以提示文件大小但真正的限制一定要放在后端。第二上传阶段优先使用流。推荐try (InputStream inputStream file.getInputStream()) { fileStorage.save(inputStream, file.getOriginalFilename(), file.getSize()); }谨慎使用byte[] bytes file.getBytes();第三上传和解析最好解耦。上传接口只做校验文件 - 保存文件 - 创建文档记录 - 提交异步任务后续任务再做解析 - 分片 - embedding - 写入向量库第四上传并发要单独控制。max-file-size只限制单个文件大小max-request-size只限制单个请求大小它们不限制同时有多少人在上传。RAG 系统里并发上传会带来临时文件、磁盘 IO、解析任务、embedding 请求和向量库写入压力。第五解析和分片阶段也要控内存。可以考虑限制单个文档最大大小。限制解析文本最大长度。分片分批处理。embedding 分批提交。解析任务设置并发上限。总结一下1. Spring Boot 不会把超限文件全部加载到内存再拒绝而是有完善的早期拒绝机制 2. Content-Length 请求头让服务器在读取请求体之前就知道请求大小 3. Tomcat 在解析请求头时检查 Content-Length超过 maxSwallowSize默认 2MB直接拒绝不读取请求体 4. Spring Boot 在解析 multipart 请求时边读边检查文件大小超过 max-file-size 立即停止并抛出异常 5. 即使文件没超限Spring Boot 也不会把整个文件加载到内存而是边读边写到临时文件内存占用