Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @Microsoft, ex-@ShopifyEng, @OpenSearchProj, @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

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 :processing_delayed? and :processing_immediate?. You see where this is going.

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

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

  def processing_immediate?(img = nil)
    !processing_delayed?
  end

  attr_writer :processing_delayed

  version :small, if: :processing_immediate? do
    process resize_to_limit: [200, 200]
  end

  version :medium, if: :processing_immediate? do
    process resize_to_limit: [260, 260]
  end

  version :watermarked, if: :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.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?