Przesyłanie dużych plików do Django Rest Framework
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
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.
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)