gridfs在rust中无法通过gridfsbucket::upload_from_stream实现高并发写入,因其驱动层对bucket加全局写锁;需绕过该api,手动分片写入chunks与files集合,并建立{files_id: 1, n: 1}复合索引。

GridFS 在 Rust 中不能靠 GridFsBucket::upload_from_stream 实现高并发写入,本质是驱动层对整个 bucket 加了全局写锁;真要压并发,得绕过它,手动往 chunks 和 files 集合写。
为什么 GridFsBucket::upload_from_stream 并发会卡死
调用多次 upload_from_stream 时,即使传的是不同 filename 或不同 file_id,底层仍会争抢同一个 Mutex——这是 mongodb crate 3.x(如 3.4.0+)的硬编码行为。实测超过 4–6 个 tokio task 后 QPS 不升反降,错误日志里看不到报错,但延迟毛刺明显、CPU 利用率卡在单核满载。
这不是你代码写得不对,也不是 MongoDB 服务端瓶颈,而是驱动故意这么设计:把 bucket 当作原子写单元,牺牲并发换一致性。
手动写 chunks 和 files 的最小安全步骤
GridFS 规范就两条:每个文件拆成 ≤255KB 的 chunk,存进 chunks 集合;元数据存进 files 集合。绕过 GridFsBucket 就是自己拼这两个集合的文档。
- 每个文件预生成唯一
_id(用ObjectId::new()),别依赖驱动自增,否则多线程下files插入可能冲突 -
chunks文档必须含字段:"_id": ObjectId、"files_id": ObjectId(和files._id对齐)、"n": u32(从 0 开始递增)、"data": Binary;漏掉n或类型写成i32,后续读取直接失败 - 用
bytes::Bytes切原始数据,每块严格 ≤255 * 1024 字节;切完用collection.insert_many批量插入chunks,比循环insert_one快 3–5 倍 -
files文档至少带:"_id"、"length"(总字节数)、"chunkSize"(固定 261120)、"uploadDate"(用chrono::Utc::now())
索引和性能关键点
官方驱动建的默认索引(files_id_1_n_1)只够读,高并发写必须补一个复合索引:{ "files_id": 1, "n": 1 },否则 chunks 插入时会因唯一约束检查变慢。
另外注意:chunks 集合不设分片键,MongoDB 5.0+ 默认不自动分片;如果单节点扛不住,得提前在 chunks 上手动启用分片,并以 files_id 为分片键——这样同一文件的所有 chunk 落在同一 shard,读取时不用跨片聚合。
读取时仍可用 GridFsBucket
写是手动的,读可以照旧用 GridFsBucket::download_to_stream,只要 files 和 chunks 数据符合规范,驱动完全识别。但要注意:如果你改了 bucket 名(比如用 "mybucket" 而非默认 "fs"),初始化 GridFsBucket 时必须显式传参,否则它还是去找 fs.files。
真正容易被忽略的是 n 字段的类型和起始值——必须是 u32,且第一个 chunk 的 n == 0;驱动读取时硬编码校验这个,错一点整文件就打不开,错误信息只显示 "invalid chunk sequence",不提示哪错了。











