Wednesday 29 March 2017

Timing your background tasks in Ruby (or bash, or any other language...)

Is It Working runs a handful of critical tasks in the background.

This post will show you how I use the new timing feature in IsItWorking to make sure that they run in a reasonable time.

Background tasks do critical things like requesting checks for your ssl expiry date, or a new whois check on your domain.

They also do cleanup tasks like clearing out excessive records on your checkins.

I use Javan's whenever to manage the schedule. It generates a crontab whenever I push an update with Capistrano.

The schedule is really simple with a handful of entries like:

every 1.minute do
 runner "Switch.check_for_past_due";
end


This task looks for any checkins that are late. It marks them as late, and queues notifications to be sent as async tasks.

def self.check_for_past_due

  Switch.not_late.past_due.find_each do |switch|
     switch.late = true
     switch.save
     switch.user.notifications.each do |notif|
       notif.send_switch_is_late(switch)
     end
  end
 
end

At the moment, this task take a couple of tens of milliseconds, but as the number of users increases, it will slow down.

At some point, I'll need to do something new; Perhaps use a faster droplet, optimise the code, or figure out some other solution!

IsItWorking's new timing feature lets me easily monitor how long this takes, and let's me get a warning if it starts taking too long.

Step 1) Log in to IsItWorking and create a checkin.



I could use the 'no timeout' option - but as I know the script will be running every minute, I might as well get the alert if it fails to run after 15 mins.

I'm using the IsItWorkingInfo gem to keep the code simple (but if you want to skip the dependencies, it is super-easy to do manually)

I add the gem to my gemfile

gem 'is_it_working_info'

then install it

bundle install

click on the 'use' button to get my checkin id



I can ignore the url as I'm using the gem - but this is actually what the gem will be pinging.

Now I just wrap my critical code with the timing block

def self.check_for_past_due

 IsItWorkingInfo::Checkin.time(key:"MYUNIQUECODE",
    message:"Switch.check_for_past_due",
    boundary:1000) do

      Switch.not_late.past_due.find_each do |switch|
        switch.late = true
        switch.save
        switch.user.notifications.each do |notif|
          notif.send_switch_is_late(switch)
        end
      end
    end
 
end

I have added a message to make it easier to remember what I'm doing here.

The boundary time is set in my code, so if I ever need to change it, it is part of the checked in code in my project.

If the task ever takes more than 1000 milliseconds, then IsItWorking will alert me and I can investigate.

I can easily review the current timing at https://IsItWorking.info, and I can also download timing data as a csv for investigation.