Matheus Richard
April 05, 2024
19

# The Fast Lane: Asynchronous Rails

Oh no! Computers are not doubling in speed every two years anymore! How can we make software run faster? You and your Rails app cannot just wait doing nothing, so join me to explore how we can leverage concurrency and parallelism concepts to enhance performance and scalability!

April 05, 2024

## Transcript

SWITCH
20. ### We don't like that! We want to play at the

same time “ - Kids

32. ### fiber = Fiber.new do "Hey, I'm a fiber!" end puts

fiber.resume # Hey, I'm a fiber!
33. ### boy = Fiber.new do puts "I'm playing stage 1" Fiber.yield

puts "I'm playing stage 2" end end
34. ### boy = Fiber.new do puts "I'm playing stage 1" Fiber.yield

puts "I'm playing stage 2" end end
35. ### boy = Fiber.new do puts "I'm playing stage 1" Fiber.yield

puts "I'm playing stage 2" end end
36. ### boy = Fiber.new do puts "I'm playing stage 1" Fiber.yield

puts "I'm playing stage 2" end end boy.resume # 󰗺 I'm playing stage 1
37. ### boy = Fiber.new do puts "I'm playing stage 1" Fiber.yield

puts "I'm playing stage 2" end end boy.resume # 󰗺 I'm playing stage 1 boy.resume # 󰗺 I'm playing stage 2

39. ### current_stage = 1 boy = Fiber.new do loop do puts

"󰗺 I'm playing stage :{current_stage}" current_stage += 1 Fiber.yield end end
40. ### current_stage = 1 boy = Fiber.new do loop do puts

"󰗺 I'm playing stage :{current_stage}" current_stage += 1 Fiber.yield end end
41. ### current_stage = 1 boy = Fiber.new do loop do puts

"󰗺 I'm playing stage :{current_stage}" current_stage += 1 Fiber.yield end end
42. ### current_stage = 1 boy = Fiber.new do loop do puts

"󰗺 I'm playing stage :{current_stage}" current_stage += 1 Fiber.yield end end
43. ### # ::. girl = Fiber.new do loop do puts "󰘁

I'm playing stage :{current_stage}" current_stage += 1 Fiber.yield end end

45. ### 5.times do boy.resume girl.resume end # 󰗺 I'm playing stage

1 # 󰘁 I'm playing stage 2 # 󰗺 I'm playing stage 3 # 󰘁 I'm playing stage 4 # 󰗺 I'm playing stage 5 # 󰘁 I'm playing stage 6 # 󰗺 I'm playing stage 7 # 󰘁 I'm playing stage 8 # 󰗺 I'm playing stage 9 # 󰘁 I'm playing stage 10

47. ### 2.times do boy.resume girl.resume girl.resume girl.resume girl.resume end # 󰗺

I'm playing stage 1 # 󰘁 I'm playing stage 2 # 󰘁 I'm playing stage 3 # 󰘁 I'm playing stage 4 # 󰘁 I'm playing stage 5 # 󰗺 I'm playing stage 6 # 󰘁 I'm playing stage 7 # 󰘁 I'm playing stage 8 # 󰘁 I'm playing stage 9 # 󰘁 I'm playing stage 10
48. ### 󰥺 YOU are the scheduler

# threads start running # automatically

56. ### t1 = Thread.new do fibonacci(35) end t2 = Thread.new do

fibonacci(35) end
57. ### t1 = Thread.new do fibonacci(35) # Runs in ~1s end

t2 = Thread.new do fibonacci(35) # Runs in ~1s end

to run

to run

BLOCK

+= 1 end
76. ### Thread.new do puts "󰗺 I'm playing stage :{current_stage} now!" current_stage

+= 1 sleep 1 end

79. ### current_stage = 1 5.times do [ Thread.new do ... end,

Thread.new do ... end ].each(&:join) end
80. ### current_stage = 1 5.times do [ Thread.new do ... end,

Thread.new do ... end ].each(&:join) end

83. ### girl Time 1 second Executing I/O 1 second . j

o i n boy 1 second 1 second . j o i n ...

86. ### 󰗺 I'm playing stage 1 now! 󰘁 I'm playing stage

1 now! 󰗺 I'm playing stage 3 now! 󰘁 I'm playing stage 3 now! 󰗺 I'm playing stage 5 now! 󰘁 I'm playing stage 5 now! 󰗺 I'm playing stage 7 now! 󰘁 I'm playing stage 7 now! 󰗺 I'm playing stage 9 now! 󰘁 I'm playing stage 9 now!
87. ### Thread.new do puts "󰗺 I'm playing stage :{current_stage} now!" current_stage

+= 1 sleep 1 end
88. ### Thread.new do puts "󰗺 I'm playing stage :{current_stage} now!" current_stage

+= 1 sleep 1 end

90. ### Thread.new do mutex.synchronize do puts "󰘁 I'm playing stage :{current_stage}

now!" current_stage += 1 end sleep 1 end
91. ### Thread.new do mutex.synchronize do puts "󰘁 I'm playing stage :{current_stage}

now!" current_stage += 1 end sleep 1 end
92. ### Thread.new do mutex.synchronize do puts "󰘁 I'm playing stage :{current_stage}

now!" current_stage += 1 end sleep 1 end
93. ### Thread.new do mutex.synchronize do puts "󰘁 I'm playing stage :{current_stage}

now!" current_stage += 1 end sleep 1 end
94. ### 󰗺 I'm playing stage 1 now! 󰘁 I'm playing stage

2 now! 󰗺 I'm playing stage 3 now! 󰘁 I'm playing stage 4 now! 󰗺 I'm playing stage 5 now! 󰘁 I'm playing stage 6 now! 󰗺 I'm playing stage 7 now! 󰘁 I'm playing stage 8 now! 󰗺 I'm playing stage 9 now! 󰘁 I'm playing stage 10 now! 5.014558000024408 seconds to run

98. ### current_stage = 1 Ractor.new do puts "󰗺 I'm playing stage

:{current_stage}" end.take
99. ### current_stage = 1 Ractor.new do puts "󰗺 I'm playing stage

:{current_stage}" end.take # raises ArgumentError
100. ### STAGE = "Foo" Ractor.new do puts "󰗺 I'm playing stage

:{STAGE}" end.take # raises Ractor.:IsolationError
101. ### STAGE = "Foo".freeze Ractor.new do puts "󰗺 I'm playing stage

:{STAGE}" end.take # I'm playing stage Foo
102. ### current_stage = 1 Ractor.new(current_stage) do |stage| puts "󰗺 I'm playing

stage :{stage}" end.take # I'm playing stage 1
103. ### current_stage = 1 Ractor.new(current_stage) do |stage| puts "󰗺 I'm playing

stage :{stage}" end.take # I'm playing stage 1

106. ### stage = Stage.new kid_1 = Process.fork do 5.times do puts

"󰗺 I'm playing a stage :{stage}" end end
107. ### stage = Stage.new kid_1 = Process.fork do 5.times do puts

"󰗺 I'm playing a stage :{stage}" end end

Easy Hard

Easy Hard
117. ### Concurrent Parallel Light Heavy Fibers Threads Ractors Processes 🪡 🧵

🏠 🏭 Easy Hard

120. ### Concurrent Parallel Light Heavy Fibers Threads Ractors Processes 🪡 🧵

🏠 🏭 Easy Hard

🏭 🧵

125. ### Don't do tomorrow what you can do today Don't do

tomorrow what you can do today 🚫

127. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save RegistrationMailer

.welcome_email(@registration) .deliver_now redirect_to @registration end # ... end
128. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save RegistrationMailer

.welcome_email(@registration) .deliver_now redirect_to @registration end # ... end
129. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save RegistrationMailer

.welcome_email(@registration) .deliver_now redirect_to @registration end # ... end
130. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save RegistrationMailer

.welcome_email(@registration) .deliver_later redirect_to @registration end # ... end
131. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save RegistrationMailer

.welcome_email(@registration) .deliver_later redirect_to @registration end # ... end
132. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save CollectRegistrationStatsJob

.perform_later(@registration) redirect_to @registration end # ... end
133. ### class RegistrationsController def create @registration = Registration.new(params) if @registration.save ProcessProfilePictureJob

.perform_later(@registration) redirect_to @registration end # ... end

end

end

end
137. ### class Team < ApplicationRecord has_many :players, dependent: :destroy end class

Player < ApplicationRecord belongs_to :team end
138. ### class Team < ApplicationRecord has_many :players, dependent: :destroy end class

Player < ApplicationRecord belongs_to :team end

140. ### Team.destroy_by(name: "Flamengo") # DELETE FROM "players" WHERE "players"."id" = 1

# DELETE FROM "players" WHERE "players"."id" = 2 # ... # DELETE FROM "players" WHERE "players"."id" = 11 # DELETE FROM "teams" WHERE "teams"."id" = 1
141. ### class Team < ApplicationRecord has_many :players, dependent: :destroy end class

Player < ApplicationRecord belongs_to :team end
142. ### class Team < ApplicationRecord has_many :players, dependent: :destroy_async end class

Player < ApplicationRecord belongs_to :team end

151. ### class TwitterNewsletter def initialize(tweets) @tweets = tweets end def to_s

# ... end end

156. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 2.011646999977529 seconds to run
157. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 2.011646999977529 seconds to run
158. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 2.011646999977529 seconds to run

160. ### class TwitterNewsletter def to_s @tweets .map { |tweet| summary_for(tweet) }

.join("\n") end end
161. ### def to_s Sync do @tweets .map { |tweet| Async {

summary_for(tweet) } } .map(&:wait) .join("\n") end end
162. ### def to_s Sync do @tweets .map { |tweet| Async {

summary_for(tweet) } } .map(&:wait) .join("\n") end end
163. ### def to_s Sync do @tweets .map { |tweet| Async {

summary_for(tweet) } } .map(&:wait) .join("\n") end end
164. ### def to_s Sync do @tweets .map { |tweet| Async {

summary_for(tweet) } } .map(&:wait) .join("\n") end end
165. ### def to_s Sync do @tweets .map { |tweet| Async {

summary_for(tweet) } } .map(&:wait) .join("\n") end end
166. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 1.011646999977529 seconds to run
167. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 1.011646999977529 seconds to run

169. ### tweets = [ "I'm learning Ruby", "I'm learning Rails" ]

time = Benchmark.measure do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 1.011646999977529 seconds to run
170. ### tweets = Array.new(1_000) do "Tweet :{_1}" end time = Benchmark.measure

do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 1.011646999977529 seconds to run
171. ### tweets = Array.new(1_000) do "Tweet :{_1}" end time = Benchmark.measure

do puts TwitterNewsletter.new(tweets) end puts ":{time.real} seconds to run" # 1.011646999977529 seconds to run

173. ### class ReportsController def create @new_authors = Author.recent @new_books = Book.recent

@new_reviews = BookReview.recent end end
174. ### class Book < ActiveRecord::Base belongs_to :author scope :recent, :> {

where("select true from pg_sleep(1)").limit(1) } end

179. ### class ReportsController def create @new_authors = Author.recent @new_books = Book.recent

@new_reviews = BookReview.recent end end

181. ### ASYNC Author Load (1010.2ms) (db time 1011.4ms) ASYNC Book Load

(2.2ms) (db time 1013.8ms) ASYNC BookReview Load (0.2ms) (db time 1014.7ms)
182. ### ASYNC Author Load (1010.2ms) (db time 1011.4ms) ASYNC Book Load

(2.2ms) (db time 1013.8ms) ASYNC BookReview Load (0.2ms) (db time 1014.7ms)
183. ### ASYNC Author Load (1010.2ms) (db time 1011.4ms) ASYNC Book Load

(2.2ms) (db time 1013.8ms) ASYNC BookReview Load (0.2ms) (db time 1014.7ms)
184. ### ASYNC Author Load (1010.2ms) (db time 1011.4ms) ASYNC Book Load

(2.2ms) (db time 1013.8ms) ASYNC BookReview Load (0.2ms) (db time 1014.7ms)

195. ### class BooksController def show @new_books = Book.recent.load_async @external_books = HTTP.get(

"https::/external.com/books" ) end end

198. ### class DashboardController def show @best_sellers = Book.best_sellers @top_reviewed_books = Book.top_reviewed

# ... end end
199. ### class DashboardController def show @best_sellers = Book.best_sellers # takes 10

seconds @top_reviewed_books = Book.top_reviewed # ... end end
200. ### class DashboardController def show @best_sellers = Book.best_sellers.load_async @top_reviewed_books = Book.top_reviewed.load_async

# ... end end

208. ### <div class="dashboard"> <div id="best_sellers"> :::- ... → :/div> <div id="top_reviewed">:::-

... ::>:/div> :/div>
209. ### <div class="dashboard"> <turbo-frame id="best_sellers" src="books/best_sellers" loading="lazy" >:/turbo-frame> <div id="top_reviewed">:::- ...

::>:/div> :/div>
210. ### <div class="dashboard"> <turbo-frame id="best_sellers" src="books/best_sellers" loading="lazy" >:/turbo-frame> <div id="top_reviewed">:::- ...

::>:/div> :/div>
211. ### <div class="dashboard"> <turbo-frame id="best_sellers" src="books/best_sellers" loading="lazy" >:/turbo-frame> <div id="top_reviewed">:::- ...

::>:/div> :/div>
212. ### <div class="dashboard"> <turbo-frame id="best_sellers" src="books/best_sellers" loading="lazy" >:/turbo-frame> <div id="top_reviewed">:::- ...

::>:/div> :/div>
213. ### class Books::BestSellersController def index @best_sellers = Book.best_sellers # takes 10

seconds end end
214. ### <turbo-frame id="best_sellers"> <h1>Best Sellers:/h1> <% @best_sellers.each do |book| %> <%#

... %> <% end %> :/turbo-frame>

itant so i
224. ### just some random text here. This is not import nore

itant so i just some random text here. This is not important so ignore it ⏳

">

">
229. ### @font-face { font-family: "My Font"; font-style: normal; font-weight: 400; font-display:

swap; src: font-url("my-font.ttf"); }
230. ### @font-face { font-family: "My Font"; font-style: normal; font-weight: 400; font-display:

swap; src: font-url("my-font.ttf"); }
231. ### Rafael França rules! Lorem ipsum, dolor sit amet consectetur adipisicing

elit. Rem sint nisi, dolorem accusantium fugiat, eligendi temporibus voluptatum est.
232. ### Rafael França rules! Lorem ipsum, dolor sit amet consectetur adipisicing

elit. Rem sint nisi, dolorem accusantium fugiat, eligendi temporibus voluptatum est.

itant so i

238. ### class AddIndexToUserRoles < ActiveRecord::Migration disable_ddl_transaction! def change add_index :users, :role,

algorithm: :concurrently end end
239. ### class AddIndexToUserRoles < ActiveRecord::Migration disable_ddl_transaction! def change add_index :users, :role,

algorithm: :concurrently end end

243. ### | Workers | Test Suite Time | |---------|-----------------| | 1

| 40 min | | 2 | 20 min | | 4 | 10 min |

true end end

end
251. ### <% books.each do |book| %> <% cache(book) do %> <%=

render book %> <% end %> <% end %>
252. ### class RolesController < ApplicationController def index Rails.cache.fetch("roles", expires_in: 1.hour) do

Auth0::Roles.fetch_all end end end

256. ### def fibonacci(n) if n < 2 n else fibonacci(n -

1) + fibonacci(n - 2) end end