You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用django-imagekit的新闻聚合应用内存占用过高问题排查

解决Django-ImageKit在Heroku上内存持续攀升的问题

嘿,我之前也帮不少开发者搞定过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

火山引擎 最新活动