Python Django ASGI - сохранение изображения в модели с помощью django-storages в ведро S3 приводит к утечке памяти
Прежде всего, я искал решение в течение нескольких дней и пробовал различные фрагменты кода, найденные здесь.
Проблема: размещенное веб-приложение потребляет все больше и больше памяти каждый раз, когда изображение сохраняется в Django-модель ImageField, настроенную на S3 через django-storages.
Что я пробовал или наблюдал: независимо от того, какой ASGI-сервер я использовал, результат был одинаковым (Daphne, Uvicorn, Hypercorn, Granian). Я проверил распределение памяти построчно в критических местах с помощью tracemalloc, и потребление памяти увеличилось именно после сохранения изображений.
async def upload_image(content_img, content_thumbnail):
lf1 = tempfile.NamedTemporaryFile()
for chunk in content_img.chunks():
lf1.write(chunk)
lf2 = tempfile.NamedTemporaryFile()
for chunk in content_thumbnail.chunks():
lf2.write(chunk)
uploaded_image = await UploadedImage.objects.acreate(user=request.user, type=type, app_name=app_name)
def upload_image_sync():
uploaded_image.image.save(content_img.name, lf1)
uploaded_image.thumbnail.save(content_thumbnail.name, lf2)
await sync_to_async(upload_image_sync)()
В приведенном выше коде вызов 'uploaded_image.image.save' или 'uploaded_image.thumbnail.save' показывает большее использование памяти с помощью tracemalloc, указывая 'json/decoder.py' как файл, занимающий память.
А вот и самое интересное. Сохранение изображения в модель показывает увеличение объема используемой памяти на ~10 МБ и скачки с ~45 МБ до ~55 МБ. Если я сохранял много изображений за короткий промежуток времени, то наблюдал скачки до ~400 МБ, но после этого текущее использование снижалось, и казалось, что память освобождается. Не всегда возвращаясь к 45 МБ, но после выполнения многих запросов это выглядело хорошо, как будто сборщик мусора работает как положено. Эти логи из tracemalloc были похожи на локальной машине и на хостинговом сервере.
Однако статистика использования памяти от хостера веб-сервера совершенно другая. Я понимаю, что tracemalloc, вероятно, показывает использование памяти только веб-приложением Django, а хостируемый контейнер включает в себя другие части, например, веб-сервер ASGI. Но выглядело это так (1 рабочий): начиная с ~250MB, после вызова частей, загружающих изображение в S3, использование памяти контейнера увеличилось на ~50-100MB на одно загруженное изображение. Самое неприятное, что память не освобождается (иногда казалось, что освобождается 10% от первоначального объема, который увеличивался при запросах за короткий промежуток времени). В результате я мог исчерпать 8 ГБ памяти сервера за 2 минуты, если проводил стресс-тест с большим количеством изображений.
Я использую railway.app для размещения веб-приложения, но я также проверил render.com, ведет ли он себя так же, и это так. Однако есть одно довольно большое различие, когда я использую hypercorn --workers 5 --max-requests 50 --max-requests-jitter 25
. Перезапуск рабочих на railway.app не освобождает память вообще (!), а на render.com освобождает. Перезапуск рабочих, к сожалению, приводил к отбрасыванию некоторых запросов, так что это не похоже на решение моей проблемы, даже если я выберу render.com в качестве основного хоста.
Кто-нибудь сталкивался с такой проблемой и решил ее? Может есть способ принудительно сохранять изображения в S3 кусками, если причина в этом? Или, может быть, внешние слои хостируемого контейнера не освобождают память при запросах?