$30 off During Our Annual Pro Sale. View Details »

Using JRuby: What, Why, When, and How

headius
November 30, 2022

Using JRuby: What, Why, When, and How

JRuby has just been updated for Ruby 3.1 support, bringing compatibility up to date for the most widely-deployed alternative Ruby implementation! This talk will teach you all about JRuby: what is it, when should you use it, how to get started and why it matters. Learn why Ruby shops around the world choose JRuby for world-class concurrency, GC, JIT, and cross-platform support.

headius

November 30, 2022
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Using JRuby:


    What, Why, When, and How
    Charles Oliver Nutter


    @headius

    View Slide

  2. Thank You Yarden!
    • Yarden Laifenfeld could not be here


    • Check out her JRuby talk!


    • "Ruby & JVM: A Love Story"


    • Really cool talk about her
    experiences using and building
    apps with JRuby!


    • @YardenLaif

    View Slide

  3. View Slide

  4. Cheers to Tom Enebo
    • We co-lead JRuby since 2006


    • Almost 16 years working together!


    • So many conferences!


    • So many beers!


    • @[email protected]


    • @tom_enebo on Twitter

    View Slide

  5. View Slide

  6. What is JRuby?
    • An implementation of Ruby atop the Java Virtual Machine


    • Ruby implementation
    fi
    rst, JVM language second


    • Many bene
    fi
    ts from JVM ecosystem


    • Ruby code should "just work"


    • Different extension API, no forking, parallel threads


    • https://www.jruby.org/

    View Slide

  7. View Slide

  8. Ruby Compatibility
    • JRuby 9.4 is out now!


    • Ruby 3.1 compatible, language feature-complete


    • Most of core, stdlib features from 2.7 to 3.1 released in 9.4.0.0


    • JRuby 9.3 (Ruby 2.6 compat)


    • Maintenance through 2023 as needed


    • Compatibility before performance!


    • Now we can refocus on optimization again

    View Slide

  9. Why Ruby on JVM?
    • Widely deployed and widely supported runtime


    • Excellent JIT, GC, concurrency, and platform support


    • Tens of thousands of libraries


    • Rich tools for monitoring, pro
    fi
    ling, debugging


    • "Write once, run anywhere": JRuby works on many platforms

    View Slide

  10. JVM Tools and GC

    View Slide

  11. Parallel and Concurrent

    View Slide

  12. Fun Stuff
    event(:player_egg_throw) do |e|
    e.hatching = true
    e.num_hatches = 120
    e.player.mesg "hatched"
    end
    Purugin

    View Slide

  13. Getting Started

    View Slide

  14. View Slide

  15. JRuby Install
    • Install a JDK


    • Java 11+ recommended, there's many distributions out there


    • Java 8 supported for 9.4 and lower


    • Install JRuby


    • Recommended: system package, Ruby installer, Docker image


    • Download tarball/zip or Windows installer

    View Slide

  16. Test it out!
    [] ~ $ rvm use jruby


    Using /Users/headius/.rvm/gems/jruby-9.4.0.0


    [] ~ $ irb


    :001 > runtime = java.lang.Runtime.runtime


    => #


    :002 > runtime.available_processors


    => 8


    :003 > runtime.free_memory


    => 91420584


    :004 >

    View Slide

  17. JRuby on Rails

    View Slide

  18. JRuby on Rails?
    • JRuby has run Rails since 2007!


    • "Are there JRuby users running Rails applications?"


    • Oh yes! And at large scale!


    • Bene
    fi
    t from the JVM, libraries, languages


    • The best way to scale large Rails apps today!

    View Slide

  19. --- testapp_mri_pg/Gemfile


    +++ testapp_jruby_pg/Gemfile


    -# Use postgresql as the database for Active Record


    -gem 'pg', '>= 0.18', '< 2.0'


    +# Use jdbcpostgresql as the database for Active Record


    +gem 'activerecord-jdbcpostgresql-adapter'


    # See https://github.com/rails/execjs#readme for more supported runtimes


    -# gem 'mini_racer', platforms: :ruby


    -


    +gem 'therubyrhino'


    group :development do


    - # Spring speeds up development...


    - gem 'spring'


    - gem 'spring-watcher-listen', '~> 2.0.0'


    end


    Minimal Con
    fi
    g Diffs
    --- testapp_mri_pg/config/database.yml


    +++ testapp_jruby_pg/config/database.yml


    @@ -65,5 +81,7 @@


    production:


    <<: *default


    database: rails_prod


    + host: localhost


    + port: 5432


    username: rails_prod


    password: rails_prod
    --- testapp_mri_pg/config/puma.rb 2019-04-19 04:48:51.425474315 +0000


    +++ testapp_jruby_pg/config/puma.rb 2019-04-17 08:56:53.529154189 +0000


    @@ -4,7 +4,7 @@


    # the maximum value specified for Puma. Default is set to 5 threads for minimum


    # and maximum; this matches the default thread size of Active Record.


    #


    -threads_count = ENV.fetch("RAILS_MAX_THREADS") { 2 }


    +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 20 }


    threads threads_count, threads_count




    # Specifies the `port` that Puma will listen on to receive requests; default is 3000.


    @@ -21,7 +21,7 @@


    # Workers do not work on JRuby or Windows (both of which do not support


    # processes).


    #


    -workers ENV.fetch("WEB_CONCURRENCY") { 2 }


    +# workers ENV.fetch("WEB_CONCURRENCY") { 2 }




    # Use the `preload_app!` method when specifying a `workers` number.


    # This directive tells Puma to first boot the application and load code

    View Slide

  20. Rails 7
    • Rails 7 works on JRuby 9.4!


    • ActiveRecord for JRuby needs some updates


    • Mostly small changes to our ActiveRecord JDBC adapter


    • Performance and compatibility looking good!

    View Slide

  21. Rails 7
    actioncable: 203 runs, 921 assertions, 0 failures, 10 errors
    actionmailbox: 92 runs, 238 assertions, 0 failures, 0 errors
    actionmailer: 230 runs, 516 assertions, 0 failures, 1 errors
    actionpack: 3550 runs, 16802 assertions, 1 failures, 1 errors
    actiontext: 80 runs, 145 assertions, 7 failures, 2 errors
    actionview: 2424 runs, 5395 assertions, 2 failures, 4 errors
    activejob: 368 runs, 837 assertions, 0 failures, 0 errors
    activemodel: 966 runs, 2853 assertions, 5 failures, 0 errors
    activestorage: 392 runs, 1121 assertions, 0 failures, 0 errors
    activesupport: 5188 runs, 12926 assertions, 43 failures, 28 errors
    99% passing!

    View Slide

  22. Failure:
    TimeWithZoneTest#test_minus_with_time_precision [activesupport/
    test/core_ext/time_with_zone_test.rb:340]:
    Expected: 86399.999999998
    Actual: 86399.99999999799
    😩

    View Slide

  23. activerecord-jdbc-adapter
    • Version-matched to Rails


    • 60.0 for Rails 6.x, 70.0 for Rails 7.x, etc


    • 70.0 in progress


    • sqlite, mysql working pretty well, gems are out there


    • 7745 runs, 25040 assertions, 32 failures, 14 errors, 19 skips


    • postgresql needs more updates, coming soon

    View Slide

  24. Performance

    View Slide

  25. What Matters to You?
    • Straight-line performance?


    • High concurrency?


    • Startup time?


    • Warmup time?


    • Memory size?


    • Optimizing for only one of these can penalize the others

    View Slide

  26. Benchmarks
    • Benchmarks are very situational


    • What looks good in a microbench may not translate to production


    • Three example benchmarks


    • railsbench, small Rails blog on SQLite, no web server, tight loop


    • Rails blog on MySQL, end to end through Puma


    • ActiveRecord reads and updates

    View Slide

  27. railsbench
    • Based on simple scaffolded blog app


    • SQLite database, single thread, all in one process


    • No web server, no connections, just loop on requests


    • Good for analyzing Rails core framework performance


    • Not great for real-world end-to-end measurement

    View Slide

  28. The Players
    • Ruby 3.1.2 with and without --yjit


    • Truf
    fl
    eRuby 22.2 JVM CE


    • Newer versions may be better


    • JRuby 9.4 on Java 17

    View Slide

  29. time to run 2000 requests (lower is better)
    0
    550
    1100
    1650
    2200
    Time
    1,460ms
    1,834ms
    1,704ms
    2,152ms
    CRuby 3.1 CRuby 3.1 yjit Tru
    ffl
    eRuby JRuby

    View Slide

  30. What You're Missing
    • Straight-line performance?


    • High concurrency?


    • Startup time?


    • Warmup time?


    • Memory size?

    View Slide

  31. Memory Footprint
    • More complex runtimes take more memory


    • Different GC strategies take more memory


    • CRuby has been optimized for startup time and memory use


    • Super valuable but may not help server apps

    View Slide

  32. Memory Footprint
    0
    750
    1500
    2250
    3000
    Memory
    900MB
    2,400MB
    80MB
    CRuby 3.1 Tru
    ffl
    eRuby JRuby

    View Slide

  33. Warmup Time
    • Optimizing runtimes just take longer to warm up


    • Code needs to be pro
    fi
    led, analyzed, compiled


    • GC needs to
    fi
    nd sweet spot for heap size, generations


    • Known issue, but we and JVM folks always try to improve


    • Pre-warm new deploys (or just accept it will start off slower)


    • JVM tooling to bootstrap into a warm VM

    View Slide

  34. Warmup Over Time
    0ms
    450ms
    900ms
    1350ms
    1800ms
    Iteration (2000 requests each)
    1 2 3 4 5 6 7 8 9 10
    CRuby 3.1 yjit

    View Slide

  35. Warmup Over Time
    0ms
    5000ms
    10000ms
    15000ms
    20000ms
    Iteration (2000 requests each)
    1 2 3 4 5 6 7 8 9 10
    JRuby

    View Slide

  36. Warmup Over Time
    0ms
    5000ms
    10000ms
    15000ms
    20000ms
    Iteration (2000 requests each)
    1 2 3 4 5 6 7 8 9 10
    CRuby 3.1 yjit JRuby

    View Slide

  37. Warmup Over Time
    0ms
    6000ms
    12000ms
    18000ms
    24000ms
    Iteration (2000 requests each)
    1 2 3 4 5 6 7 8 9 10
    Tru
    ffl
    eRuby

    View Slide

  38. Warmup Over Time
    0ms
    7500ms
    15000ms
    22500ms
    30000ms
    Iteration (2000 requests each)
    1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69
    CRuby 3.1 yjit Tru
    ffl
    eRuby JRuby

    View Slide

  39. Benchmarks Lie
    • The only benchmark that matters is your code


    • Your code is probably not requesting the same posts in a loop


    • We can make this a bit more real with full end-to-end


    • Puma web server, MySQL backend


    • External request driver (siege)


    • Max out CPU (high concurrency)

    View Slide

  40. End-to-end Rails Blog
    • Scaffolded "blog" application on MySQL


    • Local i7 laptop


    • CRuby 3.1: 16 workers, 2 threads each


    • JRuby 9.4: 32 threads


    • Truf
    fl
    eRuby: 32 threads but had issues

    View Slide

  41. Setup
    • Local everything including benchmark driver


    • MySQL in a Docker container


    • Scaffolded app is super minimal


    • Using siege: 16 concurrent, 10s intervals, benchmark mode


    • Warmup time is a thing...

    View Slide

  42. requests per second (higher is better)
    0rps
    1500rps
    3000rps
    4500rps
    6000rps
    3m
    5,160rps
    668rps
    2,361rps
    2,070rps
    CRuby 3.1 CRuby 3.1 yjit Tru
    ffl
    eRuby JVM CE JRuby

    View Slide

  43. 0MB
    1500MB
    3000MB
    4500MB
    6000MB
    1,400MB
    6,000MB
    1,520MB
    Ruby 3.1 Tru
    ffl
    eRuby JRuby

    View Slide

  44. 0MB
    400MB
    800MB
    1200MB
    1600MB
    950MB
    1,400MB
    1,520MB
    Ruby 3.1 JRuby JRuby with max heap

    View Slide

  45. Requests per second, 10s siege runs (higher is better)
    0
    1500
    3000
    4500
    6000
    1 2 3 4 5 6 7 8 9 10
    CRuby 3.1 CRuby 3.1 yjit JRuby

    View Slide

  46. ActiveRecord Performance
    • Steady improvement over the years


    • Lots of work to slim down, use prepared statements, etc


    • Still a pretty heavy framework


    • JRuby performance has steadily increased


    • Thanks in part to JVM improvements!


    • Numbers using local MySQL via Docker

    View Slide

  47. Select Performance
    select
    Iterations per second
    0
    1250
    2500
    3750
    5000
    binary boolean datetime string text *
    3,974
    4,798
    4,853
    4,499
    4,569
    4,798
    2,410
    2,584
    2,480
    2,674
    2,713
    2,630
    CRuby 3.1 JRuby 9.4

    View Slide

  48. Update Performance
    Update speci
    fi
    c typed column and save!
    Iterations per second
    0
    5000
    10000
    15000
    20000
    binary boolean datetime string text timestamp
    16,407
    18,580
    18,969
    14,630
    18,934
    19,751
    7,615
    8,406
    8,341
    6,507
    8,509
    8,491

    View Slide

  49. True Story
    • Large Rails application using 40 xlarge on EC2


    • 40 worker processes per server


    • 100k-150k req/min, 50-75ms response times


    • Migrated app to JRuby, made more use of threading


    • Down to 10 xlarge, 75% cost reduction


    • Consistently over 150k req/min, 30ms response times

    View Slide

  50. Wrapping Up

    View Slide

  51. JRuby Future
    • JRuby 9.4 is out!


    • You can help us by trying it and reporting issues


    • Maybe submit a PR?


    • Big optimization work coming the rest of this year


    • Many call types do not optimize, no splitting, lots of big plans


    • Upcoming JVM features: native
    fi
    bers, built-in FFI, new JITs and GCs

    View Slide

  52. JRuby on Rails Future
    • Rails 6 support is pretty stable


    • Rails 7 support is looking great!


    • As your app grows, JRuby can help you scale


    • Reduce resources, save money


    • Let's talk about running your app on JRuby!

    View Slide

  53. Thank You!
    • Charles Oliver Nutter


    [email protected]


    • @headius(@mastodon.social)


    • https://github.com/jruby/jruby


    • https://www.jruby.org

    View Slide