You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.15 16:16:00