Slide 1

Slide 1 text

Ruby 3.0 & Rails 6.1 Ernesto Tagwerker (@etagwerker) January 28th, 2021

Slide 2

Slide 2 text

Hi, I’m from Argentina I live in Philadelphia I ❤ Open Source

Slide 3

Slide 3 text

Heroes for Hire Alum

Slide 4

Slide 4 text

Founder & Software Engineer @FastRubyIO & @OmbuLabs

Slide 5

Slide 5 text

Sample Code $ git clone [email protected]:etagwerker/ create-2021.git

Slide 6

Slide 6 text

Ruby 3.0

Slide 7

Slide 7 text

- Ractor - Pattern Matching - Ruby 3x3 - Memory: Garbage Compaction - Fiber Scheduler - Static Analysis - JIT Improvements - And more…

Slide 8

Slide 8 text

Ruby 3.0 (w/ rvm) > rvm get head > rvm install 3.0 > rvm use 3.0 > ruby --version ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]

Slide 9

Slide 9 text

Rightward Assignments 1 42 => answer 2 p answer #=> 42 3 4 {first: "John", last: "Doe"} => {first:,last:} 5 p first #=> "John" 6 p last #=> "Doe"

Slide 10

Slide 10 text

Find Pattern 1 case ["a", 1, "b", "c", 2, "d", "e", "f", 3] 2 in [*pre, String => x, String => y, *post] 3 p pre #=> ["a", 1] 4 p x #=> "b" 5 p y #=> "c" 6 p post #=> [2, "d", "e", "f", 3] 7 end

Slide 11

Slide 11 text

Endless Method Definitions 1 def square(x) = x * x 2 3 puts "7 square = #{square(7)}"

Slide 12

Slide 12 text

Ractor • The feature formerly known as “guilds”.

Slide 13

Slide 13 text

Ractor • Ractor is an Actor-model like concurrent abstraction.

Slide 14

Slide 14 text

Concurrency vs. Parallelism

Slide 15

Slide 15 text

Concurrency vs. Parallelism Fibers Threads Ractors

Slide 16

Slide 16 text

Ractor • Ractor is designed to provide a parallel execution feature of Ruby without thread-safety concerns.

Slide 17

Slide 17 text

Ractor • Ractor does not use the GVL. Each ractor has its own GVL, so you can have multiple threads within your ractor.

Slide 18

Slide 18 text

What is the GVL? (Global VM Lock) • “The Ruby Virtual Machine is not internally thread-safe, […] so we use a global lock around it so that only one thread can access it in parallel.” Nate Berkopec

Slide 19

Slide 19 text

A typical thread

Slide 20

Slide 20 text

Parallelism Concerns • Non-determinism • Race conditions • Deadlocks • And more...

Slide 21

Slide 21 text

Ractor by design: “… without thread-safety concerns” • Shareable objects • Unshareable objects

Slide 22

Slide 22 text

Threads (This is OK) 1 GOOD = 'good'.freeze 2 BAD = 'bad' 3 4 t = Thread.new do 5 puts "GOOD=#{GOOD}" 6 puts "BAD=#{BAD}" 7 end 8 9 t.join GOOD=good BAD=bad

Slide 23

Slide 23 text

1 GOOD = 'good'.freeze 2 BAD = 'bad' 3 4 r = Fiber.new do 5 puts "GOOD=#{GOOD}" 6 puts "BAD=#{BAD}" 7 end 8 9 r.resume GOOD=good BAD=bad Fibers (This is OK)

Slide 24

Slide 24 text

1 GOOD = 'good'.freeze 2 BAD = 'bad' 3 4 r = Ractor.new do 5 puts "GOOD=#{GOOD}" 6 puts "BAD=#{BAD}" 7 end 8 9 r.take Ractor (This is NOT OK)

Slide 25

Slide 25 text

:267: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. GOOD=good # terminated with exception (report_on_exception is true): constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) :694:in `take': thrown by remote Ractor. (Ractor::RemoteError) from constants_with_ractor.rb:9:in `' constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) Ractor

Slide 26

Slide 26 text

:267: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. GOOD=good # terminated with exception (report_on_exception is true): constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) :694:in `take': thrown by remote Ractor. (Ractor::RemoteError) from constants_with_ractor.rb:9:in `' constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) Ractor

Slide 27

Slide 27 text

:267: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues. GOOD=good # terminated with exception (report_on_exception is true): constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) :694:in `take': thrown by remote Ractor. (Ractor::RemoteError) from constants_with_ractor.rb:9:in `' constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects in constant Object::BAD by non-main Ractor. (Ractor::IsolationError) Ractor

Slide 28

Slide 28 text

When and why might Ractors be better than existing Ruby threads? • “The lack of GVL allows them to fully use more cores. So in cases where Ruby can't easily use all your cores, Ractors can be better than threads.” Noah Gibbs

Slide 29

Slide 29 text

Ractor: Shareable vs. Unshareable Objects • Shareable objects: • Immutable objects (frozen objects only refer to shareable objects) • Class/module objects • Special shareable objects (Ractor objects, and so on) • Unshareable objects: Everything else

Slide 30

Slide 30 text

Pattern Matching • You might be familiar with the case/when flow: 1 object = "a string" 2 3 case object 4 when String 5 puts "object is a String" 6 else 7 puts "object is not a String" 8 end $ ruby case_when.rb object is a String

Slide 31

Slide 31 text

Pattern Matching • Ruby 3.0 introduces the case/in flow which uses pattern matching: 1 # example based on https://docs.ruby-lang. 2 org/en/3.0.0/doc/syntax/pattern_matching_rdoc. 3 html 4 5 def connect(config) 6 case config 7 in db: {user:} # matches subhash and puts 8 matched value in variable user 9 puts "Connect with user '#{user}'" 10 in connection: {username: } 11 puts "Connect with connection and user '#{username}'" 12 else 13 puts "Unrecognized structure of config" 14 end 15 end 16 17 connect(db: {user: 'admin', password: ‘abc123'}) $ ruby case_in.rb Connect with user 'admin'

Slide 32

Slide 32 text

• any Ruby object (matched by the === operator, like in when); (Value pattern) • array pattern: [, , , ...]; (Array pattern) • find pattern: [*variable, , , , ..., *variable]; (Find pattern) • hash pattern: {key: , key: , ...}; (Hash pattern) • combination of patterns with |; (Alternative pattern) • variable capture: => variable or variable; (As pattern, Variable pattern) Patterns can be:

Slide 33

Slide 33 text

Variable Binding 1 case [17, 76] 2 in Integer => a, Integer => b 3 puts "a=#{a}, b= #{b}" 4 else 5 puts "not matched" 6 end 7 8 case {a: 1, b: 7, c: 76} 9 in a: Integer => m, b: Integer => n, c: Integer => o 10 puts "m: #{m}, n: #{n}, o: #{o}, " 11 else 12 "not matched" 13 end $ ruby binding.rb a=17, b= 76 m: 1, n: 7, o: 76,

Slide 34

Slide 34 text

You can even use it in an if as a “one-liner”: 1 first_name = "John" 2 3 if {first_name: first_name} in {first_name: "John"} 4 5 puts "First name is John!" 6 end 7 8 if {first_name: first_name} in {first_name: "Bob"} 9 10 puts "First name is Bob!" 11 else 12 puts "First name is not Bob!" 13 end if_in.rb:3: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! if_in.rb:7: warning: One-line pattern matching is experimental, and the behavior may change in future versions of Ruby! First name is John! First name is not Bob!

Slide 35

Slide 35 text

If you want to skip the warnings: 1 Warning[:experimental] = false

Slide 36

Slide 36 text

Pattern Matching: Advanced Example

Slide 37

Slide 37 text

Pattern Matching: Poker Hand

Slide 38

Slide 38 text

Elixir-like Method Overloading Not Supported: 1 def process(%{"animal" => animal}) do 2 IO.puts("The animal is: #{animal}") 3 end 4 5 def process(%{"plant" => plant}) do 6 IO.puts("The plant is: #{plant}") 7 end 8 9 def process(%{"person" => person}) do 10 IO.puts("The person is: #{person}") 11 end •

Slide 39

Slide 39 text

Ruby 3x3 • “The goal is to make Ruby 3 run three times faster as compared to Ruby 2.0.” * Matz, 2016 (source)

Slide 40

Slide 40 text

Ruby 3x3

Slide 41

Slide 41 text

What is a JIT? • JIT stands for “just-in-time” compilation. A JIT allows an interpreted language such as Ruby to optimize frequently run methods so they run faster for future calls.

Slide 42

Slide 42 text

Ruby’s MJIT • The M is for methods • It requires more memory to keep its method operations cache • It has been available since Ruby 2.6 and it has been optimized on Ruby 3.0

Slide 43

Slide 43 text

JIT: How do I use it? # enabled.rb 1 puts "MJIT is enabled: #{RubyVM::MJIT.enabled?}” $ ruby --jit enabled.rb MJIT is enabled: true $ ruby enabled.rb MJIT is enabled: false

Slide 44

Slide 44 text

JIT: How do I use it in Rails? $ RUBYOPT=--jit rails server

Slide 45

Slide 45 text

Ruby 3x3

Slide 46

Slide 46 text

Ruby 3x3

Slide 47

Slide 47 text

Memory: Automatic Garbage Compaction • You no longer have to manually trigger the garbage compactor with: 1 GC.compact # available since Ruby 2.6 2 3 puts “Yay, a tidier heap sparks joy!" •

Slide 48

Slide 48 text

Memory: Automatic Garbage Compaction • Most objects will be automatically transferred to the heap and compacted to improve memory usage and performance

Slide 49

Slide 49 text

Concurrency / Parallelism • “It’s multi-core age today. Concurrency is very important. With Ractor, along with Async Fiber, Ruby will be a real concurrent language.” Matz

Slide 50

Slide 50 text

Fiber Scheduler • It can be used to intercept blocking operations and yield.

Slide 51

Slide 51 text

Fiber vs Thread • Fibers are more lightweight. They eat less memory. • The OS decides when to run/ pause your threads. • You have more control over when to run/pause your fibers.

Slide 52

Slide 52 text

Fiber Scheduler 1 f = Fiber.new { puts 1; Fiber.yield; puts 2 } 2 3 f.resume 4 # 1 5 # => nil 6 7 f.resume 8 # 2 9 # => nil

Slide 53

Slide 53 text

Fiber Scheduler 3 Thread.new do # in this thread, we'll have non-blocking fibers 4 5 Fiber.set_scheduler Scheduler.new 6 7 %w[2.6 2.7 3.0].each do |version| 8 Fiber.schedule do # Runs block of code in a separate Fiber 9 10 t = Time.now 11 # ... 16 Net::HTTP.get('rubyreferences.github.io', "/rubychanges/#{version}.html") 19 puts '%s: finished in %.3f' % [version, 20 Time.now - t] 21 end 22 end 23 end.join # At the END of the thread code, Scheduler will be called to dispatch 24 25 # all waiting fibers in a non-blocking manner 26 27 28 puts 'Total: finished in %.3f' % (Time.now - 29 start) 30 31 # Prints: 32 # 2.6: finished in 0.139 33 # 2.7: finished in 0.141 34 # 3.0: finished in 0.143 35 # Total: finished in 0.146

Slide 54

Slide 54 text

Don’t Wait For Me! Scalable Concurrency for Ruby 3 by Samuel Williams

Slide 55

Slide 55 text

Fiber Scheduler (Why?) • You don’t have to manually yield. • You end up with cleaner code in your fibers. • There is a scheduler implementation in the `async` gem. (source)

Slide 56

Slide 56 text

Static Analysis • “Ruby seeks the future with static type checking, without type declaration, using abstract interpretation. RBS & TypeProf are the first step to the future. More steps to come.” Matz

Slide 57

Slide 57 text

Static Analysis: RBS • RBS is a language to describe the structure of Ruby programs. • Ruby 3.0 ships with the `rbs` gem which includes the `rbs` CLI command.

Slide 58

Slide 58 text

Static Analysis: RBS 1 # test.rb 2 class User 3 def initialize(name:, age:) 4 @name, @age = name, age 5 end 6 7 attr_reader :name, :age 8 end 9 10 User.new(name: "John", age: 38)

Slide 59

Slide 59 text

Static Analysis: RBS $ rbs prototype rb user.rb 1 # test.rb 2 class User 3 def initialize: (name: untyped name, age: 4 untyped age) -> untyped 5 6 attr_reader name: untyped 7 8 attr_reader age: untyped 9 end

Slide 60

Slide 60 text

Static Analysis: RBS $ rbs help Usage: rbs [options...] [command...] Available commands: ast, list, ancestors, methods, method, validate, constant, paths, prototype, vendor, parse, test, version, help. Options: -r LIBRARY Load RBS files of the library -I DIR Load RBS files from the directory --no-stdlib Skip loading standard library signatures --repo DIR Add RBS repository --log-level LEVEL Specify log level (defaults to `warn`) --log-output OUTPUT Specify the file to output log (defaults to stderr)

Slide 61

Slide 61 text

TypeProf • TypeProf is a type analysis tool bundled in the Ruby 3.0 package.

Slide 62

Slide 62 text

TypeProf • It reads plain (non-type- annotated) Ruby code, analyzes what methods are defined and how they are used, and generates a prototype of type signature in RBS format.

Slide 63

Slide 63 text

Static Analysis: TypeProf $ typeprof user.rb 1 # test.rb 2 class User 3 def initialize: (name: String, age: 4 Integer) -> [String, Integer] 5 6 attr_reader name: String 7 8 attr_reader age: Integer 9 end

Slide 64

Slide 64 text

Typeprof (Online Tool) https://mame.github.io/typeprof-playground/

Slide 65

Slide 65 text

Typeprof (Demo)

Slide 66

Slide 66 text

Static Analysis: Pragmatic View (Why?) • Exhaustiveness Checking (Sorbet) • Automatic Documentation of Public Method Signatures

Slide 67

Slide 67 text

JIT Improvements • Significantly decreased the size of JIT-ed code (less memory-intensive) • Still not ready for optimizing workloads like Rails

Slide 68

Slide 68 text

And more... • IRB Performance Improvement • Keyword arguments are separated from other arguments • Pattern matching (case/in) is no longer experimental • Arguments forwarding now supports leading arguments

Slide 69

Slide 69 text

Positional and Keyword Arguments: ArgumentError 1 # This method accepts only a keyword argument 2 def foo(k: 1) 3 p k 4 end 5 6 h = { k: 42 } 7 8 # This method call passes a positional Hash argument 9 # In Ruby 2.7: The Hash is automatically converted to a keyword argument 10 # In Ruby 3.0: This call raises an ArgumentError 11 foo(h) 12 # => demo.rb:11: warning: Using the last argument as keyword parameters is 13 deprecated; maybe ** should be added to the call 14 # demo.rb:2: warning: The called method `foo' is defined here 15 # 42 16 17 # If you want to keep the behavior in Ruby 3.0, use double splat 18 foo(**h) #=> 42

Slide 70

Slide 70 text

Positional and Keyword Arguments: ArgumentError • Best case scenario: Many ArgumentErrors all over the logs. • Worst case scenario: Weird, buggy behavior with Ruby 3.0.

Slide 71

Slide 71 text

Upgrading to Ruby 3.0

Slide 72

Slide 72 text

Notes • Upgrade to Ruby 2.7 first. You get slightly better performance than in 2.6. Also, you will see a bunch of deprecation warnings re: positional and keyword arguments. • Fix deprecation warnings re: positional arguments. • Proactively check that your dependencies work with Ruby 3.0. Maybe your first OSS contribution? • Backtraces in Ruby 3.0 behave like in Ruby 2.4: an error message and the line number where the exception occurs are printed first, and its callers are printed later

Slide 73

Slide 73 text

Rails 6.1

Slide 74

Slide 74 text

- Horizontal Sharding (DB) - Strict Loading - Delegated Types - Destroy Associations Async - Disallowed Deprecation Support - Performance Improvements - And more…

Slide 75

Slide 75 text

Horizontal Sharding • Horizontal sharding is when you split up your database to reduce the number of rows on each database server, but maintain the same schema across "shards". This is commonly called “multi-tenant" sharding.

Slide 76

Slide 76 text

Sharding (Config) 1 production: 2 primary: 3 database: my_primary_database 4 adapter: mysql 5 primary_replica: 6 database: my_primary_database 7 adapter: mysql 8 replica: true 9 primary_shard_one: 10 database: my_primary_shard_one 11 adapter: mysql 12 primary_shard_one_replica: 13 database: my_primary_shard_one 14 adapter: mysql 15 replica: true

Slide 77

Slide 77 text

ApplicationRecord 1 class ApplicationRecord < ActiveRecord::Base 2 self.abstract_class = true 3 4 connects_to shards: { 5 default: { writing: :primary, reading: : 6 primary_replica }, 7 shard_one: { writing: :primary_shard_one, 8 reading: : 9 primary_shard_one_replica } 10 } 11 end

Slide 78

Slide 78 text

Shard Use Case 1 ActiveRecord::Base.connected_to(role: :reading, 2 shard: :shard_one) do 3 Record.first # lookup record from read replica of shard one 4 end 5

Slide 79

Slide 79 text

Strict Loading • `config.active_record.strict_loading_by_default` is a boolean value that either enables or disables strict_loading mode by default. Defaults to `false`.

Slide 80

Slide 80 text

Strict Loading 1 class Developer < ApplicationRecord 2 self.strict_loading_by_default = true 3 4 has_many :projects 5 end 6 7 dev = Developer.first 8 dev.projects.first 9 # raises ActiveRecord::StrictLoadingViolationError Exception: Developer is 10 # marked as strict_loading and Project cannot be lazily loaded. 11 12 dev = Developer.includes(:projects).first 13 dev.projects.first 14 # this works

Slide 81

Slide 81 text

Strict Loading: Why? • It’s a strict way to make sure you don’t end up with N+1 queries.

Slide 82

Slide 82 text

Delegated Types • “This is similar to what's called multi-table inheritance in Django, but instead of actual inheritance, this approach uses delegation to form the hierarchy and share responsibilities.” DHH • “It’s like ‘single-table inheritance’ but without inheritance (it uses delegation instead) and with multiple tables.” Ernesto

Slide 83

Slide 83 text

Delegated Types 1 # Schema: entries[ id, account_id, creator_id, 2 created_at, updated_at, entryable_type, 3 entryable_id ] 4 class Entry < ApplicationRecord 5 belongs_to :account 6 belongs_to :creator 7 delegated_type :entryable, types: %w[ Message Comment ] 8 9 end 10 11 # To be included in delegated classes 12 module Entryable 13 extend ActiveSupport::Concern 14 15 included do 16 has_one :entry, as: :entryable, touch: true 17 end 18 end

Slide 84

Slide 84 text

Delegated Types 1 # Schema: messages[ id, subject ] 2 class Message < ApplicationRecord 3 include Entryable 4 has_rich_text :content 5 end 6 7 # Schema: comments[ id, content ] 8 class Comment < ApplicationRecord 9 include Entryable 10 end

Slide 85

Slide 85 text

Delegated Types: Why? • In case you need more flexibility and single- table inheritance doesn’t seem like the best way to go.

Slide 86

Slide 86 text

Destroy Associations (Async) 1 class Account < ApplicationRecord 2 belongs_to :supplier, dependent: :destroy 3 4 end 5 6 # destroys associations synchronously

Slide 87

Slide 87 text

Destroy Associations (Async) 1 class Account < ApplicationRecord 2 belongs_to :supplier, dependent: :destroy_async 3 4 end 5 6 # `:destroy_async` will enqueue a job to 7 destroy associated records in the background.

Slide 88

Slide 88 text

Destroy Async: Why? • It can save you time (you don’t have to implement this in your application code) and it can quickly save you from request time outs after deleting a “god record”

Slide 89

Slide 89 text

ActiveStorage • Permanent URLs for public storage blobs. Services can be configured in `config/ storage.yml` with a new key `public: true | false` to indicate whether a service holds public blobs or private blobs. Public services will always return a permanent URL. • You can now configure different services for different attachments. More granular control for services per attachment.

Slide 90

Slide 90 text

ActiveStorage 1 # If you need the attachment to use a service 2 # which differs from the globally configured one, 3 # pass the +:service+ option. For instance: 4 class User < ActiveRecord::Base 5 has_one_attached :avatar, service: :s3 6 end

Slide 91

Slide 91 text

ActiveStorage: Why? • It’s good to see some forward progress in feature development, especially increasing the flexibility of ActiveStorage.

Slide 92

Slide 92 text

ActiveRecord: Performance Improvements • Avoid making queries where the value is an empty array. (https://github.com/rails/rails/ pull/37266) • Speed up queries when Rails knows that all values in the query are integers. (https:// github.com/rails/rails/pull/39009)

Slide 93

Slide 93 text

Classic Autoloader Deprecated • New Rails projects discouraged from using the classic autoloader. • The default autoloader will be `zeitwerk` for all new Rails applications.

Slide 94

Slide 94 text

Disallowed Deprecation Support • It allows the configuration of rules to match deprecation warnings that should not be allowed within the app.

Slide 95

Slide 95 text

Disallowed Deprecation Support 1 ActiveSupport::Deprecation.disallowed_warnings = [ 2 "bad_method", 3 :worse_method, 4 /(horrible|unsafe)_method/, 5 ] 6 7 # Or disallow everything: 8 9 ActiveSupport::Deprecation.disallowed_warnings = :all

Slide 96

Slide 96 text

Disallowed Deprecation Support 1 if Rails.env.production? 2 ActiveSupport::Deprecation.disallowed_behavior = [:log] 3 else 4 ActiveSupport::Deprecation.disallowed_behavior = [:raise] 5 end

Slide 97

Slide 97 text

Disallowed Deprecation Support: Why? • When we eliminate deprecation warnings that appear in our app we want to be sure that the deprecations are never re-introduced. This will be very useful in future Ruby/Rails upgrade projects.

Slide 98

Slide 98 text

Upgrading to Rails 6.1

Slide 99

Slide 99 text

Notes • Upgrade to Rails 6.0 first • Address all deprecation warnings in the Rails 6.0 test and production logs • Proactively check that our dependencies work with Rails 6.1: Maybe your first OSS contribution? Check your Gemfile.lock for incompatibilities by using RailsBump • Focus on “Removals” in the Rails 6.1 release notes. Most notable: Remove deprecated ActiveRecord::Base#update_attributes and ActiveRecord::Base#update_attributes!

Slide 100

Slide 100 text

Thank you! @etagwerker 100 Thank you!

Slide 101

Slide 101 text

Resources 1. https://www.fastruby.io/blog/ruby/performance/how-fast-are-ractors.html 2. https://www.speedshop.co/2020/05/11/the-ruby-gvl-and-scaling.html 3. https://engineering.appfolio.com/appfolio-engineering/2019/9/13/benchmarking-fibers-threads-and-processes 4. https://docs.ruby-lang.org/en/3.0.0/doc/syntax/pattern_matching_rdoc.html 5. https://www.ruby-lang.org/en/news/2020/12/25/ruby-3-0-0-released/ 6. https://twitter.com/k0kubun/status/1256142302608650244 7. https://www.rubyguides.com/2019/11/what-are-fibers-in-ruby/ 8. https://www.rubyguides.com/2015/07/ruby-threads/ 9. https://www.mendelowski.com/docs/ruby/pattern-matching/ 10. https://twitter.com/keystonelemur/status/1349244719751196672/photo/1 11. https://www.toptal.com/ruby/ruby-pattern-matching-tutorial 12. https://medium.com/@k0kubun/ruby-3-0-jit-and-beyond-4d9404ce33c 13. https://developers.redhat.com/blog/2018/03/22/ruby-3x3-performance-goal/ 14. https://developers.redhat.com/blog/2018/03/22/ruby-3x3-performance-goal/ 15. https://blog.heroku.com/ruby-just-in-time-compilation 16. https://bugs.ruby-lang.org/issues/12589 17. https://scoutapm.com/blog/ruby-3-features 18. https://engineering.appfolio.com/appfolio-engineering/2019/3/22/ruby-27-and-the-compacting-garbage-collector 19. https://www.youtube.com/watch?v=Y29SSOS4UOc (Don’t wait for me! Scalable concurrency for Ruby 3!) 20. https://rubyreferences.github.io/rubychanges/3.0.html#non-blocking-fiber-and-scheduler 21. https://sorbet.org/docs/exhaustiveness 22. https://github.com/AaronC81/sord 23. https://weblog.rubyonrails.org/2020/12/9/Rails-6-1-0-release/ 24. https://www.fastruby.io/blog/rails/upgrades/upgrade-rails-from-5-2-to-6-0.html 25. https://www.ruby-lang.org/en/news/2019/12/12/separation-of-positional-and-keyword-arguments-in-ruby-3-0/ 26. https://github.com/socketry/async/pull/56 27. https://bugs.ruby-lang.org/issues/17100

Slide 102

Slide 102 text

Thank you! @etagwerker 102 Questions?