fix: use draft()+thumbnail() to prevent OOM kill on large image resize
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>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user