From a7220d27f52b2619f6099953e61c9fa785237c3c Mon Sep 17 00:00:00 2001 From: mariomitte Date: Mon, 31 Aug 2026 09:51:21 +0200 Subject: [PATCH] fix: use draft()+thumbnail() to prevent OOM kill on large image resize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PIL img.resize() on high-resolution photos was exhausting worker RAM, causing Gunicorn to SIGKILL the worker mid-request (production OOM crash). Replace the explicit resize() path in both _compress_image_for_pdf() and _compress_image_for_docx() with: - Image.draft() — hints the JPEG decoder to decode at a lower resolution - Image.thumbnail() — in-place resize that avoids allocating a second full-resolution buffer This keeps peak memory proportional to the output size instead of the original file size, preventing the worker from being killed when processing multi-megapixel service-record photos. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- backend/modules/fleet/views.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/backend/modules/fleet/views.py b/backend/modules/fleet/views.py index aa782d6..2ef0abc 100644 --- a/backend/modules/fleet/views.py +++ b/backend/modules/fleet/views.py @@ -468,15 +468,18 @@ def _compress_image_for_pdf(image_field, max_width=1280, quality=75): """ Otvori image_field (Django FileField), kompresiraj na max_width JPEG u memoriji, vrati ImageReader spreman za reportlab. Vraća None ako slika nije dostupna. + + Koristi Image.draft() + thumbnail() kako bi se izbjeglo učitavanje pune rezolucije + velikih fotografija u RAM (sprječava OOM kill Gunicorn workera). """ try: image_field.open('rb') with Image.open(image_field) as src: + # draft() smanjuje dekodiranu rezoluciju za JPEG na serveru (hint, ne garantija) + src.draft('RGB', (max_width, max_width * 4)) img = src.convert('RGB') - if img.width > max_width: - ratio = max_width / float(img.width) - new_h = max(1, int(img.height * ratio)) - img = img.resize((max_width, new_h), Image.LANCZOS) + # thumbnail() radi in-place i ne alocira novu sliku pune veličine + img.thumbnail((max_width, max_width * 4), Image.LANCZOS) buf = BytesIO() img.save(buf, format='JPEG', quality=quality, optimize=True) buf.seek(0) @@ -493,15 +496,16 @@ def _compress_image_for_pdf(image_field, max_width=1280, quality=75): def _compress_image_for_docx(image_field, max_width=1600, quality=80): """ Pripremi sliku za python-docx kao JPEG stream razumne veličine. + + Koristi Image.draft() + thumbnail() kako bi se izbjeglo učitavanje pune rezolucije + velikih fotografija u RAM (sprječava OOM kill Gunicorn workera). """ try: image_field.open('rb') with Image.open(image_field) as src: + src.draft('RGB', (max_width, max_width * 4)) img = src.convert('RGB') - if img.width > max_width: - ratio = max_width / float(img.width) - new_h = max(1, int(img.height * ratio)) - img = img.resize((max_width, new_h), Image.LANCZOS) + img.thumbnail((max_width, max_width * 4), Image.LANCZOS) buf = BytesIO() img.save(buf, format='JPEG', quality=quality, optimize=True) buf.seek(0)