Slide 1

Slide 1 text

Using JRuby: What, Why, When, and How Charles Oliver Nutter @headius

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

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/

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

JVM Tools and GC

Slide 11

Slide 11 text

Parallel and Concurrent

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Getting Started

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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 >

Slide 17

Slide 17 text

JRuby on Rails

Slide 18

Slide 18 text

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!

Slide 19

Slide 19 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 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

Slide 20

Slide 20 text

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!

Slide 21

Slide 21 text

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!

Slide 22

Slide 22 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 23

Slide 23 text

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

Slide 24

Slide 24 text

Performance

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

What You're Missing • Straight-line performance? • High concurrency? • Startup time? • Warmup time? • Memory size?

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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)

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Wrapping Up

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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!

Slide 53

Slide 53 text

Thank You! • Charles Oliver Nutter • [email protected] • @headius(@mastodon.social) • https://github.com/jruby/jruby • https://www.jruby.org