You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Sidekiq图片处理任务内存占用过高问题求助

Sidekiq + libvips 图片处理内存泄漏问题解决方案

同类问题说明

确实有不少开发者遇到过libvips在Sidekiq长期运行场景下的内存累积问题。虽然libvips相比ImageMagick内存效率更高,但如果资源未正确释放、对象引用未及时回收,依然会出现任务完成后内存不释放、持续增长的情况。

核心问题分析

你的代码存在两个关键问题:

  1. 直接修改原始ActiveStorage::Blob对象的filename属性,导致原Blob被多次修改并持有不必要的引用,阻碍Ruby GC回收资源。
  2. 使用variant.process时依赖隐式资源管理,未显式释放libvips底层的图像资源。

优化方案

1. 修复Blob对象误用问题

不要修改原始Blob的filename,而是基于原始文件生成独立的变体或新Blob:

class Api::V1::Ads::Images::ResizeAndUploadJob < Api::V1::ApplicationJob
  sidekiq_options queue: 'high'

  def perform(blob_id)
    blob = ActiveStorage::Blob.find_by(id: blob_id)
    return if blob.nil?

    base_filename = blob.filename.base.split('_ORIGINAL').first
    content_type = 'image/webp'

    # 生成并上传1200尺寸的webp
    create_variant(blob, base_filename, [nil, 1200], content_type)
    # 生成并上传560尺寸的webp
    create_variant(blob, base_filename, [nil, 560], content_type)
    # 生成并上传130尺寸的webp
    create_variant(blob, base_filename, [nil, 130], content_type)
  end

  private

  def create_variant(original_blob, base_filename, resize_limit, content_type)
    size_suffix = resize_limit.last
    variant = original_blob.variant(format: :webp, resize_to_limit: resize_limit)
    variant.process

    # 若需要将变体保存为独立Blob(而非仅生成变体文件),可添加以下代码
    # ActiveStorage::Blob.create_and_upload!(
    #   io: StringIO.new(variant.service.download(variant.key)),
    #   filename: "#{base_filename}_x#{size_suffix}.webp",
    #   content_type: content_type
    # )
  end
end

2. 显式管理libvips资源

直接使用image_processing/vips的低级API,显式销毁图像对象、关闭文件句柄,确保底层内存被释放:

require "image_processing/vips"

class Api::V1::Ads::Images::ResizeAndUploadJob < Api::V1::ApplicationJob
  sidekiq_options queue: 'high'

  def perform(blob_id)
    blob = ActiveStorage::Blob.find_by(id: blob_id)
    return if blob.nil?

    base_filename = blob.filename.base.split('_ORIGINAL').first
    content_type = 'image/webp'

    blob.open do |original_file|
      # 处理1200尺寸
      process_and_upload_image(original_file, base_filename, 1200, content_type)
      # 处理560尺寸
      process_and_upload_image(original_file, base_filename, 560, content_type)
      # 处理130尺寸
      process_and_upload_image(original_file, base_filename, 130, content_type)
    end
  end

  private

  def process_and_upload_image(original_file, base_filename, height, content_type)
    # 重新加载原始图像,避免复用同一对象导致资源泄漏
    image = ImageProcessing::Vips.source(original_file)
    processed_image = image.resize_to_limit(nil, height).convert(:webp).call

    # 上传为新Blob
    ActiveStorage::Blob.create_and_upload!(
      io: processed_image,
      filename: "#{base_filename}_x#{height}.webp",
      content_type: content_type
    )

    # 显式释放资源
    processed_image.close
    image.destroy!
  end
end

3. 辅助优化措施

  • 手动触发GC:在任务末尾添加GC.start(谨慎使用,避免频繁调用影响性能),帮助Ruby及时回收未释放的对象引用。
  • 调整Sidekiq并发数:在Docker启动命令中设置--concurrency 2(根据服务器配置调整),减少同时处理的图片任务数,降低内存峰值。
  • 升级依赖版本:升级libvips到8.14+稳定版、image_processing到1.12+版本,修复已知的内存泄漏bug。
  • Docker内存限制:给Sidekiq容器设置内存阈值(如--memory 1g),作为兜底方案,避免内存耗尽影响其他服务。

内容的提问来源于stack exchange,提问作者CR7

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 19:35:16