Thursday 6 December 2012

Delayed job, a real pain in production

There are so many ways to handle background jobs in Ruby on Rails. But the most simplest one is delayed job (DJ) as I showed you in the following post Handling delayed jobs. Simple in the sense that it requires very less effort to set up and in uses single table to handle the jobs.

It's a very good solution for development environment, but in production people starts complaining of problems such as:
  • DJ is eating up my server resources.
  • DJ is firing new daemon processes every now and then.
  • Many DJ instances are running at a time.
And many more similar kind of issues.
To run delayed job daemon along with rails server, people often end up creating config/initializers/start_worker.rb, and put codes something like

Thread.new do
  system("rake jobs:work")
end


I don't encourage this process.
Better solution will be to start delayed job using cron jobs.
There's a gem available for this purpose: whenever.

  • Add this line to your gemfile: gem "whenever", :require => false
  • $ bundle install
  • $ wheneverize .
  • This will create config/schedule.rb.
  • Open up that file and put the following codes in it:
       set :output, "/path_to_your_project/log/cron_log.log"
       every :reboot do
         command "cd /path_to_your_project && RAILS_ENV=production script/delayed_job start"
       end
  • This will start delayed job on every server boot. You may also write 
       every 10.hours do
         command "cd /path_to_your_project && RAILS_ENV=production script/delayed_job restart"
       end
          This will restart delayed job after every 10 hours. Although point number 6 is not a very feasible    
          solution, but it surely helps in some cases.
  • whenever --update-crontab your_project_name.
  • $ crontab -l
          To check whether the jobs you just made are listed.



Wednesday 17 October 2012

HAML indentation problem

Here's a sample code to assign a dynamic div for displaying companies, the ids should be assigned based on whether the user is logged in or not:

- @companies.each do |company|
  - if user_logged_in?
    #sponsor_for_logged_users
      .icon
        = link_to company do
          = image_tag company.file
        %span{:class => 'classic'}
          %p.name= company.name
          %p= company.short_description
    .benefits= company.benefits
  - else
    #sponsor_for_unlogged_users
      .icon
        = link_to company do
          = image_tag company.file
        %span{:class => 'classic'}
          %p.name= company.name
          %p= company.short_description
    .benefits= company.benefits

Too much repeatation :(

We can't write:

- @companies.each do |company|
  - if user_logged_in?
    #sponsor_for_logged_users
  - else
    #sponsor_for_unlogged_users
      .icon
        = link_to company do
          = image_tag company.file
        %span{:class => 'classic'}
          %p.name= company.name
          %p= company.short_description
    .benefits= company.benefits

Because HAML will take the yellow part as part of else.
So what to do ???

Inside controller's action let's decide which div to select

def index
@div_class_sponsors = current_user.nil? ? "sponsors_benefits_unlogged" : "sponsors_benefits_logged"
end

The view page above now becomes:

- @companies.each do |company|
  %div{:id => @div_class_sponsors}
    .icon
      = link_to company do
        = image_tag company.file
      %span{:class => 'classic'}
        %p.name= company.name
        %p= company.short_description
    .benefits= company.benefits

Very simple. Just an idea that came to my mind instead of using the ideas available on other resources.
. and # are used to represent div class and id resp.

But we can always use %div.

Wednesday 1 February 2012

Handling Background Jobs in Rails 3.2

Hi everybody,
What's up!

Today I am going to discuss about the background tasks management in rails.

Suppose we have a UserMailer which sends email to the users once they have successfully completed the registration process. Something like this:

class UserMailer < ActionMailer::Base
  default :from => "admin@example.com"
  def registration_confirmation(user)
    mail(:to => user.email, :subject => "Registration Confirmation")
  end
end

And in the controller,

def create
    @user = User.new(params[:user])
    if @user.save
      UserMailer.registration_confirmation(@user).deliver
      redirect_to root_url, :notice => "Signed up! Please check confirmation mail!"
    else
      render "sign_up"
    end
end



After the user hits the "Create User" button, he has to wait for some time while their mail is being delivered.
During this process, rails is busy and can't respond to any other requests.
This is a very bad user experience. After all why should one has to wait for some time unnecessarily.

Now there are many gems and plugins available to handle the background jobs. I prefer to use "delayed_job" gem.
Firstly, because it stores the jobs queue in the database table. Hence, no memory leak or loss of data.
Secondly, it's very easy to implement.

So, let's see what are the steps involved:

1) add:  gem 'delayed_job_active record' to the gemfile and run 'bundle install'.

2) Next, we need the jobs table so run the commands:
   $ rails generate delayed_job:active_record
   $ rake db:migrate

3) Next go to the controller and just replace the line
    UserMailer.registration_confirmation(@user).deliver
    with,
    UserMailer.delay.registration_confirmation(@user)

4) $ rake jobs:work

Step 4 will look for any new job in the "delayed_jobs" table and execute it in the background.

This was easy. We can add many more features to the "delayed_jobs" gem for which I suggest you to checkout this Github Link.

Thanks!!


Wednesday 11 January 2012

Don't loop around active record !!!

Hello everyone, it's been a long time since I have posted here.

Today, I am going to show you a way to optimize the performance of your active record queries.

Lets say we have model definitions like this:

class User < ActiveRecord::Base
  has_many :blogs
end

class Blog < ActiveRecord::Base
  has_many :comments
  belongs_to :user
end

class Comment < ActiveRecord::Base
  belongs_to :blog
end

Now,let's say we want to display the content of the comments  in the view regarding a particular user.

WRONG WAY

@user = User.where("email like ?","example@foobar.com")

@user.first.blogs.each do |blog|
   blog.comments.each do |comments|
       puts comments.content
   end
end

The Rails logfile will look something like this:

 User Load (1.1ms)  SELECT `users`.* FROM `users` WHERE (email like 'example@foobar.com') LIMIT 1
  Blog Load (0.7ms)  SELECT `blogs`.* FROM `blogs` WHERE `blogs`.`user_id` = 2
  Comment Load (0.5ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 3
  Comment Load (0.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 4
  Comment Load (0.7ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 8
  Comment Load (0.6ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 9
  Comment Load (41.4ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 11
  Comment Load (0.8ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` = 12

What happened? We started with a single user object, then issued a separate SQL command to retrieve the blogs for that user.
Additional SQL commands were required to retrieve the comments in each of those blogs.
In this case there were only a few blogs, but 6 queries were generated. A user with more blogs, more comments, or further nesting could easily result in dozens of SQL queries and a very slow
response.

RIGHT WAY

Fortunately, Rails 3.1 provides us with an easy way to deal with this problem as Active Record lets you specify in advance all the associations that are going to be loaded. This is possible by specifying the includes method of the Model.where / Model.find calls. With includes, Active Record ensures that all of the specified associations are loaded using the minimum possible number of queries.
Here's the way to do this:

@user =User.where("email like ?","example@foobar.com").includes(:blogs => :comments)

Looking into the log again and we see these 3 simple queries:

User Load (1.0ms)  SELECT `users`.* FROM `users` WHERE (email like 'example@foobar.com')
Blog Load (0.4ms)  SELECT `blogs`.* FROM `blogs` WHERE `blogs`.`user_id` IN (2)

Comment Load (0.5ms)  SELECT `comments`.* FROM `comments` WHERE `comments`.`blog_id` IN (3, 4, 8, 9, 11, 12)


And when we run the above loop again:


@user.first.blogs.each do |blog|
   blog.comments.each do |comments|
       puts comments.content
   end
end

In log what do we see??
NOTHING !!

Yes, it's really possible to minimize the database load to a huge extent.

This wraps up my post, hope it will help you write better code.