Django FileResponse падает при работе с большими файлами

...У меня есть приложение Django с большим количеством данных и базой данных MariaDB, работающее на Raspberry Pi (OS = Debian 12). Приложение использует Daphne в качестве Webserver, потому что в нем также есть компоненты Django Channels (Websocket). Теперь я хочу реализовать функцию резервного копирования, которая автоматически сбрасывает базу данных, архивирует ее вместе с другими файлами данных и позволяет браузеру автоматически загружать ZIP-файл. Поэтому я сделал представление для загрузки:

def downbackup(request): 
  if request.user.is_superuser:
    filename = '../temp/backup/backup.zip'
    sourcefile = open(filename, 'rb')
    return FileResponse(sourcefile)
  else:
    return(HttpResponse('No Access.'))

Вид вызывается из соответствующего шаблона по url и все в порядке. Пока мы не столкнулись с большими файлами в реальной жизни. В этом случае (размер файлов около 6 ГБ) Daphne немедленно прекращает работу, а Raspi падает так глубоко, что мне приходится включать и выключать питание. Кроме того, в Monitorix я вижу огромные скачки потребления памяти после этих сбоев. Но ни в журналах Daphne, ни в журналах Django (включая отладочную настройку), ни в journalctl нет никаких сообщений об ошибках. Nginx (обратный прокси) сообщает о таймауте восходящего канала (504). Кажется, я узнал из документации Django, что FileResponse буферизирует файл, чтобы избежать потребления памяти, но что-то здесь должно быть не так. Есть идеи, рекомендации?

Для эффективной работы с большими файлами вы можете использовать StreamingHttpResponse вместо FileResponse в Django.

from django.http import StreamingHttpResponse
import os

def download_large_file(request):
    # Path to the large file
    file_path = '/path/to/your/large/file'

    def file_iterator(file_path, chunk_size=8192):
        with open(file_path, 'rb') as f:
            while True:
                chunk = f.read(chunk_size)
                if not chunk:
                    break
                yield chunk

    response = StreamingHttpResponse(file_iterator(file_path))
    response['Content-Length'] = os.path.getsize(file_path)
    return response

I would strongly advise not to use Django as a file server, it is not intended to use that way. Typically nginx is used for example with an X-Accel-Redirect [nginx-doc] such that Django essentially says where the file is, and nginx, can then return the file in a manner that only nginx provides the file when an X-Accel-Redirect is present.

Итак, по сути, вы работаете с:

def downbackup(request):
    if request.user.is_superuser:
        response = HttpResponse()
        response['Content-Disposition'] = 'attachment; filename=backup.zip'
        response['X-Accel-Redirect'] = '/protected/temp/backup/backup.zip'
        return response
    else:
        return HttpResponse('No Access.')

и, таким образом, сервер nginx обслуживает защищенный путь с:

location /protected/ {
  internal;
  alias   /path/one/above/tmp;
}

Это не только обеспечит отсутствие проблем с буферизацией и парообразованием nginx, но и может привести к кэшированию nginx определенных файлов, что повысит эффективность работы веб-сервера.

Ключевое слово internal в конфиге /protected/ означает, что вы не можете делать запросы извне к этому пути. То есть человек не может зайти на /protected/temp/backup/backup.zip, он может использоваться только для внутренней связи в nginx с "подсерверами".

Вернуться на верх