Forum: JRuby Poor performance with JRuby on Rails

Posted by Andre Zelenkovas (andrez)
on 2012-09-05 19:46
Hello

I have a typical CRUD Ruby on Rails app with a fairly complex model and
the performance in JRuby is 6 times slower than MRI considering median
response times. I'm running 30 minute tests with a single-threaded
client to ensure runtimes are warmed up. I've tried both Webrick,
warbler on JBoss and Torquebox, to no avail. I'm reaching out to the
forum hoping for some tips.

Issue/fact summary:

JRuby-Rack seems to take up to 150ms just to dispatch the request into
the controller.

ActiveRecord activity is much slower on JRuby (by a factor of 10
sometimes.)

JSON generation is actually faster in JRuby than MRI.

Tried jruby.compile.mode=FORCE and jruby.frameless.mode = true.
Frameless mode seems to improve performance by about 5%.

YAML serialization is slower than JSON serialization in JRuby but the
same in MRI.

JRuby runtimes are using massive amounts of memory (up to 40 MB of
garbage per request.)

I've tested with a single runtime in JRuby with 2 GB max heap and 512 MB
max perm size.

Any tips are appreciated.

Thanks
Andre
Posted by Christian MICHON (Guest)
on 2012-09-05 20:36
(Received via mailing list)
1/ use trinidad (no need for warbler yet)
2/ share at least 1 controller code, related model and views if possible
3/ tell us more about your persistence layer, especially if it is SQL
without indexes...



On 9/5/12, Andre Zelenkovas <lists@ruby-forum.com> wrote:
>
>
>
>
>
>


--
Christian
Posted by Benjamin Browning (Guest)
on 2012-09-05 20:56
(Received via mailing list)
Sounds like as a first step, which you may have already done to some 
extent, you need to figure out exactly where the differences are. Have 
you tried writing a simple script to exercise the relevant code paths of 
the web requests outside of a web server? That would isolate any 
differences between servers and compare raw JRuby vs MRI performance so 
you'd know what needs tracking down from there. Break the web request 
flow into manageable chunks and benchmark each chunk in isolation to see 
narrow down the possible culprits and make it easier for others to 
reproduce your results.

Ben
Posted by Richie Vos (Guest)
on 2012-09-05 21:23
(Received via mailing list)
From my testing, one thing to look out for is the time between Rails 
spent
overall on a request and the time Rails reports in the logs it worked on
the request can be quite different. Specifically, rails doesn't account 
for
time spent before the controller code runs (metals and so forth) as part 
of
it's response time.

We are using Tomcat, and when comparing the response times Rails said to
what Tomcat said, we saw drastically different numbers. I recommend
sticking a metal at the top of your middleware stack to get more 
accurate
numbers. Something like:

# app/metals/request_timer.rb
class RequestTimer
  def initialize(app)
    @app = app
  end

  def call(env)
    begin
      rack_response = nil
      response_time = Benchmark.ms do
        rack_response = @app.call(env)
      end
      rack_response
    ensure
      Rails.logger.info("Request actually took: 
#{response_time.to_i}ms")
      # ActionController::Dispatcher usually does this, but we're 
outside
of its
      # control here
      Rails.logger.flush if Rails.logger.respond_to?(:flush)
    end
  end
end

# config/application.rb
module YourApplication
  class Application < Rails::Application
#...
    config.middleware.insert(0, "RequestTimer")
  end
end


You can then move this middleware up and down the stack to find where
time's being lost (change the insert location). This may help track down
the JRuby-Rack missing time.

You said you're only doing 1 request at a time, but if you're not, watch
out for your config/database.yml's pool_size and wait_timeout settings.
Those can drastically affect your performance if you're using threaded
requests (eg config.threadsafe! and a threaded app server). This may 
help
track down the extra ActiveRecord time.

Does your massive amounts of memory usage stabilize over time? And what
type of garbage/memory (PermGen?). Might be worth hooking up something 
like
JConsole to track that one down.
Posted by Darshan Karandikar (darshan_k)
on 2012-09-06 08:44
Suggest you to go through http://www.ruby-forum.com/topic/1119702 where
I have shared my experience with JRoR. I also had faced performance
issues in the beginning. Summarizing the configs that solved the
problems for me:

1) Write thread-safe code + enable config.threadsafe! in Rails + set 
jruby.min and max runtimes = 1. This config. gave me the best CPU and 
memory utilization of all configs.
2) Increase the AR conn. pool wait timeout.
3) Warm up AR conn. pool by either using AR-JDBC bridge OR following
custom script (helps prevent the overhead of creating db conns at
runtime which was one bottleneck in my case):

#db pool warmup

puts "db pool warmup starting...................... #{Time.now}"
warm_up_count = APP_CONSTANTS['dbconn_warm_up_count'].to_i
conn_array = Array.new
for i in 1..warm_up_count do
   conn_array[i] = ActiveRecord::Base.connection_pool.checkout
   puts 'chekedout '+i.to_s+'......................'
end

puts 'warmed up...........==============..............'
for i in 1..warm_up_count do
   ActiveRecord::Base.connection_pool.checkin(conn_array[i])
   puts 'chekedin '+i.to_s+'......................'
end
puts "db pool warmup done........................... #{Time.now}"
Posted by Pradeep Singh (Guest)
on 2012-09-06 14:44
(Received via mailing list)
Put this code in a new file (I called mine middleware.rb) in 
initializers
directory and have a look at the results. Adjust according to your
middleware list by checking out 'rake middleware' -

timing_method = %q{
alias_method :old_call, :call
def call(env)
  time1 = Time.now
  ret_value = old_call(env)
  puts "time taken at #{self.class} => #{Time.now - time1}"
  ret_value
end
}


Rack::Lock.class_eval(timing_method)
ActiveSupport::Cache::Strategy::LocalCache::Middleware.class_eval(timing_method)
Rack::Runtime.class_eval(timing_method)
Rack::MethodOverride.class_eval(timing_method)
ActionDispatch::RequestId.class_eval(timing_method)
Rails::Rack::Logger.class_eval(timing_method)
ActionDispatch::ShowExceptions.class_eval(timing_method)
ActionDispatch::DebugExceptions.class_eval(timing_method)
ActionDispatch::RemoteIp.class_eval(timing_method)
ActionDispatch::Reloader.class_eval(timing_method)
ActionDispatch::Callbacks.class_eval(timing_method)
ActiveRecord::ConnectionAdapters::ConnectionManagement.class_eval(timing_method)
ActiveRecord::QueryCache.class_eval(timing_method)
ActionDispatch::Cookies.class_eval(timing_method)
ActiveRecord::SessionStore.class_eval(timing_method)
ActionDispatch::Flash.class_eval(timing_method)
ActionDispatch::ParamsParser.class_eval(timing_method)
ActionDispatch::Head.class_eval(timing_method)
Rack::ConditionalGet.class_eval(timing_method)
Rack::ETag.class_eval(timing_method)
ActionDispatch::BestStandardsSupport.class_eval(timing_method)
#RailsWarden::Manager.class_eval(timing_method)
App::Application.routes.class_eval(timing_method)


Also, may I request you to try one test with the gem 'webricknio' (run
server with 'rails s wnio')?

In my tests I've seen about 1MB increase in heap per request which I
already consider quite excessive.
Posted by Andre Zelenkovas (andrez)
on 2012-09-06 15:57
Hi all

Thanks for the suggestions, I really appreciate them. I'll try them as 
soon as I'm able and I'll post back results.

Andre
Posted by Joseph Athman (Guest)
on 2012-09-06 23:52
(Received via mailing list)
Is this in Development mode or Production mode?  Someone can correct me 
if
I'm wrong, but since Rails reloads classes in Development mode by 
default
you might not be allowing JRuby to JIT code enough since each request is
reloading a lot of classes.

What versions of JRuby, Java, and Rails are you using?  Also what DB and
ActiveRecord JDBC driver are you using?

Joe
Posted by Andre Zelenkovas (andrez)
on 2012-09-07 16:03
Hi Joseph

Production mode.
JRuby 1.6.7
Java 1.6.0_33
Ruby 1.9.3-p194
mysql_connector_java_5_1_13

Thanks
Andre
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.