Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.
Performance Optimization 101
for Ruby developers
whoami
Nihad Abbasov
github.com/narkoz
nihad@42na.in
AGENDA
- Ruby
- Rails
- Front-end
What is
application performance
indication of the time
it takes application to
respond
What is
performance optimization
process of modifying a
software system to make
some aspect of it work more
efficiently or...
Why is
performance important
if your application loads and
runs slowly, it is more likely to
be abandoned by your users
1.
Optimizing Ruby
Tips and techniques for optimizing your Ruby code
Measure code
execution time
Use benchmark-ips
gem
Benchmark with
benchmark-ips
require 'benchmark/ips'
Benchmark.ips do |x|
x.report('addition') { 1 + 1 }
x.report('additio...
Use #sample instead
of #shuffle.first
# slow
[*1..100].shuffle.first
# fast
[*1..100].sample
Use #map instead of
#each + push
# slow
array = []
[*1..100].each { |e|
array.push e }
# fast
array = [*1..100].map {
|e| ...
Use #flat_map instead of
#map + flatten
# slow
[*1..100].map { |e|
[e, e] }.flatten
# fast
[*1..100].flat_map { |e|
[e, e]...
Use #reverse_each
instead of #reverse + each
# slow
[*1..100].reverse.each {
|e| e }
# fast
[*1..100].reverse_each {
|e| e...
Use #min_by instead of
#sort_by + first
# slow
[*1..100].sort_by { |e|
e.next }.first
# fast
[*1..100].min_by { |e|
e.next...
Use #min/max instead of
#sort + first/last
# slow
[*1..100].sort_by { |e| e.next
}.last
[*1..100].sort { |e| e.next
}.firs...
Use #detect instead of
#select + first
# slow
[*1..100].select { |e| e
== 20 }.first
# fast
[*1..100].detect { |e| e
== 20...
Use #reverse.detect
instead of #select.last
# slow
[*1..100].select { |e|
(e % 10).zero? }.last
# fast
[*1..100].reverse.d...
Use #each_key instead of
#keys + each
# slow
HASH.keys.each { |k| k }
# fast
HASH.each_key { |k| k }
HASH = Hash[*('aa'..'...
Use #key? instead of
#keys + include?
# slow
HASH.keys.include? 'zz'
# fast
HASH.key? 'zz'
HASH = Hash[*('aa'..'zz')]
Use #value? instead of
#values + include?
# slow
HASH.values.include? 'zz'
# fast
HASH.value? 'zz'
HASH = Hash[*('aa'..'zz...
Make objects immutable
with #freeze
# slow
string = 'Hello'.freeze
10.times { puts string }
# slow
string = 'Hello'
10.tim...
Use magic comment #
frozen_string_literal: true
# frozen_string_literal: true
Use Date.iso8601
instead of Date.parse
# slow
Date.parse('2018-03-19')
# fast
Date.iso8601('2018-03-19')
Use Time.iso8601
instead of Time.parse
# slow
Time.parse('2018-03-21T1
1:26:50Z')
# fast
Time.iso8601('2018-03-21T1
1:26:5...
Use Hash
instead of OpenStruct
# slow
require 'ostruct'
person = OpenStruct.new
person.name = 'John'
person.name
# fast
pe...
Additional resources
and tools
▪ github.com/JuanitoFatas/fast-ruby
▪ github.com/DamirSvrtan/fasterer
▪ github.com/rubocop-...
2.
Optimizing Rails
Tips and techniques for optimizing your Rails application
Benchmark
your application
- wrk
- h2load
- ab
- siege
Use application
monitoring metrics
- scout_apm
- skylight
- newrelic
Check application
performance profile
REQUESTS PER-MINUTE CLASSIFICATION
< 50 ms Fast
< 300 ms Normal
> 300 ms Slow
Use database queries
to deal with records
# DO:
Item.order(created_at: :desc).limit(20)
# DONT:
Item.all.sort_by(&:created...
Use database queries
to deal with records
Don't be afraid to use raw SQL
Use select to get only
the necessary data
@users = User.select(:name, :email).limit(20)
<% @users.each do |user| %>
<%= us...
Use size on relation
instead of count
@messages = current_user.messages.unread
<% @messages.each do |message| %>
<%= messa...
Use database indexes
correctly
User.where(email: 'user@example.org')
# migration:
add_index :users, :email, unique: true
Indexes for
polymorphic associations
class Comment
belongs_to :commentable, polymorphic: true
belongs_to :user
end
class P...
Indexes for
polymorphic associations
add_index :comments, :user_id
add_index :comments, %i[commentable_type commentable_id]
Order records
by ID
User.order(id: :desc)
# faster alternative to
User.order(created_at: :desc)
Use subqueries
with ActiveRecord
# 2 separate queries
Ad.where(id: current_user.bookmarks.pluck(:ad_id))
# 1 query with su...
Eliminate
N+1 queries
@user = User.first
<% @user.comments.each %>
<%= comment.body %>
<% end %>
Eliminate
N+1 queries
Use AR#includes
@user = User.includes(:comments).first
Use
materialized database views
*PostgreSQL only.
use scenic gem
Use limit
in queries
User.limit(10)
Use pagination:
@pagy, @records = pagy(Post.published)
Use explain
when in doubt
User.where(id: [1, 2]).comments.explain
Use
background processing
UserMailer.welcome(@user).deliver_later
VideoImportJob.perform_later(current_user)
Avoid
rendering with loops
<% @users.each do |user| %>
<%= render 'users/profile_card', user: user %>
<% end %>
Use
collection rendering
<%= render partial: 'users/profile_card',
collection: @users %>
Use
collection caching
<%= render @post.comments, cached: true %>
Minimize or suppress
HTTP redirects
Use HTTP Strict
Transport Security
(HSTS)
Enable
HSTS preload
config.force_ssl = true
config.ssl_options = { hsts: { preload: true } }
Use libvips
for image processing
config.active_storage.variant_processor = :vips
Reduce
log writes
Use gems:
▪ rails_semantic_logger
▪ lograge
Use
faster gems
▪ github.com/SamSaffron/fast_blank
▪ github.com/ohler55/oj
▪ github.com/ohler55/ox
▪ github.com/redis/hire...
Use
a faster language
▪ Go
▪ Elixir
▪ C 👀
Additional resources
and tools
▪ github.com/MiniProfiler/rack-mini-profiler
▪ github.com/gregnavis/active_record_doctor
▪ ...
3.
Optimizing Front-end
Tips and techniques for optimizing front-end of your application
Serve
compressed assets
Nginx example with gzip:
location ~ ^/(assets|packs)/ {
...
gzip_static on;
}
Use
better compression
Use Brotli compression format:
location ~ ^/(assets|packs)/ {
...
gzip_static on;
brotli_static on;...
Use
modern cache control
Add immutable to Cache-Control headers:
location ~ ^/(assets|packs)/ {
...
add_header Cache-Contr...
Use
resource hints
<link rel="dns-prefetch"
href="https://assets.example.com">
Use WEBP
for images
Supported by:
▪ Google Chrome (desktop) 17+
▪ Google Chrome for Android version 25+
▪ Microsoft Edge 1...
Use
Turbolinks
Or:
▪ Vuejs
▪ React
▪ Stimulus
Use with caution
HTTP/2 Push
path = view_context.asset_path('application.css', host: '',
protocol: :relative)
response.set...
Questions?
github.com/narkoz
nihad@42na.in
Prochain SlideShare
Chargement dans…5
×

Performance Optimization 101 for Ruby developers - Nihad Abbasov (ENG) | Ruby Meditation 27

68 vues

Publié le

Speech of Nihad Abbasov, Senior Software Engineer at Digital Classifieds, at Ruby Meditation 27, Dnipro, 19.05.2019
Slideshare -
Next conference - http://www.rubymeditation.com/

How fast is your code? Performance is crucial as your startup grows, and optimizing your application can make a huge impact on user experience. During this talk, you will learn hints, techniques and best practices for improving the overall speed of your Ruby application.

Announcements and conference materials https://www.fb.me/RubyMeditation
News https://twitter.com/RubyMeditation
Photos https://www.instagram.com/RubyMeditation
The stream of Ruby conferences (not just ours) https://t.me/RubyMeditation

Publié dans : Technologie
  • Soyez le premier à commenter

Performance Optimization 101 for Ruby developers - Nihad Abbasov (ENG) | Ruby Meditation 27

  1. 1. Performance Optimization 101 for Ruby developers
  2. 2. whoami Nihad Abbasov github.com/narkoz nihad@42na.in
  3. 3. AGENDA - Ruby - Rails - Front-end
  4. 4. What is application performance indication of the time it takes application to respond
  5. 5. What is performance optimization process of modifying a software system to make some aspect of it work more efficiently or use fewer resources
  6. 6. Why is performance important if your application loads and runs slowly, it is more likely to be abandoned by your users
  7. 7. 1. Optimizing Ruby Tips and techniques for optimizing your Ruby code
  8. 8. Measure code execution time Use benchmark-ips gem
  9. 9. Benchmark with benchmark-ips require 'benchmark/ips' Benchmark.ips do |x| x.report('addition') { 1 + 1 } x.report('addition with send') { 1.send(:+, 1) } x.compare! end
  10. 10. Use #sample instead of #shuffle.first # slow [*1..100].shuffle.first # fast [*1..100].sample
  11. 11. Use #map instead of #each + push # slow array = [] [*1..100].each { |e| array.push e } # fast array = [*1..100].map { |e| e }
  12. 12. Use #flat_map instead of #map + flatten # slow [*1..100].map { |e| [e, e] }.flatten # fast [*1..100].flat_map { |e| [e, e] }
  13. 13. Use #reverse_each instead of #reverse + each # slow [*1..100].reverse.each { |e| e } # fast [*1..100].reverse_each { |e| e }
  14. 14. Use #min_by instead of #sort_by + first # slow [*1..100].sort_by { |e| e.next }.first # fast [*1..100].min_by { |e| e.next }
  15. 15. Use #min/max instead of #sort + first/last # slow [*1..100].sort_by { |e| e.next }.last [*1..100].sort { |e| e.next }.first [*1..100].sort { |e| e.next }.last # fast [*1..100].max_by { |e| e.next } [*1..100].min { |e| e.next } [*1..100].max { |e| e.next }
  16. 16. Use #detect instead of #select + first # slow [*1..100].select { |e| e == 20 }.first # fast [*1..100].detect { |e| e == 20 }
  17. 17. Use #reverse.detect instead of #select.last # slow [*1..100].select { |e| (e % 10).zero? }.last # fast [*1..100].reverse.detect { |e| (e % 10).zero? }
  18. 18. Use #each_key instead of #keys + each # slow HASH.keys.each { |k| k } # fast HASH.each_key { |k| k } HASH = Hash[*('aa'..'zz')]
  19. 19. Use #key? instead of #keys + include? # slow HASH.keys.include? 'zz' # fast HASH.key? 'zz' HASH = Hash[*('aa'..'zz')]
  20. 20. Use #value? instead of #values + include? # slow HASH.values.include? 'zz' # fast HASH.value? 'zz' HASH = Hash[*('aa'..'zz')]
  21. 21. Make objects immutable with #freeze # slow string = 'Hello'.freeze 10.times { puts string } # slow string = 'Hello' 10.times { puts string }
  22. 22. Use magic comment # frozen_string_literal: true # frozen_string_literal: true
  23. 23. Use Date.iso8601 instead of Date.parse # slow Date.parse('2018-03-19') # fast Date.iso8601('2018-03-19')
  24. 24. Use Time.iso8601 instead of Time.parse # slow Time.parse('2018-03-21T1 1:26:50Z') # fast Time.iso8601('2018-03-21T1 1:26:50Z')
  25. 25. Use Hash instead of OpenStruct # slow require 'ostruct' person = OpenStruct.new person.name = 'John' person.name # fast person = {} person[:name] = 'John' person[:name]
  26. 26. Additional resources and tools ▪ github.com/JuanitoFatas/fast-ruby ▪ github.com/DamirSvrtan/fasterer ▪ github.com/rubocop-hq/rubocop-performance
  27. 27. 2. Optimizing Rails Tips and techniques for optimizing your Rails application
  28. 28. Benchmark your application - wrk - h2load - ab - siege
  29. 29. Use application monitoring metrics - scout_apm - skylight - newrelic
  30. 30. Check application performance profile REQUESTS PER-MINUTE CLASSIFICATION < 50 ms Fast < 300 ms Normal > 300 ms Slow
  31. 31. Use database queries to deal with records # DO: Item.order(created_at: :desc).limit(20) # DONT: Item.all.sort_by(&:created_at).reverse.first(20)
  32. 32. Use database queries to deal with records Don't be afraid to use raw SQL
  33. 33. Use select to get only the necessary data @users = User.select(:name, :email).limit(20) <% @users.each do |user| %> <%= user.name %><br> <%= user.email %> <% end %>
  34. 34. Use size on relation instead of count @messages = current_user.messages.unread <% @messages.each do |message| %> <%= message.body %> <% end %> <h2>Unread Messages: <%= @messages.size %></h2>
  35. 35. Use database indexes correctly User.where(email: 'user@example.org') # migration: add_index :users, :email, unique: true
  36. 36. Indexes for polymorphic associations class Comment belongs_to :commentable, polymorphic: true belongs_to :user end class Post has_many :comments, as: :commentable has_one :user end
  37. 37. Indexes for polymorphic associations add_index :comments, :user_id add_index :comments, %i[commentable_type commentable_id]
  38. 38. Order records by ID User.order(id: :desc) # faster alternative to User.order(created_at: :desc)
  39. 39. Use subqueries with ActiveRecord # 2 separate queries Ad.where(id: current_user.bookmarks.pluck(:ad_id)) # 1 query with subquery Ad.where(id: current_user.bookmarks.select(:ad_id))
  40. 40. Eliminate N+1 queries @user = User.first <% @user.comments.each %> <%= comment.body %> <% end %>
  41. 41. Eliminate N+1 queries Use AR#includes @user = User.includes(:comments).first
  42. 42. Use materialized database views *PostgreSQL only. use scenic gem
  43. 43. Use limit in queries User.limit(10) Use pagination: @pagy, @records = pagy(Post.published)
  44. 44. Use explain when in doubt User.where(id: [1, 2]).comments.explain
  45. 45. Use background processing UserMailer.welcome(@user).deliver_later VideoImportJob.perform_later(current_user)
  46. 46. Avoid rendering with loops <% @users.each do |user| %> <%= render 'users/profile_card', user: user %> <% end %>
  47. 47. Use collection rendering <%= render partial: 'users/profile_card', collection: @users %>
  48. 48. Use collection caching <%= render @post.comments, cached: true %>
  49. 49. Minimize or suppress HTTP redirects Use HTTP Strict Transport Security (HSTS)
  50. 50. Enable HSTS preload config.force_ssl = true config.ssl_options = { hsts: { preload: true } }
  51. 51. Use libvips for image processing config.active_storage.variant_processor = :vips
  52. 52. Reduce log writes Use gems: ▪ rails_semantic_logger ▪ lograge
  53. 53. Use faster gems ▪ github.com/SamSaffron/fast_blank ▪ github.com/ohler55/oj ▪ github.com/ohler55/ox ▪ github.com/redis/hiredis-rb ▪ github.com/ddnexus/pagy
  54. 54. Use a faster language ▪ Go ▪ Elixir ▪ C 👀
  55. 55. Additional resources and tools ▪ github.com/MiniProfiler/rack-mini-profiler ▪ github.com/gregnavis/active_record_doctor ▪ github.com/tmm1/stackprof ▪ github.com/schneems/derailed_benchmarks ▪ github.com/plentz/lol_dba ▪ github.com/flyerhzm/bullet
  56. 56. 3. Optimizing Front-end Tips and techniques for optimizing front-end of your application
  57. 57. Serve compressed assets Nginx example with gzip: location ~ ^/(assets|packs)/ { ... gzip_static on; }
  58. 58. Use better compression Use Brotli compression format: location ~ ^/(assets|packs)/ { ... gzip_static on; brotli_static on; }
  59. 59. Use modern cache control Add immutable to Cache-Control headers: location ~ ^/(assets|packs)/ { ... add_header Cache-Control public,immutable; }
  60. 60. Use resource hints <link rel="dns-prefetch" href="https://assets.example.com">
  61. 61. Use WEBP for images Supported by: ▪ Google Chrome (desktop) 17+ ▪ Google Chrome for Android version 25+ ▪ Microsoft Edge 18+ ▪ Firefox 65+ ▪ Opera 11.10+ ▪ Native web browser, Android 4.0+ (ICS)
  62. 62. Use Turbolinks Or: ▪ Vuejs ▪ React ▪ Stimulus
  63. 63. Use with caution HTTP/2 Push path = view_context.asset_path('application.css', host: '', protocol: :relative) response.set_header('Link', "<#{path}>; rel=preload; as=style")
  64. 64. Questions? github.com/narkoz nihad@42na.in

×