Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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
  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
  3. None
  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
  5. None
  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/
  7. None
  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
  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
  10. JVM Tools and GC

  11. Parallel and Concurrent

  12. Fun Stuff event(:player_egg_throw) do |e| e.hatching = true e.num_hatches =

    120 e.player.mesg "hatched" end Purugin
  13. Getting Started

  14. None
  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
  16. Test it out! [] ~ $ rvm use jruby Using

    /Users/headius/.rvm/gems/jruby-9.4.0.0 [] ~ $ irb :001 > runtime = java.lang.Runtime.runtime => #<Java::JavaLang::Runtime:0x64a896b0> :002 > runtime.available_processors => 8 :003 > runtime.free_memory => 91420584 :004 >
  17. JRuby on Rails

  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!
  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
  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!
  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!
  22. Failure: TimeWithZoneTest#test_minus_with_time_precision [activesupport/ test/core_ext/time_with_zone_test.rb:340]: Expected: 86399.999999998 Actual: 86399.99999999799 😩

  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
  24. Performance

  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
  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
  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
  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
  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
  30. What You're Missing • Straight-line performance? • High concurrency? •

    Startup time? • Warmup time? • Memory size?
  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
  32. Memory Footprint 0 750 1500 2250 3000 Memory 900MB 2,400MB

    80MB CRuby 3.1 Tru ffl eRuby JRuby
  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
  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
  35. Warmup Over Time 0ms 5000ms 10000ms 15000ms 20000ms Iteration (2000

    requests each) 1 2 3 4 5 6 7 8 9 10 JRuby
  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
  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
  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
  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)
  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
  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...
  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
  43. 0MB 1500MB 3000MB 4500MB 6000MB 1,400MB 6,000MB 1,520MB Ruby 3.1

    Tru ffl eRuby JRuby
  44. 0MB 400MB 800MB 1200MB 1600MB 950MB 1,400MB 1,520MB Ruby 3.1

    JRuby JRuby with max heap
  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
  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
  47. Select Performance select <typed column> 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
  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
  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
  50. Wrapping Up

  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
  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!
  53. Thank You! • Charles Oliver Nutter • [email protected] • @headius(@mastodon.social)

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