Slide 1

Slide 1 text

Scaling Ruby with JRuby Charles Oliver Nutter @headius(@mastodon.social)

Slide 2

Slide 2 text

Me • Programming my whole life • Professionally since 1996 • JRuby since 2006 • headius@headius.com • @headius(@mastodon.social)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

RubyConf Thailand 2019

Slide 5

Slide 5 text

Drag Race Thailand!

Slide 6

Slide 6 text

Thank You Yarden! • I got to speak at RubyConf! • Tomorrow's JRuby keynote • "Ruby & JVM: A Love Story" • Really cool talk about her experiences using and building apps with JRuby! • @YardenLaif

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 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 9

Slide 9 text

Ruby Compatibility • JRuby 9.4 is out now! • Ruby 3.1 compatible, 98% of language specs passing • Nearly complete core + stdlib features from 2.7, 3.0 and 3.1 • JRuby 9.3 (Ruby 2.6 compat) • Maintenance through 2023 as needed • Compatibility before performance! • Now we can refocus on optimization again

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

Ruby 3.2 Coming Soon! • We will set up another checklist issue • You can help us add missing features! • Hopefully release 3.2 compatibility in the next 6 months

Slide 15

Slide 15 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 16

Slide 16 text

JVM Tools and GC

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

Fun Stuff • Glimmer GUI DSL • Multiple backends (SWT, GTK, ...) • JRuby + SWT is the most mature • JRuby makes cross-platform GUI much easier! • Works same everywhere • GUI libraries shipped with gem

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Getting Started

Slide 21

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

Slide 22 text

Try 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 23

Slide 23 text

JRuby on Rails

Slide 24

Slide 24 text

JRuby Runs Rails! • Since 2007! • "Are there JRuby users running Rails applications?" • Oh yes! And at large scale! • Bene fi t from the JVM, libraries, languages • Great way to scale large Rails apps today!

Slide 25

Slide 25 text

Getting Started • Generate a new app, Rails will use JRuby defaults • Couple gotchas with Rails 7, we can help • Use threads in Puma instead of workers • 2n+1 is a good rule of thumb • Make sure to increase DB connections as well • Existing app: try bundling, replace unsupported libs

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 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! • Couple gotchas right now but we're working through them

Slide 28

Slide 28 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 29

Slide 29 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 • sqlite: 7745 runs, 25040 assertions, 32 failures, 14 errors • postgresql needs more updates, coming soon

Slide 30

Slide 30 text

Performance and Scaling

Slide 31

Slide 31 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 32

Slide 32 text

Benchmarks • Benchmarks are very situational • What looks good in a microbench may not translate to production • Two example benchmarks • railsbench, small Rails blog on SQLite, no web server, tight loop • Rails blog on MySQL, end to end through Puma

Slide 33

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

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

Slide 35 text

time to run 2000 requests (lower is better) 0 550 1100 1650 2200 Time 1,460ms 1,550ms 1,704ms 2,152ms CRuby 3.1 CRuby 3.1 yjit Tru ffl eRuby JRuby

Slide 36

Slide 36 text

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

Slide 37

Slide 37 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 38

Slide 38 text

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

Slide 39

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

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

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

Slide 42 text

JRuby Architecture Ruby (.rb) JIT Java Instructions (java bytecode) Ruby Instructions (IR) parse interpret interpreter interpret C1 compile native code better native code java bytecode interpreter execute C2 compile Java Virtual Machine JRuby Internals deoptimize Performance improves the longer your app runs

Slide 43

Slide 43 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 44

Slide 44 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 45

Slide 45 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 46

Slide 46 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 • Full end-to-end: Puma web server, MySQL backend • External request driver (siege) • Max out CPU (high concurrency)

Slide 47

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

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

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

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

Slide 51 text

1300MB 1375MB 1450MB 1525MB 1600MB 1,400MB 1,520MB Ruby 3.1 JRuby

Slide 52

Slide 52 text

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

Slide 53

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

Slide 54 text

Wrapping Up

Slide 55

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

Slide 56 text

JRuby Future • JRuby 9.4 is out! • Try it out, report issues, submit a PR! • Ongoing Rails 7 updates • Big optimization work coming in the next year • New JVM features • Native fi bers! Built-in FFI!

Slide 57

Slide 57 text

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