Przesyłanie dużych plików do Django Rest Framework

Oct 09 2020

Próbuję przesłać duży plik (4 GB) z PUT na zestawie widoków DRF.

Podczas przesyłania moja pamięć jest stabilna. Przy 100% proces serwera uruchomieniowego Pythona zajmuje coraz więcej pamięci RAM i jest zabijany przez jądro. Mam wiersz logowania w putmetodzie tego, APIViewale proces jest zabijany przed wywołaniem tej metody.

Używam tego ustawienia, aby wymusić użycie plików FILE_UPLOAD_HANDLERS = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"]

Skąd pochodzi ten szczyt pamięci? Chyba próbuje załadować zawartość pliku do pamięci, ale dlaczego (i gdzie)?

Więcej informacji:

  • Wypróbowałem DEBUG prawda i fałsz
  • Runserver znajduje się w dockerze za traefikiem, ale nie ma ograniczeń w traefik AFAIK, a przesyłanie osiąga 100%
  • Nie wiem jeszcze, czy uzyskałbym takie samo zachowanie z daphneserwerem zamiast zamiast
  • EDYCJA: użycie z przodu a Content-Type multipart/form-data
  • EDYCJA: próbowałem FileUploadParseri (FormParser, MultiPartParser)dla parser_classes w moimAPIView

Odpowiedzi

3 Wonskcalb Oct 14 2020 at 19:51

TL; DR:

Ani problem z DRF, ani z Django, jest to znany od 2,5 lat problem Daphne . Rozwiązaniem jest użycie uvicornu, hypercornu lub czegoś innego na razie.

Wyjaśnienia

To, co tu widzisz, nie pochodzi z Django Rest Framework, ponieważ:

  • FileUploadParser jest przeznaczony do obsługi wysyłania dużych plików, ponieważ czyta fragment pliku po kawałku ;
  • Twój widok nie jest wykonywany wyklucza parsery, które nie są wykonywane, dopóki nie uzyskasz dostępu dorequest.FILES właściwości

Fakt, że wspominasz o Daphne, przypomina mi tę odpowiedź SO, która wspomina o podobnym problemie i wskazuje na kod, że Daphne nie obsługuje przesyłania dużych plików, ponieważ ładuje całe ciało do pamięci RAM przed przekazaniem go do widoku. (Kod jest nadal obecny w swojej gałęzi głównej w momencie pisania)

Widzisz to samo zachowanie, runserverponieważ po zainstalowaniu Daphne zastępuje początkowe polecenie serwera uruchomieniowego samym sobą, aby zapewnić obsługę WebSockets do celów programistycznych.

Aby upewnić się, że to prawdziwy winowajca, spróbuj wyłączyć kanały / uruchom domyślny serwer wykonawczy Django i przekonaj się, czy Twoja aplikacja nie została zabita przez OOM Killer.

CaioKretzer Oct 09 2020 at 03:20

Nie wiem, czy działa z resztą django, ale możesz spróbować usunąć fragment pliku.

        [...]
        anexo_files = request.FILES.getlist('anexo_file_'+str(k))
        index = 0
        for file in anexo_files:
            index = index + 1
            extension = os.path.splitext(str(file))[1]
            nome_arquivo_anexo = 'media/uploads/' + os.path.splitext(str(file))[0] + "_" + str(index) + datetime.datetime.now().strftime("%m%d%Y%H%M%S") + extension
            handle_uploaded_file(file, nome_arquivo_anexo)
            
            AnexoProjeto.objects.create(
                projeto=projeto,
                arquivo_anexo = nome_arquivo_anexo 
            )
        [...]

Gdzie plik handle_uploaded_file jest

def handle_uploaded_file(f, nome_arquivo):
    with open(nome_arquivo, 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)