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,
|
Otvori image_field (Django FileField), kompresiraj na max_width JPEG u memoriji,
|
||||||
vrati ImageReader spreman za reportlab. Vraća None ako slika nije dostupna.
|
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:
|
try:
|
||||||
image_field.open('rb')
|
image_field.open('rb')
|
||||||
with Image.open(image_field) as src:
|
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')
|
img = src.convert('RGB')
|
||||||
if img.width > max_width:
|
# thumbnail() radi in-place i ne alocira novu sliku pune veličine
|
||||||
ratio = max_width / float(img.width)
|
img.thumbnail((max_width, max_width * 4), Image.LANCZOS)
|
||||||
new_h = max(1, int(img.height * ratio))
|
|
||||||
img = img.resize((max_width, new_h), Image.LANCZOS)
|
|
||||||
buf = BytesIO()
|
buf = BytesIO()
|
||||||
img.save(buf, format='JPEG', quality=quality, optimize=True)
|
img.save(buf, format='JPEG', quality=quality, optimize=True)
|
||||||
buf.seek(0)
|
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):
|
def _compress_image_for_docx(image_field, max_width=1600, quality=80):
|
||||||
"""
|
"""
|
||||||
Pripremi sliku za python-docx kao JPEG stream razumne veličine.
|
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:
|
try:
|
||||||
image_field.open('rb')
|
image_field.open('rb')
|
||||||
with Image.open(image_field) as src:
|
with Image.open(image_field) as src:
|
||||||
|
src.draft('RGB', (max_width, max_width * 4))
|
||||||
img = src.convert('RGB')
|
img = src.convert('RGB')
|
||||||
if img.width > max_width:
|
img.thumbnail((max_width, max_width * 4), Image.LANCZOS)
|
||||||
ratio = max_width / float(img.width)
|
|
||||||
new_h = max(1, int(img.height * ratio))
|
|
||||||
img = img.resize((max_width, new_h), Image.LANCZOS)
|
|
||||||
buf = BytesIO()
|
buf = BytesIO()
|
||||||
img.save(buf, format='JPEG', quality=quality, optimize=True)
|
img.save(buf, format='JPEG', quality=quality, optimize=True)
|
||||||
buf.seek(0)
|
buf.seek(0)
|
||||||
|
|||||||
Reference in New Issue
Block a user