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?