Slide 1

Slide 1 text

Scalable Apps with JRuby Charles Oliver Nutter (@headius)

Slide 2

Slide 2 text

สวัสดี! • 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!

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

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!

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Parallel and Concurrent

Slide 10

Slide 10 text

JVM Tools and GC

Slide 11

Slide 11 text

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

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

JRuby is Ruby plus the best parts of the JVM

Slide 14

Slide 14 text

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

Slide 15

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

Slide 16 text

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

Slide 17

Slide 17 text

Getting Started

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Install JRuby

Slide 21

Slide 21 text

That's it!

Slide 22

Slide 22 text

JRuby IRB

Slide 23

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 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 26

Slide 26 text

Low-Level Performance

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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!

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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!

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

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

Slide 43 text

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

Slide 44

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

Slide 45 text

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

Slide 46

Slide 46 text

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?

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Web Applications

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

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

Slide 56 text

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!

Slide 57

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

Slide 58 text

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!

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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 *

Slide 61

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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)

Slide 67

Slide 67 text

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

Slide 68

Slide 68 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 69

Slide 69 text

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

Slide 70

Slide 70 text

JRuby is the fastest way to run Rails applications.

Slide 71

Slide 71 text

Migrating to JRuby

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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!

Slide 74

Slide 74 text

Step 1: jruby-lint

Slide 75

Slide 75 text

jruby-lint gem See how ready your Ruby code is to run on JRuby $ gem install jruby-lint $ cd my-app $ jrlint

Slide 76

Slide 76 text

[] ~/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

Slide 77

Slide 77 text

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'

Slide 78

Slide 78 text

No content

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

Step 2: Replace C Extensions

Slide 82

Slide 82 text

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!

Slide 83

Slide 83 text

$ bundle install

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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!

Slide 87

Slide 87 text

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/

Slide 88

Slide 88 text

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/

Slide 89

Slide 89 text

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!

Slide 90

Slide 90 text

Wrapping Up

Slide 91

Slide 91 text

JRuby is here for you!

Slide 92

Slide 92 text

Get the best of the JVM without leaving Ruby

Slide 93

Slide 93 text

Use JVM libraries and languages

Slide 94

Slide 94 text

Scale up your apps

Slide 95

Slide 95 text

Save money on server resources

Slide 96

Slide 96 text

Keep the Ruby dream alive!

Slide 97

Slide 97 text

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