Slide 1

Slide 1 text

JRuby: From Zero To Scale 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

No content

Slide 5

Slide 5 text

Ruby Implementation First! • As compatible as we can be • All your Ruby code should just work* • * - except a few things we cannot support like fork() • No native c extensions (but we have java extensions) • Please report any problems

Slide 6

Slide 6 text

JVM Language Gets all the benefits the Java platform has to offer...

Slide 7

Slide 7 text

JVM Tools and GC

Slide 8

Slide 8 text

Parallel and Concurrent

Slide 9

Slide 9 text

Access to JVM Libraries Everything which has ever been made also has a JVM library for it

Slide 10

Slide 10 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 11

Slide 11 text

Skip 2.6 Support? • JRuby 9.3 -> Ruby 2.7? • How important is 2.6? • We skipped 2.4 to no ill effects • Less maintenance for us • 2.6 Checklist: https://github.com/jruby/jruby/issues/5576

Slide 12

Slide 12 text

Getting Started

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

JRuby Install • Install a JDK • Java 8 recommended, there's many distributions out there • Java 9+ work well but may print warnings • Install JRuby • Recommended: system package, Ruby installer, Docker image • Download tarball/zip or Windows installer

Slide 15

Slide 15 text

That's it!

Slide 16

Slide 16 text

Test it out! [] ~ $ rvm use jruby Using /Users/headius/.rvm/gems/jruby-9.2.6.0 [] ~ $ irb jruby-9.2.6.0 :001 > runtime = java.lang.Runtime.runtime => # jruby-9.2.6.0 :002 > runtime.available_processors => 8 jruby-9.2.6.0 :003 > runtime.free_memory => 91420584 jruby-9.2.6.0 :004 >

Slide 17

Slide 17 text

Startup Time

Slide 18

Slide 18 text

Why Is This Hard? • CRuby: Mostly native code at startup • Parser, iseq compiler, iseq interpreter, core classes, extensions • JRuby: Mostly interpreted at startup • Everything in JRuby starts "cold" • JVM eventually optimizes...but it's too late for startup time

Slide 19

Slide 19 text

total execution time (lower is better) 0s 1.25s 2.5s 3.75s 5s gem --version gem list (~350 gems) 4.6s 3.6s 0.7s 0.4s CRuby JRuby (JDK8)

Slide 20

Slide 20 text

Running in Same JVM total execution time (lower is better) 0s 1.25s 2.5s 3.75s 5s gem list (~350 gems) 1.3s 1.6s 1.6s 1.6s 2.2s 1.7s 1.7s 2.0s 2.2s 3.5s 4.6s

Slide 21

Slide 21 text

JRuby Flag: --dev • --dev flag • export JRUBY_OPTS="--dev" • Disables JRuby's JIT • Reduces JVM JIT • 30-40% reduction • Don't forget when benchmarking! total execution time (lower is better) 0s 1.15s 2.3s 3.45s 4.6s gem list (~350 gems) 3.0s 4.6s JRuby JRuby --dev

Slide 22

Slide 22 text

OpenJDK Class Data Sharing • Class Data Sharing • Shared JVM class data • Reduced memory • Improved startup time • Install jruby-startup gem • Run "generate-appcds" • JRuby will use automatically total execution time (lower is better) 0s 1.75s 3.5s 5.25s 7s gem list (~350 gems) 3.2s 3.7s 6.3s JRuby --dev --dev + CDS

Slide 23

Slide 23 text

OpenJ9 "quickstart" • "quickstart" and "shareclasses" • Shares class data across runs • Shares JIT results across runs • -Xquickstart
 -Xshareclasses:name=whatevs • JAVA_OPTS="..." total execution time (lower is better) 0s 2s 4s 6s 8s gem list (~350 gems) 3.1s 6.5s 7.1s JRuby --dev --dev + quick

Slide 24

Slide 24 text

Best Times total execution time (lower is better) 0s 1s 2s 3s 4s gem list (~350 gems) 3.1s 3.2s 3.0s 0.8s CRuby JRuby --dev JDK8 JRuby --dev JDK11 CDS JRuby --dev J9 quick

Slide 25

Slide 25 text

Future: Ahead-of-time Compile • New JVM tools for precompiling code to native • GraalVM's Substrate VM • Reduces base VM startup to CRuby speeds • Does not help Ruby code loaded at runtime • Soon: precompiled JRuby *and* Ruby code • App startup as fast as CRuby?

Slide 26

Slide 26 text

total execution time (lower is better) 0s 0.45s 0.9s 1.35s 1.8s -e 1 0.5s 1.7s JRuby --dev TruffleRuby native total execution time (lower is better) 0s 4s 8s 12s 16s gem list (~350 gems) 14.9s 4.6s JRuby --dev TruffleRuby native

Slide 27

Slide 27 text

Gems, Paths, etc • Don't share gem path with other Rubies • C extension gems have JRuby versions (e.g. Nokogiri) • Ruby installers will handle this for you • Be mindful if system packages do not • Watch out for .ruby-version silently switching

Slide 28

Slide 28 text

Getting Help • JRuby on GitHub: https://github.com/jruby/jruby • Chat with JRuby devs, users • #jruby on Freenode IRC • jruby/jruby on Gitter • Experimental: jruby on Matrix • Mailing list: https://lists.ruby-lang.org

Slide 29

Slide 29 text

JRuby on Rails

Slide 30

Slide 30 text

Why JRuby on Rails? • JRuby has run Rails since 2006! • "Are there JRuby users running Rails applications?" • Oh yes! And at large scale! • Benefit from the JVM, libraries, languages • The best way to scale large Rails apps today

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Rails Support Update

Slide 33

Slide 33 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 34

Slide 34 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 35

Slide 35 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 36

Slide 36 text

Rails 6.0.0.rc1 actioncable: 203 runs, 921 assertions, 0 failures, 10 errors actionmailbox: 79 runs, 205 assertions, 0 failures, 2 errors actionmailer: 220 runs, 509 assertions, 0 failures, 0 errors actionpack: 3255 runs, 16027 assertions, 1 failures, 0 errors actiontext: 53 runs, 94 assertions, 5 failures, 0 errors actionview: 2068 runs, 4667 assertions, 2 failures, 3 errors activejob: 301 runs, 659 assertions, 0 failures, 0 errors activemodel: 844 runs, 2350 assertions, 0 failures, 0 errors activestorage:225 runs, 696 assertions, 0 failures, 0 errors activesupport: 4362 runs, 13909 assertions, 15 failures, 1 errors 99.902% passing!

Slide 37

Slide 37 text

ARJDBC (master - 60.x) • 60.0.rc1 is out! • Same excludes as 52.x • New excludes as we work towards 6.0.0 final • 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 Daniel Ritz

Slide 38

Slide 38 text

Migrating an App

Slide 39

Slide 39 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 40

Slide 40 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 41

Slide 41 text

Questionable Gems

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Questionable Assignments?

Slide 44

Slide 44 text

Other Issues…

Slide 45

Slide 45 text

Native C Extensions! % bundle install STEP 2

Slide 46

Slide 46 text

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

Slide 47

Slide 47 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 48

Slide 48 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 49

Slide 49 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 50

Slide 50 text

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

Slide 51

Slide 51 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 52

Slide 52 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 53

Slide 53 text

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

Slide 54

Slide 54 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 55

Slide 55 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 56

Slide 56 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 57

Slide 57 text

Running JRuby on Rails • Puma recommended as server • Deployment tools work the same on JRuby • Another option: single-file deployment with Warbler • JVM will try to use all your memory • Tweak JVM heap max with JAVA_OPTS=-Xmx500M • JRUBY_OPTS, JAVA_OPTS so child processes pick up flags

Slide 58

Slide 58 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 • Single process with solid GC uses resources better

Slide 59

Slide 59 text

One User's Story • Large Rails application using 40 c1.xlarge on EC2 • 40 Unicorn workers per server • 100k-150k requests per minute, 50-75ms response times • Migrated app to JRuby, made more use of threading • Down to 10 c1.xlarge, 75% cost reduction • Consistently over 150k requests per minute, 30ms response times

Slide 60

Slide 60 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 61

Slide 61 text

Baseline Rails App • Simple scaffolded "blog" application • EC2 t2.xlarge, Ubuntu 18.04 • CRuby 2.6.2, JRuby 9.2.7, TruffleRuby RC16 • what the hell drivers different servers, numbers change • for MRI too • Workers versus threads briefly

Slide 62

Slide 62 text

requests per second (higher is better) 0rps 300rps 600rps 900rps 1200rps Scaffolded blog post view 1,160rps 1,014rps CRuby JRuby

Slide 63

Slide 63 text

Caveats • Benchmark driver changes results • Keep-alive versus new connections • JVM takes time to fully warm up • Working on ways to mitigate this • Many users drive the system a bit after deploy • What about a larger app?

Slide 64

Slide 64 text

rubygems.org performance

Slide 65

Slide 65 text

rubygems.org benchmark • Laptop (i7 with 16Gb of memory) (localhost) • Production mode force_https = false • Puma (JRuby does not support unicorn) • All on same machine: • rails, postgresql, elasticsearch, toxiproxy, redis, memcached • client software: Apache Bench (ab)

Slide 66

Slide 66 text

rubygems.org benchmark • http://localhost:9292/profiles/enebo • Gem push is not working (toxiproxy?) • Benchmark mystery! • wrk never would show improvements with multiple threads • ab keep alive problems (due to bug #1565 in Puma)

Slide 67

Slide 67 text

Requests/second 0 20 40 60 80 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 ab -c 1 -t 30s http://localhost:9292/profiles/enebo The Warmup Tail

Slide 68

Slide 68 text

Peak Requests/second 0 100 200 300 400 Concurrent threads 1 2 4 6 8 10 12 14 16 18 20 JRuby puma single 2.6.2 puma clustered 2.6.2 puma single ab -c {n} -t 30 http://localhost:9292/profiles/enebo 3 16

Slide 69

Slide 69 text

Memory Usage Memory (RES) 0 1000 2000 3000 4000 # of test runs 1 Run (35 mins) 6 Runs 11 Runs 16 Runs 21 Runs 1,536 1,536 1,536 1,434 1,228 3,620 3,540 3,460 3,340 3,060 CRuby (20 workers) JRuby (20 threads, 384m heap) +8.4% +14.4% +4.5% +6.7% +0% 2.5x smaller +0% +3.3% +3.2%

Slide 70

Slide 70 text

Scaling Takeaways + Uses less memory (threads vs processes) • Whole duplicated stack + app uses more memory + More memory stable over time • CoW? Fragmenting in GC? Memory leak? + More CPU efficient • More error tolerant on same hardware – More warmup time

Slide 71

Slide 71 text

Wrapping Up

Slide 72

Slide 72 text

JRuby Futures • Ruby 2.6 or 2.7? • Stop by chats and let us know • Native-compiling JRuby for startup time • Upcoming JVM features: true fibers, built-in FFI, new JITs and GCs

Slide 73

Slide 73 text

JRuby on Rails Futures • Rails 6 is working today • Continue getting more tests green, JRuby in CI • We're here when you need us • As your app grows, JRuby can help you scale • Reduce resources, save money • Start with JRuby or keep it in mind as you go

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

Help Wanted! • New features, Rails tests are great opportunities! • Learn more about how Ruby and JRuby work • Help us keep up with Ruby development • We are always standing by to help you!

Slide 76

Slide 76 text

Try JRuby! Let us know how it goes!

Slide 77

Slide 77 text

Thank You! • Charles Oliver Nutter • headius@headius.com • @headius • Tom Enebo • tom.enebo@gmail.com • @tom_enebo • http://jruby.org • Ruby 2.6 checklist:
 https://github.com/jruby/jruby/issues/5576