Friday, October 14, 2011

Simple nginx tuning for rails application on production server

Recently I was working on tuning my production environment for better performance. In production server mainly three things makes a difference to the web application,
  1. REE-GC Tuning
  2. Nginx Tuning
  3. Passenger Tuning

As I have already done REE-GC tuning in previous articles, so lets discuss on Nginx tuning. Passenger tuning will be get covered in subsequent articles.

In Nginx there are many ways to boost your application performance, but I would like to share few important tweaks.

To validate performance improvement, you can use page-speed or Yslow

Before doing these tweaks on my rails application, yslow and page-speed score was around 25-35/100. After doing these tweaks it increased to 90-96/100

To get 100/100 we have to write a good code-design not best code-design ;). In our application the performance dip is due to not using CSS sprite.


A. Enable Gzip :
Gzip helps to compress and decompress the data on-the-fly between browser and server. Nginx has HttpGzipModule for same.

Add following lines into to http block nginx.conf
gzip  on;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript;
gzip_vary on;
gzip_disable     "MSIE [1-6]\.";
#gzip_proxied any;
#gzip_buffers 128 128k;
#gzip_min_length  1100;
Depending on your need you can set the above commented directives.


B. Enable caching :
Setting an expiry date or a maximum age in the HTTP headers for static resources instructs the browser to load previously downloaded resources from local disk rather than over the network.

Add following lines into to server block nginx.conf
   location ~* \.(ico|css|js|gif|jpe?g|png|swf)(\?[0-9]+)?$ {
         expires max;
         break;
   }

C. Minify JavaScript :
Compacting JavaScript code can save many bytes of data and speed up downloading, parsing, and execution time.
For this you can use any third party tools or gems like Jammit,asset_packager to minify your JavaScript files. Rails 3 has built in support.

Sample nginx.conf :
Apart from this you can use multiple assets server for static resources, which will be covered in subsequent articles.

Tuesday, August 23, 2011

Install Redis server on Ubuntu maunally as service

This post is all about installing redis server manually on Ubuntu system and then setup it as a service.

For this post I have originally referred a blog post of denofubiquity.

By default Ubuntu provides redis server installation by

sudo apt-get install redis-server

And you done with the installation of redis server.

But if you want to install latest version of redis server, then you can go through the rest of post.

wget http://redis.googlecode.com/files/redis-2.2.12.tar.gz
# You can specify a link of redis-server which you want to install
tar -zxf redis-2.2.12.tar.gz
cd redis-2.2.12
make
sudo make install

Now setup a redis.conf. I have copied a redis.conf from default installation of redis-server which I have used. If you want to install redis-server for your production machine you may refer a redis.conf from denofubiquity's post

wget https://raw.github.com/gist/1164482/77e4ecf14ffac42b0e987e7ffe16cb757d734ff9/redis.conf
sudo mkdir /etc/redis
sudo mv redis.conf /etc/redis/redis.conf
Copy redis-server startup scripts.
wget https://raw.github.com/gist/1164503/d1bc2cc6782b943d9b28aa93fc7038f4ae5a905f/redis-server
sudo mv redis-server /etc/init.d/redis-server
sudo chmod +x /etc/init.d/redis-server
sudo useradd redis
sudo mkdir -p /var/lib/redis
sudo mkdir -p /var/log/redis
sudo chown redis.redis /var/lib/redis
sudo chown redis.redis /var/log/redis
Mark redis server as startup services.
sudo update-rc.d redis-server defaults
Start your redis server.
sudo /etc/init.d/redis-server start

Note :

You can customise your redis configuration as per your requirements.
Like from Redis 2.2, it supports unix socket, so you can use it if required.
- Specify socket in redis.conf using this line
unixsocket /tmp/redis.sock
- Tell Redis.new() to read it as required. like
Redis.new(:path => "/tmp/redis.sock")

Also you can customise memory configuration in your redis.conf.

Reference :

http://www.denofubiquity.com/nosql/412

Wednesday, August 17, 2011

Nginx rewrite non-www url to www

One of my project facing issue with non-www url. At Browser side we are calling multiple services from different domain and same time we are sharing same cookies for calling services from our website. But it was giving authentication failure for our services.

Issue:

We have embedded zimbra collaboration suite into our rails application. Once user logged into our application, he will automatically gets Single signed into zimbra collaboration suite. We ware showing zimbra UI in separate iframe on web page.

Zimbra has nice feature to extend their own functionality using zimlet.

Zimlet is simply JavaScript code which runs on client side(On Browser).

Using the power of zimlet we want to access the services of our own application, where we ware sharing cookies which already got created for portal on browser side when user signed in.

Every thing was working fine on production. But some time some users were facing issue of authentication failure for zimlet functionality, even though they have signed in into rails application.

After digging into issue, found that some users accessing the application using without www. i.e "https://abcrails.com", and zimlet was trying to access a rails application using "https://www.abcrails.com" where authentication is failing. As cookies ware created for "https://abcrails.com" and not for "https://www.abcrails.com"

For this I have only one solution depending on my requirements, is force non-www url to www.

So following nginx configuration is for:

1. Force http reuest to https

2. Force non-www url to www

Monday, August 15, 2011

Replace DelayedJob with Resque

Background task in Ruby on Rails

One of our task of migrating background task from Delayed_job server to the Resque server. But I have kept this task with low priority in my task list queue.

But since last week, our IT team is facing an issue with Delayed_job server. Our delayed_job server is getting crashed 20 times in 24 hours.

I havn't done the root cause analysis. but I have decided to take this an opportunity to replace delayed_job to resque server on high priority.

Is is not mean that Resque is better than Delayed_job. They have their own features. The working functionality of both server is same.

As Resque is best fitting in our requirements I have choose Resque in place of Delayed_job.

Also our IT team don't have any way or clue get the list of background tasks which are completed, which are pending and which are currently in process.

Why Resque server:

Well following info is sufficient from page https://github.com/defunkt/resque

Resque vs DelayedJob

How does Resque compare to DelayedJob, and why would you choose one over the other?

  • Resque supports multiple queues
  • DelayedJob supports finer grained priorities
  • Resque workers are resilient to memory leaks / bloat
  • DelayedJob workers are extremely simple and easy to modify
  • Resque requires Redis
  • DelayedJob requires ActiveRecord
  • Resque can only place JSONable Ruby objects on a queue as arguments
  • DelayedJob can place any Ruby object on its queue as arguments
  • Resque includes a Sinatra app for monitoring what's going on
  • DelayedJob can be queried from within your Rails app if you want to add an interface

If you're doing Rails development, you already have a database and ActiveRecord. DelayedJob is super easy to setup and works great. GitHub used it for many months to process almost 200 million jobs.

Choose Resque if:

  • You need multiple queues
  • You don't care / dislike numeric priorities
  • You don't need to persist every Ruby object ever
  • You have potentially huge queues
  • You want to see what's going on
  • You expect a lot of failure / chaos
  • You can setup Redis
  • You're not running short on RAM

Choose DelayedJob if:

  • You like numeric priorities
  • You're not doing a gigantic amount of jobs each day
  • Your queue stays small and nimble
  • There is not a lot failure / chaos
  • You want to easily throw anything on the queue
  • You don't want to setup Redis

In no way is Resque a "better" DelayedJob, so make sure you pick the tool that's best for your app.

Action:

This task is done for, quick solution to the production issue. So you might also use it, if you have same issue.

This task I have done on one of our project which is running on rails 2.3.5 with REE 1.8.7. But it will work same for rails 3 also.

Before diving into migration, I highly encourage you to checkout railscasts 171(delayed_job) and RAILSCASTS-271(Resque).

1. Install Redis server

It is dependency for resque server. Like Delayed_jobs uses database(delayed_jobs table), Resque uses Redis server.

I am using ubuntu so,

> sudo apt-get install redis-server

If you want to install latest version of the Redis you can install manually.

2. Install resque gem

If you are using bundler mention it in Gemfile or in your config/environment.rb depending on your application.

3. Load Resque server tasks

Add a file called lib/tasks/resque.rake

# loads all rake task of resque
require 'resque/tasks'

# following statement is required only if your background task needs rails enviroment else skip it
task "resque:setup" => :environment

4. Replace your Delayed_job code with Resque

A.

Find a places in code where you or your developers has been used delayed_jobs.

For this you can simply find a line "send_later" or "Delayed::Job.enqueue" in your code.

Use your development IDE or use grep tool from command line

> cd your_project

> grep -rn "send_later" .

and

> grep -rn "Delayed::Job.enqueue" .

B.

From the above result you will able to find location where delayed_job is used.

So if your are using SomeModel.send_later(function_name, parameter1, parameter2, ..., parameter n)

Replace a line like this

#SomeModel.send_later(function_name, parameter1, parameter2, ..., parameter n)
Resque.enqueue(NameOfModelOrClass, parameter1, parameter2, ..., parameter n)

Name of NameOfModelOrClass can be define from the following step

Either use one of the following

1. Modify respective model

Add following line into the model,

@queue = :name_of_queue

Name of the queue can be any thing whatever you want. You can also logically separate background task by using queue name.

Mofify function_name in such way,

def self.function_name(parameter1, parameter2, ..., parameter n)
{
	# Defination of a function
}

to,

#def self.function_name(parameter1, parameter2, ..., parameter n)
def self.perform(parameter1, parameter2, ..., parameter n)
{
	# Defination of a function
}

2. Separate code from model which I have preferred.

Define class in lib folder(any location you want)

Class FileImportWorker
{
	@queue = :name_of_the_queue

	def self.perform()
	{
		# cut the defination of the function_name and paste here
	}

}

Pass this class name to Resque.enqueue method.

C.

if you are using Delayed::Job.enqueue(ClassOfJob.new(parameter1, parameter2, ..., parameter n))

#Delayed::Job.enqueue(ClassOfJob.new(parameter1, parameter2, ..., parameter n))
Resque.enqueue(ClassOfJob, parameter1, parameter2, ..., parameter n)

e.g

class NewsletterJob < Struct.new(:text, :emails)
    def perform
      emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
    end    
  end 
Delayed::Job.enqueue NewsletterJob.new('lorem ipsum...', Customers.find(:all).collect(&:email))

to,

class NewsletterJob
    @queue = :name_of_the_queue

    def self.perform(text, emails)
      emails.each { |e| NewsletterMailer.deliver_text_to_email(text, e) }
    end    
  end 
Resque.enqueue(NewsletterJob, 'lorem ipsum...', Customers.find(:all).collect(&:email))

5. Now start your Resque server,

You can start server using a following command.

rake resque:work QUEUE=* COUNT=1

QUEUE=* : specify on which queue to work. * for all Queue

COUNT=1 : Number of worker process

6. Access to queue

Now you can run your code and can have look of pending and in-progress tasks. For this run the following command,

resque-web

It will start resque web interface on port 5678

http://localhost:5678

Bonus Point:

1. Mount Reqsue web interface server into your application

Instead of accessing resque web interface using localhost:5478, you can mount web interface into your application.

Add the following line into your config/routes.rb

mount Resque::Server, :at => "/resque"

Now, access via http://your_server/resque

Also, you can provide basic authentication for it,

add a file called config/initializers/resque_auth.rb with following code,

Resque::Server.use(Rack::Auth::Basic) do |user, password|
  password == "secret"
end

2. You can club background tasks (which we have defined in lib folder) into single folder

As done in rails cast you can add a folder app/workers, and keep your all tasks file in it.

or

Which I done by adding folder lib/workers

Only you need to load this folder into your application,

In rails 2,

Add file config/initializers/load_workers.rb with following code.

Dir["#{RAILS_ROOT}/lib/workers/*.rb"].each { |f| require(f) }

In rails 3,

add following line in application.rb,

config.autoload_paths += %W(#{config.root}/lib/workers)

Troubleshooting:

1. Resque database connection issue

We are using postgresql in our portal but found that when I pushed my code on testing server where we are using same configuration which we have for our production ( We have configured Nginx + Passenger + REE for same), resque was not working.

After looking into resque-web interface found following error,

PGError: server closed the connection unexpectedly

Which I solved by adding following code into /lib/tasks/resque.rake

task "resque:setup" => :environment do
  ENV['QUEUE'] = '*'

  Resque.after_fork do |job|
    ActiveRecord::Base.establish_connection
  end

end

desc "Alias for resque:work (To run workers on Heroku)"
task "jobs:work" => "resque:work"

You can find this solution in following URL.

RAILSCASTS-271(Resque)

http://stackoverflow.com/questions/2611747/rails-resque-workers-fail-with-pgerror-server-closed-the-connection-unexpectedly

I didn't find time to do root cause analysis for this issue, but I thing this is due to passenger as it was working well on my development machine in production mode.

2. Sequence issue

Exact error I forgot but I have faced a sequence related error. Solution was to not to pass a complex object in call Resque.enqueue, like ActiveRecord, File data etc.

3. undefined method `******' for #Hash:000

This is due to, if your are storing complex object(ActiveRecord) in resque. Resque uses Redis server which is key-value store, which returns object in hash.

So tackle this simple pass object-id as parameter and in background processing code extract the original object again using ID.

Saturday, May 28, 2011

Convert string to a class/model

This post is related to how to convert string to class ( model ). I came across this situation while migrating one of my organization's project on rails 3.

Many times we came across a situation where we need to convert a string to respective class or model. So that we can call methods on it. Generally to handle this situation new rails developers use case statement like this,

case class_name_string
when 'Account'
object = Account.find(id)

when 'Contact'
object = Contact.find(id)

when 'Opportunity'
object = Opportunity.find(id)

when 'Campaign'
object = Campaign.find(id)

when 'Model1'
object = Model1.find(id)

when 'Model2'
object = Model2.find(id)
:
:
:
:
when 'Modeln'
object = Modeln.find(id]) 
end

This can be actually done pretty easily by using constantize in only one line

class_name_string.constantize.find(id)

Sunday, May 22, 2011

Touch pad is not working on Ubuntu 11.04

Following is blog post with respect to the problem which I faced on my dell xps laptop with Ubuntu 11.04. My mouse pad(Touch pad) was not working after login screen.

After some googling I found forum articles which describing same problem. Those peoples have different version of Ubuntu with different manufactures of laptop.

But I didn't found an exact cause of this issue. One of the person from the forum was saying, this is might be due to use of mobile broadband network.

Might be this is the cause because, I am also using mobile broadband on my laptop where issue is occurring. But at the same time in my company I am using Ubuntu 11.04 on my development machine with wired Internet but I never faced this issue.

Anyways following is the solution,
Execute following command on terminal

gconftool-2 --set --type boolean /desktop/gnome/peripherals/touchpad/touchpad_enabled true


Reference:

https://help.ubuntu.com/community/SynapticsTouchpad

http://ubuntuforums.org/showthread.php?t=1658103

Friday, April 15, 2011

Configure Rack-bug for rails 3

While migrating one of my rails application on rails 3, I thought of using rack-bug simultaneously for improving performance.

Following are the steps which I used for configuring rack-bug for rails 3 (3.0.6).


1. Install rack-bug (branch rails3) as plugin
   cd vendor/plugins
   git clone -b rails3 https://github.com/brynary/rack-bug.git


If you want to you it as gem then add following line into Gemfile
gem 'rack-bug', :git => 'https://github.com/brynary/rack-bug.git', :branch => 'rails3'


2. Replace the code from file actionview_extension.rb
which is avilable in vendor/plugins/rack-bug/lib/rack/bug/panels/templates_panel/ as specified in bug of rack_bug repository


if defined?(ActionView) && defined?(ActionView::Template)
  ActionView::Template.class_eval do
    def render_with_rack_bug(*args, &block)
      Rack::Bug::TemplatesPanel.record(virtual_path) do
        render_without_rack_bug(*args, &block)
      end
    end
    alias_method_chain :render, :rack_bug
  end
end

If you are using gem override the specified file in some way


3. Add following lines into your config.ru
   require 'rack/bug'
   use Rack::Bug, :secret_key => "someverylongandveryhardtoguesspreferablyrandomstring"
   run myapp


4. Start your server and access the URL http://your_app/__rack_bug__/bookmarklet.html
   and enter the password.