Slide 1

Slide 1 text

JRuby: The Road to Ruby and Rails 6 Charles Oliver Nutter (@headius) Thomas Enebo (@tom_enebo)

Slide 2

Slide 2 text

• JRuby co-leads • Red Hat Inc. Charles Thomas Ruby Java Beer

Slide 3

Slide 3 text

#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

Slide 4

Slide 4 text

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!

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Recent Work

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Startup Time

Slide 12

Slide 12 text

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!

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

total execution time (lower is better) 0 0.05 0.1 0.15 0.2 -v CRuby JRuby (JDK8)

Slide 15

Slide 15 text

total execution time (lower is better) 0 0.45 0.9 1.35 1.8 -e 1 CRuby JRuby (JDK8) JRuby (10th iter)

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

Running in Same JVM total execution time (lower is better) 0 1.25 2.5 3.75 5 gem list (~350 gems)

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

Java 11 total execution time (lower is better) 0 1.75 3.5 5.25 7 gem list (~350 gems) JRuby --dev --dev + CDS

Slide 20

Slide 20 text

OpenJ9 JDK 8 total execution time (lower is better) 0 2 4 6 8 gem list (~350 gems) JRuby --dev --dev + quick

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

total execution time (lower is better) 0 0.75 1.5 2.25 3 -v -e 1 JRuby --dev TruffleRuby SVM TruffleRuby JVM

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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!

Slide 26

Slide 26 text

JRuby on Rails

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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!

Slide 31

Slide 31 text

Title 0 300 600 900 1200 Scaffolded blog post view CRuby JRuby TruffleRuby

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

Resident Memory • CRuby • 4 processes: ~400MB • 10 processes: ~1GB • JRuby • Default settings: ~1.3GB • With 250MB heap cap: ~580MB (no significant perf hit)

Slide 34

Slide 34 text

rubygems.org performance

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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)

Slide 37

Slide 37 text

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???

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

From CRuby To JRuby

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

JRuby-lint See how ready your Ruby code is to run on JRuby % gem install jruby-lint % cd my-app % jruby-lint STEP 1

Slide 43

Slide 43 text

Questionable Gems

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Questionable Assignments?

Slide 46

Slide 46 text

Other Issues…

Slide 47

Slide 47 text

Native C Extensions! % bundle install STEP 2

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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…

Slide 50

Slide 50 text

Strategy: FFI - Foreign Function Interface • Bind to DLL/shared library • Call functions & interact with structs/pointers • Pure-Ruby syntax! • Portable across all Ruby implementations

Slide 51

Slide 51 text

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!

Slide 52

Slide 52 text

Strategy: Script Java Library • JRuby allows call Java classes with Ruby syntax • Zillions of existing Java libraries • Usually minimal glue code

Slide 53

Slide 53 text

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!

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

Strategy: Native Java Extension • Write the extension in Java • Using our extension APIs • Generally fastest

Slide 56

Slide 56 text

Porting oj • Popular JSON gem • 19810 lines of C • 7 parsers/dumpers (object, strict, compat, null, custom, rails, wab) https://github.com/ohler55/oj

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Rails Status

Slide 60

Slide 60 text

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!

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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!

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Summary

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

Thank You! • Charles Oliver Nutter • [email protected] • @headius • Tom Enebo • [email protected] • @tom_enebo • http://jruby.org

Slide 69

Slide 69 text

Tracking Progress of 2.6 Support https://github.com/jruby/jruby/issues/5576

Slide 70

Slide 70 text

Tracking progress of 2.6 Support