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

Scalable Apps with JRuby

headius
September 06, 2019

Scalable Apps with JRuby

headius

September 06, 2019
Tweet

More Decks by headius

Other Decks in Programming

Transcript

  1. Scalable Apps with JRuby
    Charles Oliver Nutter (@headius)

    View Slide

  2. สวัสดี!
    • Charles Oliver Nutter
    [email protected], @headius
    • JVM language advocate at Red Hat
    • 24 years of JVM, 13 years of JRuby
    • My first time in Thailand!

    View Slide

  3. View Slide

  4. View Slide

  5. What is JRuby
    • It's just Ruby!
    • Ruby 2.5 compatible, if something's broken tell us
    • Supports pure-Ruby gems, standard library, many extensions
    • We want to be a Ruby first!
    • It's a JVM language
    • Full access to the power of the JVM platform!

    View Slide

  6. View Slide

  7. View Slide

  8. JVM Benefits
    • Widely deployed and widely supported runtime
    • Excellent JIT, GC, concurrency, and platform support
    • Wide array of libraries
    • Rich tools for monitoring, profiling, debugging
    • WORA: JRuby feels the same on Windows as on Linux

    View Slide

  9. Parallel and Concurrent

    View Slide

  10. JVM Tools and GC

    View Slide

  11. Thousands of Libraries
    Released artifacts: 7,000,212
    Unique artifacts: 347,981
    Released gem files: 1,042,770
    Unique artifacts: 154,626
    Maven Central RubyGems.org

    View Slide

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

    View Slide

  13. JRuby is Ruby plus the best
    parts of the JVM

    View Slide

  14. Roadmap
    • 9.2.8.0 (Ruby 2.5 compatible) is current version
    • 9.1.x is EOL
    • Support 2.6 or 2.7?
    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

    View Slide

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

    View Slide

  16. You Can Help!
    • New features are great opportunities to contribute!
    • Learn more about how Ruby and JRuby work
    • Help us keep up with Ruby development
    • Use Ruby or Java, we'll accept both
    • We are always standing by on IRC, Gitter, Twitter to help you

    View Slide

  17. Getting Started

    View Slide

  18. View Slide

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

    View Slide

  20. Install JRuby

    View Slide

  21. That's it!

    View Slide

  22. JRuby IRB

    View Slide

  23. 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
    • System packages may not
    • Watch out for .ruby-version silently switching

    View Slide

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

    View Slide

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

  26. Low-Level Performance

    View Slide

  27. Microbenchmarks
    - Usually not useful to users
    • Not related to typical applications (e.g. Rails)
    • JRuby has won microbenchmarks for years
    + Fun to show off and improve
    + Easier to isolate specific measurements
    + Quick way to explore new runtimes and tech

    View Slide

  28. bench_mandelbrot
    • Generate a Mandelbrot fractal image
    • Useful? Hmmm
    • Good test of numeric algorithm performance
    • Heavily relies on JVM to optimize

    View Slide

  29. bench_mandelbrot.rb
    def mandelbrot(size)
    sum = 0
    byte_acc = 0
    bit_num = 0
    y = 0
    while y < size
    ci = (2.0*y/size)-1.0
    x = 0
    while x < size
    zrzr = zr = 0.0
    zizi = zi = 0.0
    cr = (2.0*x/size)-1.5
    escape = 0b1
    z = 0
    while z < 50

    View Slide

  30. bench_mandelbrot total execution time (lower is better)
    0s
    1s
    2s
    3s
    4s
    CRuby 2.5 CRuby 2.6 JIT JRuby JRuby Indy
    1.33s
    2.95s
    3.5s
    3.57s

    View Slide

  31. InvokeDynamic
    • JVM support for dynamic calls, variables, etc
    • Java 7 feature after input and testing from JRuby
    • Steadily improving performance, reducing overhead
    • Opt-in due to startup impact: -Xcompile.invokedynamic
    • May be default very soon!

    View Slide

  32. bench_mandelbrot total execution time (lower is better)
    0s
    1s
    2s
    3s
    4s
    CRuby 2.6 JIT JRuby JRuby Indy
    1.33s
    2.95s
    3.5s

    View Slide

  33. New JVMs and JITs
    • IBM's OpenJ9
    • Recently open-sourced
    • Many compelling features
    • Graal: JIT written in Java
    • Faster evolution
    • More advanced optimization

    View Slide

  34. bench_mandelbrot total execution time (lower is better)
    0s
    1s
    2s
    3s
    4s
    CRuby 2.6 JIT JRuby JRuby Indy
    1.33s
    2.95s
    3.5s

    View Slide

  35. bench_mandelbrot total execution time (lower is better)
    0s
    1s
    2s
    3s
    4s
    CRuby 2.6 JIT JRuby Indy JRuby Indy Graal
    0.139s
    1.33s
    3.5s

    View Slide

  36. Optimizing Objects and Arrays
    • Ruby instance vars are dynamic, but usually predictable
    • Ruby arrays are mutable, but usually small and immutable
    • We can make compact JVM objects
    • Instance vars, array elements as Java fields
    • Reduce memory use and allocation
    • Similar to CRuby storing references in object header

    View Slide

  37. Objects in Rails `select` Bench
    percent live alloc'ed class
    rank self accum bytes objs bytes objs name
    23 0.82% 73.58% 1744576 18168 5894464 61396 org.jruby.gen.RubyObject17
    32 0.44% 78.33% 937784 23432 2071464 51774 org.jruby.gen.RubyObject2
    42 0.30% 81.96% 633312 19775 1525824 47666 org.jruby.gen.RubyObject0
    43 0.30% 82.26% 632168 11280 2783968 49705 org.jruby.gen.RubyObject6
    46 0.27% 83.10% 587072 18330 2133984 66671 org.jruby.gen.RubyObject1
    58 0.22% 86.08% 465056 3630 1672864 13066 org.jruby.gen.RubyObject25
    60 0.21% 86.51% 439304 10970 1493024 37313 org.jruby.gen.RubyObject3
    61 0.20% 86.71% 434608 9044 2311744 48151 org.jruby.gen.RubyObject5
    68 0.16% 87.93% 349936 7280 1305136 27180 org.jruby.gen.RubyObject4
    79 0.11% 89.34% 233824 3646 838432 13093 org.jruby.gen.RubyObject8
    238 0.01% 96.11% 28088 314 30816 345 org.jruby.gen.RubyObject14

    View Slide

  38. 10M * One-variable Object
    0MB
    200MB
    400MB
    600MB
    800MB
    Not Optimized Optimized
    320
    480
    400
    Ruby Object Object[]
    33% memory reduction

    View Slide

  39. Arrays in Rails `select` Bench
    percent live alloc'ed class
    rank self accum bytes objs bytes objs name
    5 4.90% 33.79% 10481824 218361 38183968 795489 org.jruby.RubyArray
    11 3.11% 56.32% 6661072 138762 22817680 475358 org.jruby.specialized.RubyArrayOneObject
    17 1.46% 67.96% 3124112 55779 15838128 282815 org.jruby.specialized.RubyArrayTwoObject
    More than half of allocated arrays are 1- or 2-element!

    View Slide

  40. 10M * One-element Array
    0MB
    250MB
    500MB
    750MB
    1000MB
    Not Optimized Optimized
    400
    650
    570
    Ruby Object IRubyObject[]
    33% memory reduction

    View Slide

  41. JVM is Great, But...
    • Runtime optimizations give us excellent performance...eventually
    • Startup time, warmup time are impacted
    • We continue to reduce this impact

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  45. JRuby Flag: --dev
    • export JRUBY_OPTS="--dev"
    • Disables JRuby's JIT
    • Reduces JVM JIT
    • Don't use when benchmarking!
    • 30-40% reduction
    • More improvements coming
    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

    View Slide

  46. Ahead-of-time Compile?
    • Precompile JVM code to native
    • Improves base startup time
    • Used by TruffleRuby currently
    • Coming soon: precompiled JRuby and Ruby code
    • Instant application startup?

    View Slide

  47. total execution time (lower is better)
    0.4s
    4.3s
    8.2s
    12.1s
    16s
    -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

    View Slide

  48. Web Applications

    View Slide

  49. Small and Large
    • Sinatra and Roda
    • https://github.com/CaDs/ruby_benchmarks
    • Comparing JRuby, CRuby, and TruffleRuby
    • Rails
    • https://github.com/rubygems/rubygems.org
    • JRuby versus CRuby

    View Slide

  50. Sinatra and Roda
    • Well-supported on JRuby
    • Many production users at very large scales
    • Very simple example with no database
    • Benchmarking request routing mostly
    • Good indicator of small service performance

    View Slide

  51. Sinatra and Roda requests/second (higher is better)
    0
    12500
    25000
    37500
    50000
    Sinatra Roda Roda (1 thread) Roda (WEBrick)
    6,414
    4,307
    5,214
    4,257
    21,209
    15,419
    44,703
    42,468
    14,489
    12,284
    CRuby JRuby TruffleRuby

    View Slide

  52. Sinatra performance over time, requests/second
    0rps
    12500rps
    25000rps
    37500rps
    50000rps
    5s 10s 15s 20s 25s
    CRuby JRuby

    View Slide

  53. Roda performance over time, requests/second
    0rps
    12500rps
    25000rps
    37500rps
    50000rps
    5s 10s 15s 20s 25s
    CRuby JRuby

    View Slide

  54. JRuby on Rails
    • Benefit from the JVM, libraries, languages
    • Single JRuby process runs your whole site
    • "Are there JRuby users running Rails applications?"
    • Oh yes! And at scale!
    • The best way to scale large Rails apps today

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  58. Rails is the Thing!
    • If you can't run Rails fast, you've got more work to do
    • By far the biggest use case for Ruby
    • Frustratingly difficult to optimize
    • JRuby has run microbenchmarks faster for years...
    • ...but Rails performance was always about the same
    • Times are changing!

    View Slide

  59. ActiveRecord Performance
    • Rails apps live and die by ActiveRecord
    • Largest CPU consumer by far
    • Heavy object churn, GC overhead
    • Create, read, and update measurements
    • CRuby 2.5.1 vs JRuby 9.2 on JDK11

    View Slide

  60. ActiveRecord Selects
    time for 1000 selects, lower is better
    0
    0.075
    0.15
    0.225
    0.3
    CRuby 2.5 JRuby C2 JRuby Graal
    binary boolean date datetime decimal float integer
    string text time timestamp *

    View Slide

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

    View Slide

  62. Simple Rails Performance
    • Rails 5.1.6, Postgresql 10, scaffolded view
    • JRuby: 10 threads, CRuby: 10 processes
    • 4k requests to warm up, then measure every 10k
    • EC2 c4.xlarge: 4 vCPUs, 7.5GB
    • Bench, database, and app on same instance

    View Slide

  63. Requests per second, full stack scaffolded read on Postgresql
    0
    325
    650
    975
    1300
    CRuby JRuby
    1,253.86
    910.02

    View Slide

  64. Requests per second
    0
    325
    650
    975
    1300
    Requests over time
    10k 20k 30k 40k 50k 60k 70k 80k 90k 100k
    CRuby 2.5 CRuby 2.6 JIT JRuby 9.2

    View Slide

  65. JRuby on Rails Memory
    • Single instance is roughly 10x larger
    • Threading to the rescue!
    • Ten CRuby processes = 500MB
    • Ten JRuby threads = 400-500MB
    • Copy-on-write helps CRuby a bit
    • Eventually most processes grow
    Instances vs Memory
    0MB
    1250MB
    2500MB
    3750MB
    5000MB
    1 5 10 100
    CRuby JRuby

    View Slide

  66. rubygems.org Performance
    • Laptop (i7 with 16Gb of memory) (localhost)
    • No SSL
    • Puma instead of Unicorn
    • Single machine:
    • rails, postgresql, elasticsearch, toxiproxy, redis, memcached
    • Benchmark driver: Apache Bench (ab)

    View Slide

  67. Performance versus concurrent users, requests/second
    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
    3 16

    View Slide

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

    View Slide

  69. Scaling Takeaways
    + Uses less memory (threads vs processes)
    + More memory stable over time
    + More CPU efficient
    – More warmup time

    View Slide

  70. JRuby is the fastest way
    to run Rails applications.

    View Slide

  71. Migrating to JRuby

    View Slide

  72. Try JRuby!
    • New apps will be easiest, of course
    • Existing apps require a few extra steps
    • Tweak database, server configs
    • Bundle install and deal with C extensions
    • Run your tests

    View Slide

  73. Use-Case: Discourse
    • “A platform for community discussion”
    • Very large, well-known Rails application
    • >500 gems
    • 250,000 lines of Ruby
    • JRuby is not currently supported
    • But it is almost working!

    View Slide

  74. Step 1: jruby-lint

    View Slide

  75. jruby-lint gem
    See how ready your Ruby code is to run on JRuby
    $ gem install jruby-lint

    $ cd my-app

    $ jrlint

    View Slide

  76. [] ~/projects/discourse $ jrlint
    JRuby-Lint version 0.9.0
    ./Gemfile:: [gems, info] For more on gem compatibility see http://wiki.jruby.org/C-Extension-Alternatives
    ./Gemfile:80: [gems, warning] Found gem 'oj' which is reported to have some issues:
    Try `gson`, `json` or `json_pure` instead.|
    gem 'oj'
    ./Gemfile:81: [gems, warning] Found gem 'pg' which is reported to have some issues:
    Use activerecord-jdbcpostgresql-adapter instead or pg_jruby (drop-in replacement).|
    gem 'pg'
    ./Gemfile:187: [gems, warning] Found gem 'mysql2' which is reported to have some issues:
    Use activerecord-jdbcmysql-adapter.|
    gem 'mysql2'
    ./Gemfile:188: [gems, warning] Found gem 'redcarpet' which is reported to have some issues:
    Same as with **RDiscount** use alternatives such as kramdown, Maruku or markdown_j|
    gem 'redcarpet'
    ./Gemfile:189: [gems, warning] Found gem 'sqlite3' which is reported to have some issues:
    Use activerecord-jdbcsqlite3-adapter.|
    gem 'sqlite3', '~> 1.3.13'
    ./app/mailers/user_notifications.rb:152: [nonatomic, warning] Non-local operator assignment (@popular_topics) is not guaranteed to be atomic.
    @popular_topics = topics_for_digest[0, SiteSetting.digest_topics]
    ./app/mailers/user_notifications.rb:630: [nonatomic, warning] Non-local operator assignment (@site_name) is not guaranteed to be atomic.
    @site_name = SiteSetting.email_prefix.presence || SiteSetting.title # used by I18n
    ./app/models/report.rb:21: [nonatomic, warning] Non-local operator assignment (@start_date) is not guaranteed to be atomic.
    @start_date ||= Report.default_days.days.ago.utc.beginning_of_day
    ./app/models/report.rb:22: [nonatomic, warning] Non-local operator assignment (@end_date) is not guaranteed to be atomic.
    @end_date ||= Time.now.utc.end_of_day
    ./app/models/admin_dashboard_next_data.rb:17: [nonatomic, warning] Non-local operator assignment (@json) is not guaranteed to be atomic.
    @json ||= get_json
    ./app/models/topic_posters_summary.rb:31: [nonatomic, warning] Non-local operator assignment (@descriptions_by_id) is not guaranteed to be atomic.
    @descriptions_by_id ||= begin
    ./app/models/topic_posters_summary.rb:33: [nonatomic, warning] Non-local operator assignment (descriptions[id]) is not guaranteed to be atomic.
    descriptions[id] ||= []
    ./app/models/topic_posters_summary.rb:79: [nonatomic, warning] Non-local operator assignment (@avatar_lookup) is not guaranteed to be atomic.
    @avatar_lookup ||= options[:avatar_lookup] || AvatarLookup.new(user_ids)
    ./app/models/topic_posters_summary.rb:83: [nonatomic, warning] Non-local operator assignment (@primary_group_lookup) is not guaranteed to be atomic.
    @primary_group_lookup ||= options[:primary_group_lookup] || PrimaryGroupLookup.new(user_ids)
    ./app/models/directory_item.rb:8: [nonatomic, warning] Non-local operator assignment (@headings) is not guaranteed to be atomic.
    @headings ||= [:likes_received,
    ./app/models/directory_item.rb:18: [nonatomic, warning] Non-local operator assignment (@types) is not guaranteed to be atomic.
    @types ||= Enum.new(all: 1,
    ./app/models/group_history.rb:11: [nonatomic, warning] Non-local operator assignment (@actions) is not guaranteed to be atomic.
    @actions ||= Enum.new(
    ./app/models/locale_site_setting.rb:10: [nonatomic, warning] Non-local operator assignment (@values) is not guaranteed to be atomic.
    @values ||= supported_locales.map do |locale|
    ./app/models/locale_site_setting.rb:25: [nonatomic, warning] Non-local operator assignment (@language_names) is not guaranteed to be atomic.
    @language_names ||= begin
    ./app/models/locale_site_setting.rb:41: [nonatomic, warning] Non-local operator assignment (@supported_locales) is not guaranteed to be atomic.
    @supported_locales ||= begin
    ./app/models/category.rb:98: [nonatomic, warning] Non-local operator assignment (TOPIC_CREATION_PERMISSIONS) is not guaranteed to be atomic.
    TOPIC_CREATION_PERMISSIONS ||= [:full]
    ./app/models/category.rb:99: [nonatomic, warning] Non-local operator assignment (POST_CREATION_PERMISSIONS) is not guaranteed to be atomic.
    POST_CREATION_PERMISSIONS ||= [:create_post, :full]
    ./app/models/category.rb:109: [nonatomic, warning] Non-local operator assignment (@topic_id_cache) is not guaranteed to be atomic.
    @topic_id_cache = DistributedCache.new('category_topic_ids')
    ./app/models/category.rb:228: [nonatomic, warning] Non-local operator assignment (@@cache) is not guaranteed to be atomic.
    @@cache ||= LruRedux::ThreadSafeCache.new(1000)
    ./app/models/category.rb:520: [nonatomic, warning] Non-local operator assignment (@has_children) is not guaranteed to be atomic.
    @has_children ||= (id && Category.where(parent_category_id: id).exists?) ? :true : :false

    View Slide

  77. Unsupported Extensions
    ./Gemfile:: [gems, info] For more on gem compatibility see http://wiki.jruby.org/C-Extension-Alternatives
    ./Gemfile:80: [gems, warning] Found gem 'oj' which is reported to have some issues:
    Try `gson`, `json` or `json_pure` instead.|
    gem 'oj'
    ./Gemfile:81: [gems, warning] Found gem 'pg' which is reported to have some issues:
    Use activerecord-jdbcpostgresql-adapter instead or pg_jruby (drop-in replacement).|
    gem 'pg'
    ./Gemfile:187: [gems, warning] Found gem 'mysql2' which is reported to have some issues:
    Use activerecord-jdbcmysql-adapter.|
    gem 'mysql2'
    ./Gemfile:188: [gems, warning] Found gem 'redcarpet' which is reported to have some issues:
    Same as with **RDiscount** use alternatives such as kramdown, Maruku or markdown_j|
    gem 'redcarpet'
    ./Gemfile:189: [gems, warning] Found gem 'sqlite3' which is reported to have some issues:
    Use activerecord-jdbcsqlite3-adapter.|
    gem 'sqlite3', '~> 1.3.13'

    View Slide

  78. View Slide

  79. Threading Concerns
    ./app/models/report.rb:21: [nonatomic, warning] Non-local operator assignment (@start_date) is
    not guaranteed to be atomic.
    @start_date ||= Report.default_days.days.ago.utc.beginning_of_day
    ./app/models/report.rb:22: [nonatomic, warning] Non-local operator assignment (@end_date) is not
    guaranteed to be atomic.
    @end_date ||= Time.now.utc.end_of_day
    ./app/models/admin_dashboard_next_data.rb:17: [nonatomic, warning] Non-local operator assignment
    (@json) is not guaranteed to be atomic.
    @json ||= get_json
    ./app/models/topic_posters_summary.rb:31: [nonatomic, warning] Non-local operator assignment
    (@descriptions_by_id) is not guaranteed to be atomic.
    @descriptions_by_id ||= begin
    ./app/models/topic_posters_summary.rb:33: [nonatomic, warning] Non-local operator assignment
    (descriptions[id]) is not guaranteed to be atomic.
    descriptions[id] ||= []
    ./app/models/topic_posters_summary.rb:79: [nonatomic, warning] Non-local operator assignment
    (@avatar_lookup) is not guaranteed to be atomic.
    @avatar_lookup ||= options[:avatar_lookup] || AvatarLookup.new(user_ids)
    ./app/models/topic_posters_summary.rb:83: [nonatomic, warning] Non-local operator assignment
    (@primary_group_lookup) is not guaranteed to be atomic.
    @primary_group_lookup ||= options[:primary_group_lookup] || PrimaryGroupLookup.new(user_ids)
    @language_names) is not guaranteed to be atomic.
    @language_names ||= begin

    View Slide

  80. Unsupported Features
    ./script/measure.rb:48: [objectspace, warning]
    Use of ObjectSpace is expensive and disabled
    by default. Use -X+O to enable.
    ObjectSpace.each_object do |o|
    ./script/check_forking.rb:14: [fork, error]
    Kernel#fork is not implemented on JRuby.
    child = fork do
    ./script/check_forking.rb:17: [fork, error]
    Kernel#fork is not implemented on JRuby.
    grand_child = fork do

    View Slide

  81. Step 2: Replace C Extensions

    View Slide

  82. Why Not Support C Extensions?
    • Often used for performance reasons
    • The API is enormous and invasive
    • Direct pointer access to Ruby objects
    • JRuby 1.6 shipped experimental support, but...
    • Huge amount of work for partial compatibility
    • Performance was slower than pure Ruby!

    View Slide

  83. $ bundle install

    View Slide

  84. Options
    1.Remove it if not needed
    2.Use a pure-Ruby version if performance is good enough
    3.Call into a native library using FFI (Foreign Function Interface)
    4.Use an equivalent JRuby library
    5.Write a JRuby extension

    View Slide

  85. oj: Optimized json
    • Fast json parsing and dumping with many options
    • No support for JRuby currently
    • Common transitive dependency with its own API
    • Needed for many popular apps/libraries
    https://github.com/ohler55/oj

    View Slide

  86. oj for JRuby!
    • 9200 lines of Java (vs 20k lines of C)
    • Almost ready: 448 runs, 765 assertions, 43 failures, 12 errors
    • 35 F/E from minor features, date/time diffs
    • Performs even better than the C extension!
    • ...and we've done almost no optimization!

    View Slide

  87. Load Performance
    0M
    0.3M
    0.6M
    0.9M
    1.2M
    small medium large
    0.13
    0.29
    1.1
    0.05
    0.11
    0.36
    0.06
    0.11
    0.72
    MRI (oj) JRuby (json) JRuby (oj)
    Millions of loads per second (higher is better)
    https://techblog.thescore.com/2014/05/23/benchmarking-json-generation-in-ruby/

    View Slide

  88. Dump Performance
    0
    0.75
    1.5
    2.25
    3
    small medium large
    0.44
    0.86
    2.3
    0.22
    0.44
    1.1
    0.33
    0.73
    2.1
    MRI (oj) JRuby (json) JRuby (oj)
    Million of dumps per second (higher is better)
    https://techblog.thescore.com/2014/05/23/benchmarking-json-generation-in-ruby/

    View Slide

  89. That's it!
    • Once your application bundles, it should run!
    • Remaining issues: come talk to us
    • Many options for deploying, packaging as Java app, etc
    • Remember those JVM tools!

    View Slide

  90. Wrapping Up

    View Slide

  91. JRuby is here for you!

    View Slide

  92. Get the best of the JVM
    without leaving Ruby

    View Slide

  93. Use JVM libraries
    and languages

    View Slide

  94. Scale up your apps

    View Slide

  95. Save money on
    server resources

    View Slide

  96. Keep the Ruby dream alive!

    View Slide

  97. Thank You!
    • Charles Oliver Nutter
    [email protected]
    • Follow me on Twitter: @headius
    • http://jruby.org

    View Slide