JRuby: The Road to Ruby 2.6 and Rails 6

F1d37642fdaa1662ff46e4c65731e9ab?s=47 headius
April 20, 2019

JRuby: The Road to Ruby 2.6 and Rails 6

Over the past six months, the JRuby team has been working on filling in edges: getting refinements working well, porting C extensions, and getting well-known applications running. At the same time, we always try to push forward on performance and general compatibility. In this talk, we'll cover recent work to support Rails 6 and Ruby 2.6. We'll show off a major Rails application running on JRuby. And we'll show you how you can help us keep JRuby moving forward.

F1d37642fdaa1662ff46e4c65731e9ab?s=128

headius

April 20, 2019
Tweet

Transcript

  1. JRuby: The Road to Ruby and Rails 6 Charles Oliver

    Nutter (@headius) Thomas Enebo (@tom_enebo)
  2. • JRuby co-leads • Red Hat Inc. Charles Thomas Ruby

    Java Beer
  3. #FreeOlaBini • Ola Bini, former JRuby contributor, is being held

    without cause by Ecuadorian authorities in connection with Julian Assange • His detention is illegal and should worry all of us • Please tell your friends about Ola and support efforts to help force the Ecuadorian authorities to release him
  4. What is JRuby • It's just Ruby! • Ruby 2.5

    compatible, if something's broken tell us • Supports pure-Ruby gems, many extensions • We want to be a Ruby first! • It's a JVM language • Full access to the power of the JVM platform!
  5. Roadmap • 9.2.7.0 last week, 9.2.8.0 in May • 9.1.x

    is EOL • How to handle 2.6? 9.1.17.0 ... 9.2.0.0 2.5.3+ 2.3.x 2.6? 9.2.7.0 master jruby-9_1 9.1.1.18.0 EOL ruby-2.6
  6. Ruby 2.6? • Should we wait for 2.7? • How

    important is 2.6.x • We went from 2.3.x -> 2.5.x • Less maintenance for us
  7. Recent Work

  8. Refinements! • Partially implemented in 9.2.0.0 • Refinements in modules,

    prepends, with super: broken • Different Method lookup logic in JRuby • Nearly 100% in 9.2.7.0! • Rework of method lookup and super calls (fixed super bugs too!) • 93 tests, 470 assertions, 4 failures, 2 errors, 0 skips
  9. Load and Require • JRuby's load/require logic was from Ruby

    1.8 times • Path expansion differences • No $LOADED_FEATURES cache • Subtle bugs in autoload (breaking Zeitwerk) • Rework is in progress hopefully for 9.2.8
  10. Enumerators and Fibers • More and more use in Ruby

    apps • See async, falcon for an extreme case • No fibers on JVM, have to use native threads • JRuby 9.2.8 will merge logic, fix Enumerator#next bugs • OpenJDK's Project Loom: native fibers for JVM! • We'll start supporting this after RailsConf
  11. Startup Time

  12. Developer Experience • Startup time is critical to the Ruby

    experience • Fast iteration on command line tools • Automatic reload or quick reboot of apps • Primary concern when switching to away from CRuby • CRuby is super fast to start up!
  13. Why Is This Hard? • CRuby: Mostly native code at

    startup • Parser, iseq compiler, iseq interpreter, core classes, extensions • JRuby: Mostly interpreted bytecode at startup • Everything in JRuby starts "cold" • Repeated JRuby startup in same JVM gets much faster
  14. total execution time (lower is better) 0 0.05 0.1 0.15

    0.2 -v CRuby JRuby (JDK8)
  15. total execution time (lower is better) 0 0.45 0.9 1.35

    1.8 -e 1 CRuby JRuby (JDK8) JRuby (10th iter)
  16. total execution time (lower is better) 0 1.25 2.5 3.75

    5 gem --version gem list (~350 gems) CRuby JRuby (JDK8) JRuby (10th iter)
  17. Running in Same JVM total execution time (lower is better)

    0 1.25 2.5 3.75 5 gem list (~350 gems)
  18. Disabling JRuby JIT total execution time (lower is better) 0

    1.15 2.3 3.45 4.6 gem list (~350 gems) JRuby JRuby --dev
  19. Java 11 total execution time (lower is better) 0 1.75

    3.5 5.25 7 gem list (~350 gems) JRuby --dev --dev + CDS
  20. OpenJ9 JDK 8 total execution time (lower is better) 0

    2 4 6 8 gem list (~350 gems) JRuby --dev --dev + quick
  21. Best Times Compared total execution time (lower is better) 0

    1 2 3 4 gem list (~350 gems) CRuby JRuby --dev JDK8 JRuby --dev JDK11 CDS JRuby --dev J9 quick
  22. Ahead-of-time Compile? • Recent interest in precompiling JVM apps to

    native • TruffleRuby uses GraalVM's Substrate VM • "The SVM version of TruffleRuby has better startup performance and lower memory footprint than JRuby, Rubinius and TruffleRuby on the JVM, and similar or better startup performance than MRI." • Precompiled TruffleRuby core + Java dependencies • Ruby and C extensions must be optimized from scratch
  23. total execution time (lower is better) 0 0.75 1.5 2.25

    3 -v -e 1 JRuby --dev TruffleRuby SVM TruffleRuby JVM
  24. total execution time (lower is better) 0 5.5 11 16.5

    22 gem --version gem list (~350 gems) JRuby --dev TruffleRuby SVM TruffleRuby JVM
  25. Startup Future • Continue reducing complexity at boot • Future

    improvements to JVM JIT caching, class sharing, etc • Precompiling Ruby code to JVM • JRuby on SVM: fully precompiled JRuby+app • Fully-native precompiled Ruby apps! • Lots of work to do!
  26. JRuby on Rails

  27. Why JRuby on Rails? • "Are there JRuby users running

    Rails applications?" • Oh yes! And at large scale! • JRuby has run Rails since 2006 • We believe it is the best way to scale large Rails apps today • Excellent GC • Fully parallel threading
  28. Scaling Rails • Classic problem on MRI • No concurrent

    threads, so we need processes • Processes duplicate runtime state and waste resources • JRuby is the answer! • Multi-threaded single process runs your entire site
  29. Rails Performance • Simple scaffolded "blog" application • EC2 t2.xlarge,

    Ubuntu 18.04 • CRuby 2.6.2, JRuby 9.2.6, TruffleRuby RC15 • wrk driver with keepalive disabled due to Puma bug
  30. Optimizing for Rails • Most important metric for Ruby performance

    • Very difficult framework to optimize • See also k0kubun's JIT talk • JRuby typically ran a bit slower than CRuby • Until recently!
  31. Title 0 300 600 900 1200 Scaffolded blog post view

    CRuby JRuby TruffleRuby
  32. Caveats • Keep-alive improves numbers • This bench is pretty

    small, so it shows up • JRuby takes about 60s of driving to fully warm up • TruffleRuby numbers are extremely erratic and never settle down • Idle server was using 2GB, 150% CPU
  33. Resident Memory • CRuby • 4 processes: ~400MB • 10

    processes: ~1GB • JRuby • Default settings: ~1.3GB • With 250MB heap cap: ~580MB (no significant perf hit)
  34. rubygems.org performance

  35. rubygems.org benchmark • Production mode • force_https = false (localhost

    testing) • Use puma (JRuby does not support unicorn) • Same machine: • rails, postgresql, elasticsearch, toxiproxy, redis, memcached • client software: wrk, ab
  36. rubygems.org benchmark • http://localhost:9292/profiles/enebo • Gem push is not working?

    • Benchmark mystery! • wrk never would show improvements with multiple threads • ab keep alive problems (due to bug #1565 in Puma)
  37. Requests/second 0 75 150 225 300 Iteration (30s) 1 2

    3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 JRuby puma single 2.6.2 puma clustered 2.6.2 puma single wrk -t 1 -d 30s http://localhost:9292/profiles/enebo Supposedly Keep-Alive???
  38. Requests/second 0 100 200 300 400 Iteration (30s) 1 2

    3 4 5 6 7 8 9 10 11 12 13 JRuby puma single 2.6.2 puma clustered 2.6.2 puma single wrk -H “Connection: Close” -t 1 -d 30s http://localhost:9292/profiles/enebo Disable Keep-Alive
  39. Requests/second 0 100 200 300 400 Concurrent threads 1 2

    4 6 8 10 12 14 JRuby puma single 2.6.2 puma clustered 2.6.2 puma single ab -c {n} -t 30 http://localhost:9292/profiles/enebo
  40. From CRuby To JRuby

  41. Use-Case: Discourse • Big well-known Rails application • “A platform

    for community discussion” • >500 gems • 250,000 lines of Ruby • JRuby is not currently supported
  42. JRuby-lint See how ready your Ruby code is to run

    on JRuby % gem install jruby-lint % cd my-app % jruby-lint STEP 1
  43. Questionable Gems

  44. https://github.com/jruby/jruby/wiki/C-Extension-Alternatives

  45. Questionable Assignments?

  46. Other Issues…

  47. Native C Extensions! % bundle install STEP 2

  48. Strategy: Ignore? gem ‘byebug’, platform: :ruby

  49. Strategy: Replace with Pure Ruby def ruby_xor!(x, y) i =

    0 max = (x.length < y.length ? x.length : y.length) while i < max x.setbyte(i, x.getbyte(i) ^ y.getbyte(i)) i += 1 end end ruby 3.702M (± 5.8%) i/s - 18.431M in 4.995973s xorcist 11.015M (± 8.1%) i/s - 54.710M in 5.007484s Unless the performance matters…
  50. Strategy: FFI - Foreign Function Interface • Bind to DLL/shared

    library • Call functions & interact with structs/pointers • Pure-Ruby syntax! • Portable across all Ruby implementations
  51. https://github.com/chuckremes/ffi-rzmq-core/blob/master/lib/ffi-rzmq-core/libzmq.rb module LibZMQ extend FFI::Library # ... ffi_lib(ZMQ_LIB_PATHS + %w{libzmq})

    attach_function :zmq_strerror, [:int], :pointer, :blocking => true # ... end Load FFI methods Load specific DLL Attach function Nam e of function Input param eters Return value puts "Error: #{LibZMQ.zmq_strerror(errno)}" Call it just like Ruby!
  52. Strategy: Script Java Library • JRuby allows call Java classes

    with Ruby syntax • Zillions of existing Java libraries • Usually minimal glue code
  53. mini_racer port • mini_racer is minimal bindings to V8 •

    JRuby will script Java library J2V8 • Native bindings to V8 from Java • Someone wrote the C so we don’t have to!
  54. mini_racer porting snippets require 'mini_racer/jruby/j2v8_linux_x86_64-4.8.0.jar' java_import com.eclipsesource.v8.V8 @v8 ||= V8::createV8Runtime

    JSToRuby(@v8.execute_script(src, file, 0)) Load Java Library Make Java V8 class accessible Call method on that class Tough to tell we are calling Java here
  55. Strategy: Native Java Extension • Write the extension in Java

    • Using our extension APIs • Generally fastest
  56. Porting oj • Popular JSON gem • 19810 lines of

    C • 7 parsers/dumpers (object, strict, compat, null, custom, rails, wab) https://github.com/ohler55/oj
  57. JRuby APIs @JRubyModule(name = "Oj") public class RubyOj extends RubyModule

    { // ... @JRubyMethod(module = true, required = 1, rest = true) public static IRubyObject strict_load(ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) { OjLibrary oj = resolveOj(self); Options options = oj.default_options.dup(context); ParserSource source = processArgs(context, args, options); return new StrictParse(source, context, options).parse(oj, true, block); } } Define Oj module Define Oj.strict_load
  58. Porting Gems Conclusion • Pain when replacement does not already

    exist • Many strategies to move past the pain • Once someone does it we all get it • Yay for Open Source
  59. Rails Status

  60. Rails 5.2.3 actioncable: some results…hard pg dep for other fails

    actionmailer: 204 runs, 457 assertions, 0 failures, 0 errors actionpack: 3174 runs, 15884 assertions, 1 failures, 0 errors actionview: 1993 runs, 4398 assertions, 2 failures, 4 errors activejob: 180 runs, 415 assertions, 0 failures, 0 errors activemodel: 810 runs, 2265 assertions, 0 failures, 0 errors activestorage: 144 runs, 372 assertions, 0 failures, 0 errors activesupport: 4212 runs, 763229 assertions, 10 failures, 1 errors railties: uses fork() [issue #35900] 99.998% passing!
  61. Failure: TimeWithZoneTest#test_minus_with_time_precision [activesupport/ test/core_ext/time_with_zone_test.rb:340]: Expected: 86399.999999998 Actual: 86399.99999999799

  62. ActiveRecordJDBC 52.2 • Historic dates • PG-specific tests in AR

    test suite (PRs needed yet) • Miscellaneous… sqlite3: 19 excludes, 5327 runs, 14961 assertions postgresql: 53 excludes, 5915 runs, 16626 assertions mysql2: 33 excludes, 5491 runs, 15527 assertions
  63. Rails 6.0.0.beta3 actioncable: 203 runs, 921 assertions, 0 failures, 10

    errors actionmailbox: 79 runs, 205 assertions, 0 failures, 2 errors actionmailer: 219 runs, 503 assertions, 0 failures, 0 errors actionpack: 3245 runs, 15995 assertions, 1 failures, 0 errors actiontext: 49 runs, 88 assertions, 5 failures, 0 errors actionview: 2037 runs, 4484 assertions, 2 failures, 3 errors activejob: 292 runs, 648 assertions, 0 failures, 0 errors activemodel: 842 runs, 2330 assertions, 0 failures, 0 errors activestorage:222 runs, 683 assertions, 0 failures, 0 errors activesupport: 4338 runs, 13858 assertions, 12 failures, 1 errors 99.921% passing!
  64. ARJDBC (master - 6.x) • Same excludes as 52.x •

    New excludes as we work on new 6.0.0.beta • Rails 6 apps basically work fine! sqlite3: 20 excludes, 6415 runs, 16749 assertions postgresql: 89 excludes, 6962 runs, 18289 assertions mysql2: 66 excludes, 6535 runs, 17210 assertions
  65. Summary

  66. None
  67. None
  68. Thank You! • Charles Oliver Nutter • headius@headius.com • @headius

    • Tom Enebo • tom.enebo@gmail.com • @tom_enebo • http://jruby.org
  69. Tracking Progress of 2.6 Support https://github.com/jruby/jruby/issues/5576

  70. Tracking progress of 2.6 Support