$30 off During Our Annual Pro Sale. View Details »

Scaling Ruby with JRuby

headius
December 09, 2022

Scaling Ruby with JRuby

A talk on JRuby and how to scale Ruby and Rails applications using it. Delivered at RubyConf Thailand in Bangkok, December 9 2022.

headius

December 09, 2022
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Scaling Ruby with JRuby
    Charles Oliver Nutter


    @headius(@mastodon.social)

    View Slide

  2. Me
    • Programming my whole life


    • Professionally since 1996


    • JRuby since 2006


    [email protected]


    • @headius(@mastodon.social)

    View Slide

  3. View Slide

  4. RubyConf Thailand 2019

    View Slide

  5. Drag Race Thailand!

    View Slide

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

    View Slide

  7. View Slide

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

    View Slide

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

    View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

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

    View Slide

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

    View Slide

  16. JVM Tools and GC

    View Slide

  17. View Slide

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

    View Slide

  19. View Slide

  20. Getting Started

    View Slide

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

    View Slide

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

    View Slide

  23. JRuby on Rails

    View Slide

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

    View Slide

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

    View Slide

  26. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  30. Performance and Scaling

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. What You're Missing
    • Straight-line performance?


    • High concurrency?


    • Startup time?


    • Warmup time?


    • Memory size?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. Wrapping Up

    View Slide

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

    View Slide

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

    View Slide

  57. Thank You!
    • Charles Oliver Nutter


    [email protected]


    • @headius(@mastodon.social)


    • https://github.com/jruby/jruby


    • https://www.jruby.org

    View Slide