使用django-imagekit的新闻聚合应用内存占用过高问题排查
嘿,我之前也帮不少开发者搞定过django-imagekit在Heroku上的内存暴涨问题,结合你的场景——8000张图片时内存是100张staging环境的3倍,大概率是imagekit的使用方式或者缓存机制在拖后腿。给你几个具体的排查和解决方向:
1. 别让模板每次请求都重新生成缩略图
如果你的模板里是直接用{% generateimage %}这种动态生成的方式,那麻烦大了——每来一个请求,服务器都得重新读取原始图片、处理生成缩略图,8000张图片的话,频繁的图片处理操作会把Heroku dyno的内存直接占满,而且这些临时生成的文件还可能堆积。
快速解决办法:
改用ImageSpecField把缩略图预生成并存起来,只在图片上传的时候处理一次,后续请求直接读现成的文件。代码示例:
from django.db import models from imagekit.models import ImageSpecField from imagekit.processors import ResizeToFill class Article(models.Model): image = models.ImageField(upload_to='articles/') # 预生成100x100的缩略图,存到存储服务里 thumbnail = ImageSpecField(source='image', processors=[ResizeToFill(100, 100)], format='JPEG', options={'quality': 60})
之后模板里直接用{{ article.thumbnail.url }}就行,再也不会每次请求都消耗内存处理图片了。
2. 给ImageKit的缓存设个“上限”
django-imagekit默认会把生成的图片对象缓存起来,处理的图片越多,这个缓存就越大,而且默认的本地内存缓存不会自动清理,时间久了内存就被占满了。
解决办法:
要么给本地缓存设个最大条目数,要么直接用Redis缓存(Heroku上装个Redis插件就行,很方便)。修改settings.py的配置:
# 让ImageKit用默认缓存 IMAGEKIT_CACHE_BACKEND = 'default' # 配置默认缓存,限制本地缓存的大小 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'imagekit-cache', 'MAX_ENTRIES': 1000, # 最多存1000个缓存条目,满了就自动清旧的 } }
要是用Redis的话,直接把BACKEND改成Redis的后端就行,内存占用会更可控。
3. 别直接在模板里用超大的原始图片
如果你的模板里有时候会调用原始图片,而原始图片都是几MB的大文件,每次加载都会把完整的图片读到内存里,8000张的话,累积下来内存肯定扛不住。
解决办法:
哪怕是“原始图片”,也建议上传时就生成一个优化后的中等尺寸版本用于页面展示,只在用户需要高清图的时候才给原始链接。比如在图片上传时自动压缩:
from PIL import Image from django.db.models.signals import pre_save from django.dispatch import receiver @receiver(pre_save, sender=Article) def optimize_uploaded_image(sender, instance, **kwargs): if instance.image: with Image.open(instance.image.path) as img: # 把透明图片转成RGB,避免保存问题 if img.mode in ("RGBA", "P"): img = img.convert("RGB") # 压缩图片质量,减少文件大小和内存占用 img.save(instance.image.path, optimize=True, quality=70)
4. 别把图片存在Heroku的本地存储里
Heroku的dyno本地存储是临时的,每次dyno重启都会丢失所有文件,这意味着重启后imagekit得重新生成所有8000张图片的缩略图——短时间内大量的图片处理操作直接会把内存拉满,而且之后还会重复这个问题。
解决办法:
把图片存到外部存储服务,比如AWS S3或者Cloudinary,Heroku上配置起来也很简单。用django-storages配合S3的话,settings.py这么配:
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' AWS_ACCESS_KEY_ID = '你的AWS Access Key' AWS_SECRET_ACCESS_KEY = '你的AWS Secret Key' AWS_STORAGE_BUCKET_NAME = '你的S3桶名' AWS_S3_REGION_NAME = '比如us-east-1'
这样生成的缩略图和原始图片都存在S3里,dyno重启也不会丢失,内存压力会小很多。
5. 监控一下内存到底被谁占了
要是还是找不到问题,可以用Heroku的日志或者装个New Relic插件监控内存使用,看看是哪些请求导致内存暴涨。本地测试的话,可以用objgraph工具检查有没有图片对象没被垃圾回收:
pip install objgraph
然后在视图或者模板渲染完之后,跑这段代码:
import objgraph # 打印内存中增长最快的对象 objgraph.show_growth(limit=10)
这样就能看到是不是有大量的图片处理对象堆在内存里没释放。
先从预生成缩略图和换外部存储这两点开始试,这两个是最常见的痛点,应该能快速缓解内存问题。
内容的提问来源于stack exchange,提问作者apiljic




