Django如何向FileField存储的超大zip文件逐步写入数据
问题核心错误
你原有实现存在3个致命问题,导致大文件场景不可用、增量写入失效:
- 用
io.BytesIO作为zip的存储载体,GB级文件会直接占满内存 - ZipFile打开用
'w'模式,每次执行都会清空原有文件内容,之前写入的数据全部丢失 - 误以为每次更新内容都需要调用FileField的
save()方法,实际上第一次保存生成文件后,直接操作存储上的真实文件即可实时落盘,不需要重复调用字段的save方法。
可行实现方案
全程内存占用控制在KB级别,支持增量写入、进度持久化,适配GB级大文件场景:
import os import io import zipfile from django.core.files.base import File from django.conf import settings def _get_local_storage_path(field_file): """获取FileField对应本地存储的真实文件路径,仅适用于Django默认本地文件存储""" if hasattr(field_file.storage, "path"): return field_file.storage.path(field_file.name) return os.path.join(settings.MEDIA_ROOT, field_file.name) def generate_data(todo): # 1. 初始化空zip压缩包并绑定到模型字段 empty_zip_buffer = io.BytesIO() # 空zip结构极小,内存操作无压力 with zipfile.ZipFile(empty_zip_buffer, "w", compression=zipfile.ZIP_DEFLATED): pass empty_zip_buffer.seek(0) todo.big_file.save("heavy_file.zip", File(empty_zip_buffer)) todo.status = 0 todo.progress = 0 todo.save(update_fields=["big_file", "status", "progress"]) # 2. 直接在磁盘文件上增量追加内容,不加载全量到内存 zip_real_path = _get_local_storage_path(todo.big_file) # 注意:必须用'a'追加模式打开,禁止用'w'模式 with zipfile.ZipFile(zip_real_path, "a", compression=zipfile.ZIP_DEFLATED) as zip_handler: for filename, data_source in long_iteration(todo): # 如果data_source本身是大文件,用分块写入进一步降低内存占用 with zip_handler.open(filename, "w") as in_zip_writer: # 若data_source是类文件对象,按1MB分块写,不要一次性read() if hasattr(data_source, "read"): while chunk := data_source.read(1024 * 1024): in_zip_writer.write(chunk) else: in_zip_writer.write(data_source) # 满足进度保存条件时,直接更新数据库状态即可,不需要重新保存FileField # 因为内容已经直接写入磁盘文件,访问路径未发生变化 if check_need_save_progress(): todo.status = 1 todo.progress = calculate_current_progress() todo.save(update_fields=["status", "progress"]) # 3. 所有内容写入完成,更新最终状态 todo.status = 2 todo.progress = 100 todo.save(update_fields=["status", "progress"])
关键注意事项
- 不要在内存中缓存全量zip内容:第一次调用
big_file.save()生成空文件后,所有写入操作直接针对磁盘上的真实文件执行,内存占用始终保持在极低水平 - ZipFile打开模式必须选对:
w为新建覆盖模式,会清空原有内容;a为追加模式,是增量写入的唯一选择 - 进度保存逻辑:文件内容是实时落盘的,不需要重复调用FileField的save方法,只需要更新模型中存储状态、进度的字段到数据库即可,用
update_fields参数可以减少不必要的数据库字段写入 - 远程存储适配:如果使用S3、OSS等不支持原地修改的对象存储,不要直接操作存储路径,改为先写入服务器本地临时文件,全部写入完成后一次性上传到存储后端绑定到
big_file字段,过程中进度照常存在数据库即可 - 压缩参数选择:
zipfile.ZIP_DEFLATED是常规压缩算法,如果追求写入速度可以换成zipfile.ZIP_STORED(不压缩,仅打包)
内容的提问来源于stack exchange,提问作者10000days
相关产品推荐
相关产品推荐

