Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., CTO at artsy.net, fun at playplay.io, NYC

Email Twitter LinkedIn Github

Our image upload pipeline has gotten a bit too long and no longer fits in the Heroku’s 30-second limit. We generate several image versions and would like to get a couple of sizes first and the larger processors to be delayed. I’ve looked at a few solutions, first.

  • This article describes how to delay all processors, but stores the original file for all versions, first. Other than being wasteful it has a real problem for the Cloudfront cache – if some large image were to be cached before the delayed job can resize it, we’d get an image of a wrong size for 24 hours.
  • The carrierwave_backgrounder gem is a better candidate. Unfortunately it relies on a local store and doesn’t work in a distributed environment. I think it shouldn’t be too hard to fetch the original image and then invoke the processors, but I wasn’t ready for a day-long project and it doesn’t quite do what I want – process some versions in the background.

So, here’s a slightly hacky solution to delay-processing of select CarrierWave image versions.

Let’s make all processing conditional upon :is_processing_delayed? and :is_processing_immediate?. You see where this is going.

class ImageUploader < CarrierWave::Uploader::Base
  include CarrierWave::RMagick

  def is_processing_delayed?(img = nil)
    !! @is_processing_delayed
  end

  def is_processing_immediate?(img = nil)
    ! is_processing_delayed?
  end

  def is_processing_delayed=(value)
    @is_processing_delayed = value
  end

  version :small, :if => :is_processing_immediate? do
    process :resize_to_limit => [200, 200]
  end

  version :medium, :if => :is_processing_immediate? do
    process :resize_to_limit => [260, 260]
  end

  version :watermarked, :if => :is_processing_delayed? do
    process :watermark
  end

  ...
end

By default processing is immediate, there’s nothing to do. Add the following to the model that is holding the uploader.

def recreate_delayed_versions!
    image.is_processing_delayed = true
    image.recreate_versions!
end

Then delay the method normally in the model’s after_save with self.delay.recreate_delayed_versions! – that’s it.

It almost looks too simple. Improvements? Suggestions?