chunks缺失导致openDownloadStream()卡死或返回截断数据,因GridFS不校验完整性,驱动跳过缺失块继续查询,直至累计字节数≥files.length才停止。

chunks缺失直接导致openDownloadStream()卡死或返回截断数据
GridFS 读取流程是先查 fs.files 拿元信息(比如 length、chunkSizeBytes),再按 files_id + n 顺序查 fs.chunks 拼接二进制。一旦某块(比如 n: 42)丢失,驱动不会报“chunk not found”,而是:
- Node.js 驱动在拼到最后一个 chunk 时发现总字节数对不上,抛 RangeError: Invalid chunk size 或静默截断;
- Java 驱动可能卡在 getNext() 等待超时,日志里反复打印 Failed to read chunk n=42 for files_id=...;
- 浏览器下载大文件时进度条突然停在 87%,HTTP 响应体少了几 MB,但状态码仍是 200。
为什么查不到缺失 chunk 却不报错?
因为 GridFS 协议本身不校验完整性——它只相信 fs.files.length 和实际查到的 chunks 字节和是否一致。驱动默认行为是:
- 按 n 从 0 开始逐个查询 fs.chunks;
- 如果某 n 值没匹配到文档,就跳过,继续查 n+1;
- 直到累计字节数 ≥ files.length 才停止,哪怕中间缺了三块、多了两块。
这就造成:你删了一个 chunk,文件还能“下载成功”,只是内容错乱或提前结束。验证方法很简单:
db.fs.chunks.countDocuments({ files_id: ObjectId("...") }) * 255 * 1024 !== db.fs.files.findOne({ _id: ObjectId("...") }).length
修复前必须确认是否真丢了 chunk,还是索引坏了
常见误判场景:
- fs.chunks 缺失 { files_id: 1, n: 1 } 复合索引 → 查询变全表扫描,看起来像“查不到”,其实是慢到超时被客户端断开;
- 使用了非默认 bucket_name(如 myfiles),却去 fs.chunks 查,实际该查 myfiles.chunks;
- files_id 类型不一致:应用层存的是字符串 "60a1b2c3...",但 chunks 里是 ObjectId,导致 $match 完全失效。
运行 db.fs.chunks.getIndexes() 确认索引存在,再用 db.fs.chunks.find({ files_id: { $eq: ObjectId("...") } }).sort({ n: 1 }).limit(5) 手动检查前几块是否存在且 n 连续。
chunks缺失后强行恢复的风险点
别用“补一个空 chunk”这种操作:
- n 必须严格连续,补错序号会导致后续所有 chunk 解析偏移;
- data 字段不能为 null 或空 BinData,驱动会拒绝解析;
- 补 chunk 后 md5 值必然不匹配,但 fs.files.md5 是上传时算的,不会自动更新,下游校验会失败;
- 最稳妥的方式其实是:定位到缺失的 files_id,从备份或源系统重传该文件,而不是修 chunks。
如果你确定只能抢救,至少用 db.fs.chunks.insertOne({ files_id: ..., n: 42, data: BinData(0, "...") }) 插入合法结构,且确保 data 字段长度 ≤ chunkSizeBytes(默认 255KB)。
真正难处理的不是单个 chunk 缺失,而是缺失发生在高并发写入期间——你刚补上一块,另一路上传又覆盖了同一 files_id 的元数据,files.length 和 chunks 实际分布彻底脱钩。这种情况下,连“从 chunks 反推文件大小”都不可靠。











