1

I have a problem of performances with rails. When i do an ajax call to a controller like this :

def test
    @hotels = Hotel.all
    render :json => ['hotels' => @hotels ], :include=> [:country, :city]
end

It takes maybe 2-5 seconds to finish. I only have 40 hotels in my database. I think it very long... for example, the same request on Django will take 400ms

Did i forgot to configure well my environement?

I use Rails entreprise version and passenger.

EDIT : My log file :

     Started GET "/hotels/test.json" for 172.16.81.1 at Wed Oct 12 22:11:06 +0200 2011
    [paperclip] Duplicate URL for image with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in HotelImage class
    [paperclip] Duplicate URL for thumbnail with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Hotel class
    [paperclip] Duplicate URL for map with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Hotel class
    [paperclip] Duplicate URL for thumbnail with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in DestinationAlbumPhoto class
    [paperclip] Duplicate URL for map with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Destination class
    [paperclip] Duplicate URL for image with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Continent class
    [paperclip] Duplicate URL for thumbnail with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Destination class
    [paperclip] Duplicate URL for image with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Event class
    [paperclip] Duplicate URL for thumbnail with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in HotelAlbumPhoto class
    [paperclip] Duplicate URL for map with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Event class
      Processing by HotelController#test as JSON
      [1m[36mHotel Load (0.2ms)[0m  [1mSELECT `hotels`.* FROM `hotels`[0m
      [1m[35mCountry Load (0.1ms)[0m  SELECT `countries`.* FROM `countries` WHERE (`countries`.`id` = 3)
      [1m[36mCity Load (0.1ms)[0m  [1mSELECT `cities`.* FROM `cities` WHERE (`cities`.`id` = 2)[0m
    Completed 200 OK in 405ms (Views: 366.1ms | ActiveRecord: 0.3ms)

It's writen 405ms but firefox tell me 3,7sec.

My hotel model :

class Hotel < ActiveRecord::Base
  cattr_reader :per_page
  @@per_page = 16

  belongs_to :hotel_type
  belongs_to :hotel_theme
  belongs_to :country
  belongs_to :city
  belongs_to :destination
  belongs_to :continent

  has_many :hotel_comments, :dependent => :destroy

  has_many :hotel_album_photos, :dependent => :destroy

  has_many :hotel_activity_values

  has_many :hotel_service_values

  accepts_nested_attributes_for :hotel_album_photos

  has_attached_file :thumbnail, :styles => { :medium => "300x300>", :thumb => "191x134>"} , :default_url => '/images/default/missing.png' 
  has_attached_file :map, :styles => { :medium => "300x300>", :thumb => "191x134>"} , :default_url => '/images/default/missing.png' 

  scope :country, lambda { |country_id|
     self.scoped.where('country_id IN ( ? )', country_id) unless country_id.blank?
  }

  scope :selection, lambda { |selection|
     self.scoped.where('selection = ? ', 1) unless selection.blank?
  }

  scope :city, lambda { |city_id|
      self.scoped.where('city_id IN ( ? )', city_id) unless city_id.blank?
  }

  scope :hoteltype, lambda { |type|
      self.scoped.where('hotel_type_id IN ( ? )', type) unless type.blank?
   }

  scope :theme, lambda { |theme|
      self.scoped.where('hotel_theme_id IN ( ? )', theme) unless theme.blank?
   }

  scope :prices, lambda { |prices|
      condition = []
      prices.each do |price|
        pricesArray = price.split('-')
        condition.push '(price BETWEEN ' + pricesArray[0] + ' AND ' + pricesArray[1] + ')'
      end
      self.scoped.where(condition.join(' OR ')) 
   }

   scope :order_by_price, lambda { |direction|
     self.scoped.order('price ' + direction)
   }

   scope :order_by_rate, lambda { |rate|
     self.scoped.order('global_rate ' + rate)
   }

   scope :services, lambda { |services|
      {:joins => [:hotel_service_values ]  , :conditions => { :hotel_service_values => {:hotel_service_id  => services}}}
   }

  scope :limiter, lambda { |limiter|
      self.scoped.limit(limiter)
   }

end

Thank you for help.

Sebastien
  • 6,640
  • 14
  • 57
  • 105
  • Can you see what the console output or log file is telling you? How much time is spent in the database, rendering, etc? – rwilliams Oct 13 '11 at 09:07
  • Is this in development or production? What queries are you seeing in your log? How are your Hotel, Country, and City associations set up? – Jordan Running Oct 13 '11 at 09:24
  • @Sebastien you've definitely got the N+1 problem I suggested. If you make my suggested change, you'll end up with just 3 SQL lines in that file – Matthew Rudy Oct 13 '11 at 09:31
  • I use development environment. – Sebastien Oct 13 '11 at 09:32
  • @Sebastien, I'd suggest you look at the network time too. It looks like this is not running on your local machine. 3 seconds would include the latency, plus the response time, plus the transfer time. How big in kb is the json returned? – Matthew Rudy Oct 13 '11 at 09:33
  • @Matthew Rudy You right but i still has too long requests... – Sebastien Oct 13 '11 at 09:34
  • @Sebastien how big is the json response, firefox should tell you that in the network view. – Matthew Rudy Oct 13 '11 at 09:36
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/4222/discussion-between-matthew-rudy-and-sebastien) – Matthew Rudy Oct 13 '11 at 09:36
  • @Matthew Rudy You right, i use a virtual machine with VMware but i tried to deploy my apps on my dedicated server... same performances... – Sebastien Oct 13 '11 at 09:37

3 Answers3

7

Looking at you code, my guess is you have a simple "N+1" problem.

Namely you load @hotels into an array, but when you come to produce the json you load the country and cityfor eachhotel.

So for your 40 hotels, you have to do a total of 81 database queries.

This can simply be improved by doing an include when you load.

In the old style

Hotel.all(:include => [:country, :city])

In Rails 3 style

Hotel.includes(:country, :city).all

With this change you should only be making 3 database calls in total.

See the Rails Guide on Eager Loading for more info.

Matthew Rudy
  • 16,724
  • 3
  • 46
  • 44
0

Looking at the Log, Rails seems to be thinking it got the response out in 405 ms. This leaves rest of the stack to consider:

  1. Rails (✓)
  2. Passenger
  3. Apache
  4. Network
  5. DNS resolving
  6. Browser rendering

First, see how other browsers fare. I have seen occasional issues on one of our (non-rails) sites with Firefox, while Webkit-based browsers (Safari, Chrome) are OK. Maybe Firefox chokes up for some reason (e.g. DNS resolving issues or something other).

If all browsers are the same (or especially, if Firefox is the culprit), then open Firebug and take look at the Net tab, especially the XHR (AJAX) tab under that. Hover your mouse over one of the queries' time bar to see breakdown of where the time went: DNS, Connecting, Sending, Waiting, Receiving.

If Firebug indicates that the time is spent on the request itself (Sending, Waiting, Receiving), then take a look at Apache log - turn on CustomLog time reporting (add %D - see documentation) and see how long Apache thinks these requests take.

This should narrow down your problem to further analyze causes.

Community
  • 1
  • 1
Laas
  • 5,978
  • 33
  • 52
-1

I was using VMWare to make my web server running. My performances problems was about that. I find my solution here :

Webrick is very slow to respond. How to speed it up?

Community
  • 1
  • 1
Sebastien
  • 6,640
  • 14
  • 57
  • 105