Search code examples
ruby-on-railsrubyruby-on-rails-3background-processsucker-punch

Rails and sucker_punch: Debounce x seconds before executing job to control rate of execution


In my Rails 3.2 project, I am using SuckerPunch to run a expensive background task when a model is created/updated.

Users can do different types of interactions on this model. Most of the times these updates are pretty well spaced out, however for some other actions like re-ordering, bulk-updates etc, those POST requests can come in very frequently, and that's when it overwhelms the server.

My question is, what would be the most elegant/smart strategy to start the background job when first update happens, but wait for say 10 seconds to make sure no more updates are coming in to that Model (Table, not a row) and then execute the job. So effectively throttling without queuing.

My sucker_punch worker looks something like this:

class StaticMapWorker
    include SuckerPunch::Job
    workers 10

    def perform(map,markers)
        #perform some expensive job
    end
end

It gets called from Marker and 'Map' model and sometimes from controllers (for update_all cases)like so:

after_save :generate_static_map_html

def generate_static_map_html
    StaticMapWorker.new.async.perform(self.map, self.map.markers)
end

So, a pretty standard setup for running background job. How do I make the job wait or not schedule until there are no updates for x seconds on my Model (or Table)

If it helps, Map has_many Markers so triggering the job with logic that when any marker associations of a map update would be alright too.


Solution

  • What you are looking for is delayed jobs, implemented through ActiveJob's perform_later. According to the edge guides, that isn't implemented in sucker_punch.
    ActiveJob::QueueAdapters comparison

    Fret not, however, because you can implement it yourself pretty simply. When your job retrieves the job from the queue, first perform some math on the records modified_at timestamp, comparing it to 10 seconds ago. If the model has been modified, simply add the job to the queue and abort gracefully.

    code!

    As per the example 2/5 of the way down the page, explaining how to add a job within a worker Github sucker punch

    class StaticMapWorker
      include SuckerPunch::Job
      workers 10
    
      def perform(map,markers)
        if Map.where(modified_at: 10.seconds.ago..Time.now).count > 0
          StaticMapWorker.new.async.perform(map,markers)
        else
          #perform some expensive job
        end
      end
    end