GridFS文件读取必须使用gridfs.GridFS实例,直接查询fs.files和fs.chunks仅得元数据与分块,无法还原完整文件;绕过GridFS协议拼接逻辑会导致损坏,尤其大文件或流式场景。

GridFS文件读取必须用gridfs.GridFS实例,不能直接查fs.files集合
直接查询fs.files和fs.chunks集合只能拿到元数据和二进制分块,无法还原完整文件——GridFS协议要求按files_id和n顺序拼接chunks,这个逻辑由gridfs.GridFS封装。绕过它会导致文件损坏,尤其是大于16MB的大文件或带流式读取需求的场景。
实操建议:
- 用
pymongo连接MongoDB后,初始化gridfs.GridFS(db, collection="fs")(注意collection参数要和实际bucket名一致,默认是fs) - 遍历文件用
fs.find({}),它返回GridOut对象,支持.read()、.chunk_size、.filename等属性 - 避免用
fs.get_last_version()除非明确只要最新版——历史版本会遗漏
OSS上传需分块且控制part_size,否则大文件易超时或OOM
阿里云oss2 SDK对单个put_object有100MB限制;超过要用resumable_upload或分片上传。GridFS里常见几十MB甚至GB级备份文件,硬读到内存再传必然失败。
实操建议:
- 用
oss2.ResumableUpload而非put_object,自动处理断点续传和分片 - 设置
store=oss2.ResumableStore(root="/tmp/oss-upload-cache")避免重复计算ETag -
part_size设为5MB~20MB:太小导致HTTP请求数爆炸;太大在弱网下容易超时(OSS默认单请求超时90秒) - 文件名映射建议保留
filename字段,但需过滤非法字符:re.sub(r'[\/*?:"|]', '_', obj.filename)
同步需处理增量与去重,靠uploadDate和md5双校验
GridFS不保证uploadDate全局单调——副本集切换或批量导入可能导致时间乱序;仅靠时间戳跳过文件会漏数据。OSS端也无原生MD5比对能力(ETag对分片上传不是MD5),必须主动计算。
实操建议:
- MongoDB端优先用
files.md5字段(GridFS 3.2+写入时自动生成),比uploadDate更可靠 - OSS端用
head_object查Content-MD5响应头——但注意:只有put_object上传的文件才带此头,ResumableUpload不带,得自己存MD5到user_metadata - 首次全量同步后,后续跑定时任务只查
uploadDate > 上次同步时间,再逐个比对md5值 - 记录同步状态别用本地文件,改存到MongoDB的
sync_log集合,含filename、md5、oss_key、sync_time
错误重试要区分网络异常和业务冲突,oss2.exceptions.NoSuchKey不是重试信号
脚本运行中可能遇到连接中断、OSS限流(oss2.exceptions.ServerError: 503)、Mongo游标失效(CursorNotFound)。但像NoSuchKey这种是OSS端根本没该对象,属于正常流程,重试毫无意义,反而掩盖真实问题。
实操建议:
- 对
oss2.exceptions.ServerError和pymongo.errors.AutoReconnect做指数退避重试(最多3次,间隔1s/3s/9s) -
oss2.exceptions.NoSuchKey、gridfs.NoFile直接跳过,记warn日志 - 所有异常必须捕获并记录
filename、error_type、traceback到日志文件,方便定位是权限问题(如OSSAccessDenied)还是路径问题(如MongoDatabaseNotFound) - 不要用
try...except Exception:吞掉所有异常——会掩盖KeyboardInterrupt导致脚本无法Ctrl+C退出
真正麻烦的是GridFS里文件名为空或重复的边界情况,OSS不支持同名覆盖(除非显式replace=True),这些得在find()后预过滤,而不是等到上传时报错再处理。


















