Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

WebPerformance: Why and How? – Stefan Wintermeyer

181 views

Published on

Elixir Club 11
June 23, 2018
Ternopil

Published in: Technology
  • Be the first to comment

  • Be the first to like this

WebPerformance: Why and How? – Stefan Wintermeyer

  1. 1. WebPerformance Why and How Stefan Wintermeyer (@wintermeyer)
  2. 2. Two Hearts in One Chest
  3. 3. What websites do not have to care about WebPerformance?
  4. 4. next-weeks-lottery-numbers.com If you run the only page with this information!
  5. 5. What websites DO have to care about WebPerformance?
  6. 6. last-weeks-lottery-numbers.com Because there are a lot of similar pages.
  7. 7. What's the impact of slow sites? Lower conversions and engagement, higher bounce rates... Ilya Grigorik @igrigorik Make The Web Faster, Google
  8. 8. Yo ho ho and a few billion pages of RUM How speed affects bounce rate @igrigorik
  9. 9. Usability Engineering 101 Delay User reaction 0 - 100 ms Instant 100 - 300 ms Feels sluggish 300 - 1000 ms Machine is working... 1 s+ Mental context switch 10 s+ I'll come back later... Stay under 250 ms to feel "fast". Stay under 1000 ms to keep users attention. @igrigorik
  10. 10. Web Search Delay Experiment Type of Delay Delay (ms) Duration (weeks) Impact on Avg. Daily Searches Pre-header 100 4 -0.20 % Pre-header 200 6 -0.59% Post-header 400 6 0.59% Post-ads 200 4 0.30% Source: https://www.igvita.com/slides/2012/webperf-crash-course.pdf
  11. 11. For many, mobile is the one and only internet device! Country Mobile-only users Egypt 70% India 59% South Africa 57% Indonesia 44% United States 25% onDevice Research @igrigorik
  12. 12. < 1.000 ms Page Loading Time on 3G ist der Mount Everest.
  13. 13. The (short) life of our 1000 ms budget 3G (200 ms RTT) 4G(80 ms RTT) Control plane (200-2500 ms) (50-100 ms) DNS lookup 200 ms 80 ms TCP Connection 200 ms 80 ms TLS handshake (200-400 ms) (80-160 ms) HTTP request 200 ms 80 ms Leftover budget 0-400 ms 500-760 ms Network overhead of one HTTP request! @igrigorik
  14. 14. Some WebPerf Problems can’t be fixed within Phoenix. If your page initially loads 3 MB of JavaScript it will never be fast.
  15. 15. Webpage Rendering Basics
  16. 16. Network HTML DOM CSS CSSOM JavaScript Render tree Layout Paint
  17. 17. Network HTML DOM CSS CSSOM JavaScript Render tree Layout Paint
  18. 18. Network HTML DOM CSS CSSOM JavaScript Render tree Layout Paint
  19. 19. Network HTML DOM CSS CSSOM JavaScript Render tree Layout Paint
  20. 20. Network HTML DOM CSS CSSOM JavaScript Render tree Layout Paint
  21. 21. This rendering process takes a minimum of 100 ms which we have to subtract from the 1,000 ms.
  22. 22. Download a file with HTTP 1.1 over TCP
  23. 23. Latency clientZeit 0 ms 80 ms 160 ms 240 ms 320 ms 10 TCP Segmente (14.600 Bytes) 20 TCP Segmente (29.200 Bytes) 40 TCP Segmente (15.592 Bytes) server SYN ACK ACK GET SYN,ACK ACK ACK
  24. 24. TCP Slow-Start KB 0 55 110 165 220 Roundtrip 1. 2. 3. 4. 214KB 100KB 43KB 14KB 114KB 57KB 29KB 14KB
  25. 25. HTTP 2 you can parallel download multiple files over the same TCP connection. Plus better header compression. Plus push, … More on that later.
  26. 26. Waterfall www.webpagetest.org https://rubyconference.by
  27. 27. 3G Run => https://www.webpagetest.org/result/ 180412_BK_4b5755e7d0717d0f218a88edf5a691a9/
  28. 28. LTE Run => https://www.webpagetest.org/result/ 180412_6W_0697a100d965b385b53917b77d51d081/
  29. 29. LTE run => https://www.webpagetest.org/result/ 180316_5B_de436fb724593d7b746b9c1f89ff3c2d/
  30. 30. Can I interest you in a Rails caching example? It’s so much better in Phoenixland but we can learn from it.
  31. 31. Example Online Shop
  32. 32. $ rails new shop $ cd shop $ rails g scaffold Category name $ rails g scaffold Product category:references name description price:decimal{8,2} $ rails g scaffold User email first_name last_name password_digest $ rails g scaffold Review user:references product:references rating:integer $ rails db:migrate
  33. 33. Shop domain model Category name string Product description text name string price decimal (8,2) Review rating integer User email string first_name string last_name string password_digest string
  34. 34. app/models/product.rb: class Product < ApplicationRecord belongs_to :category has_many :reviews def number_of_stars if reviews.any? reviews.average(:rating).round else nil end end end
  35. 35. db/seeds.rb: Category.create(name: "A") Category.create(name: "B") Category.create(name: "C") 100.times do Product.create(name: Faker::Food.dish, description: Faker::Food.description, category: Category.all.sample, price: rand(20)) end 50.times do user = User.create(first_name: Faker::Name.first_name, last_name: Faker::Name.last_name) products = Product.all 3.times do Review.create(user: user, product: products.sample, rating: rand(5)) end end
  36. 36. app/views/products/index.html.erb: <table class="table table-striped"> […] <tbody> <% @products.each do |product| %> <tr> <td><%= product.category.name %></td> <td><%= product.name %></td> <td><%= product.description %></td> <td><%= number_to_currency(product.price) %></td> <td> <% product.number_of_stars.to_i.times do %> <img src="<%= asset_path( 'star.svg' ) %>“ /> <% end %> </td> […] </tr> <% end %> </tbody> </table>
  37. 37. Completed 200 OK in 497ms (Views: 460.4ms | ActiveRecord: 34.1ms) Development env.
  38. 38. The (short) life of our 1000 ms budget 3G (200 ms RTT) 4G(80 ms RTT) Control plane (200-2500 ms) (50-100 ms) DNS lookup 200 ms 80 ms TCP Connection 200 ms 80 ms TLS handshake (200-400 ms) (80-160 ms) HTTP request 200 ms 80 ms Leftover budget 0-400 ms 500-760 ms Network overhead of one HTTP request! @igrigorik 497ms And we don’t have product images yet.
  39. 39. Development environment? Are you crazy?!
 
 Hold your horses! I’ll switch to production environment later.
  40. 40. Fragment Caching
  41. 41. $ rails dev:cache Development mode is now being cached. By default caching is disabled in dev env.
  42. 42. 1. Step Single Row
  43. 43. app/views/products/index.html.erb: <tbody> <% @products.each do |product| %> <% cache product do %> <tr> <td><%= product.category.name %></td> <td><%= product.name %></td> <td><%= product.description %></td> <td><%= number_to_currency(product.price) %></td> <td> <% product.number_of_stars.to_i.times do %> <img src="<%= asset_path( 'star.svg' ) %>" /> <% end %> </td> [...] </tr> <% end %> <% end %> </tbody>
  44. 44. app/views/products/index.html.erb: <tbody> <% @products.each do |product| %> <% cache product do %> <tr> <td><%= product.category.name %></td> <td><%= product.name %></td> <td><%= product.description %></td> <td><%= number_to_currency(product.price) %></td> <td> <% product.number_of_stars.to_i.times do %> <img src="<%= asset_path( 'star.svg' ) %>" /> <% end %> </td> [...] </tr> <% end %> <% end %> </tbody>
  45. 45. app/models/review.rb: class Review < ApplicationRecord belongs_to :user belongs_to :product, touch: true end Product description text name string price decimal (8,2) Review rating integer User email string first_name string last_name string password_digest string
  46. 46. Total Views Activerecord Vanilla 497ms 460 ms 34 ms Fragment Cache Row 79 ms 74,6 ms 1 ms
  47. 47. 2. Step The Complete Table => Russian Doll
  48. 48. app/views/products/index.html.erb: <tbody> <% cache @products do %> <% @products.each do |product| %> <% cache product do %> <tr> <td><%= product.category.name %></td> <td><%= product.name %></td> <td><%= product.description %></td> <td><%= number_to_currency(product.price) %></td> <td> <% product.number_of_stars.to_i.times do %> <img src="<%= asset_path( 'star.svg' ) %>" /> <% end %> </td> […] </tr> <% end %> <% end %> <% end %> </tbody>
  49. 49. app/views/products/index.html.erb: <tbody> <% cache @products do %> <% @products.each do |product| %> <% cache product do %> <tr> <td><%= product.category.name %></td> <td><%= product.name %></td> <td><%= product.description %></td> <td><%= number_to_currency(product.price) %></td> <td> <% product.number_of_stars.to_i.times do %> <img src="<%= asset_path( 'star.svg' ) %>" /> <% end %> </td> […] </tr> <% end %> <% end %> <% end %> </tbody>
  50. 50. Total Views Activerecord Vanilla 497ms 460 ms 34 ms Fragment Cache Row 79 ms 74,6 ms 1 ms Fragment Cache Table 53 ms 49,5 ms 0,6 ms
  51. 51. Use the Database!
  52. 52. app/models/product.rb: class Product < ApplicationRecord belongs_to :category has_many :reviews def number_of_stars if reviews.any? reviews.average(:rating).round else nil end end end
  53. 53. app/models/product.rb: class Product < ApplicationRecord belongs_to :category has_many :reviews end $ rails g migration AddNumberOfStarsToProduct number_of_stars:integer $ rails db:migrate
  54. 54. app/models/product.rb: class Review < ApplicationRecord belongs_to :user belongs_to :product, touch: true after_create :recalculate_product_rating after_destroy :recalculate_product_rating private def recalculate_product_rating rating = product.reviews.average(:rating).round if rating != self.product.number_of_stars product.update_attribute(:number_of_stars, rating) end end end
  55. 55. Total Views Activerecord Vanilla 497ms 460 ms 34 ms Fragment Cache Row 79 ms 74,6 ms 1 ms Fragment Cache Table 53 ms 49,5 ms 0,6 ms Plus Database Improvents 40 ms 39 ms 0,5 ms Production Env. 35 ms 34 ms 0,5 ms
  56. 56. Warning: 
 Fragment Caching is slower the 1st request! Why? Rails checks if the Fragment Cache exists. When it doesn’t it renders the view and writes the cache which is time consuming.
  57. 57. Need More Speed? You are at the right place! ;-) Have a look at http://phoenixframework.org Phoenix takes 5 ms for the same page. BTW: without caching
  58. 58. HTTP Caching
  59. 59. Web browsers and proxies don‘t want to fetch identical resources multiple times.
  60. 60. The idea of Etags and Last-Modified
  61. 61. Web browser: „My user wants to fetch xyz.html. I cached a copy last week. Is that still good?“
  62. 62. Web server: „xyz.html hasn‘t changed since last week. Go a head with your copy!“ aka 304 Not Modified
  63. 63. > curl -I http://0.0.0.0:3000/products HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "9a779b80e4b0ac3c60d29807e302deb7" [...] > curl -I http://0.0.0.0:3000/products HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "fa8fc1e981833a6885b583d351c4d823"
  64. 64. > curl -I http://0.0.0.0:3000/products HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "9a779b80e4b0ac3c60d29807e302deb7" [...] > curl -I http://0.0.0.0:3000/products HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "fa8fc1e981833a6885b583d351c4d823"
  65. 65. Set the Etag class ProductsController < ApplicationController # GET /products def index @products = Product.all fresh_when :etag => @products end [...]

  66. 66. > curl -I http://0.0.0.0:3000/products -c cookies.txt HTTP/1.1 200 OK Etag: "4d348810e69400799e2ab684c0ef4777" > curl -I http://0.0.0.0:3000/products -b cookies.txt HTTP/1.1 200 OK Etag: "4d348810e69400799e2ab684c0ef4777" The cookie is needed for the CSRF-Token.
  67. 67. > curl -I http://0.0.0.0:3000/products -b cookies.txt --header 'If-None-Match: "4d348810e69400799e2ab684c0ef4777"' HTTP/1.1 304 Not Modified Etag: "4d348810e69400799e2ab684c0ef4777" 304!
  68. 68. Win-Win of a 304 • The Browser doesn’t have to download everything. • The Server doesn’t have to render the view which is the most time consuming bit.
  69. 69. Not good enough?
  70. 70. Writing the initial cache wastes a lot of time.
  71. 71. Let‘s preheat the cache in off business hours! Cron is your friend.
  72. 72. Use the night to preheat your cache. And don‘t be afraid of brute force!
  73. 73. A U T O B A H N
  74. 74. The fastest page is delivered by Nginx without ever contacting Phoenix or Ruby on Rails.
  75. 75. ├── Gemfile ├── [...] ├── public │   ├── 404.html │   ├── 422.html │   ├── 500.html │   ├── favicon.ico │   └── robots.txt ├── [...] That‘s already done for the files in the public directory.
  76. 76. Add caches_page to your controller to save views as static gz files in your public directory: caches_page :index, :show, :gzip => :true Add gem actionpack-page_caching for Rails 5.2
  77. 77. Brute Force is your friend! During the night the server has a hard time to stay awake any way.
  78. 78. Tricky part: How to delete out of date gz files?
  79. 79. after_update :expire_cache before_destroy :expire_cache private def expire_cache ActionController::Base.expire_page(Rails.application.routes.url_h elpers.company_path(self)) ActionController::Base.expire_page(Rails.application.routes.url_h elpers.companies_path) end app/models/product.rb
  80. 80. caches_page vs. !current_user.nil? ???
  81. 81. caches_page is good to cache customized user content too. It just takes more thinking.
  82. 82. Let us assume a user base of 10,000,000 people.
  83. 83. /tmp ᐅ wget http://www.railsconf.com/2013/talks --2013-04-27 21:04:24-- http://www.railsconf.com/2013/talks Resolving www.railsconf.com... 107.20.162.205 Connecting to www.railsconf.com|107.20.162.205|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/html] Saving to: ‘talks’ [ <=> ] 74,321 258KB/ s in 0.3s 2013-04-27 21:04:25 (258 KB/s) - ‘talks’ saved [74321] /tmp ᐅ du -hs talks 76K talks /tmp ᐅ gzip talks /tmp ᐅ du -hs talks.gz 28K talks.gz /tmp ᐅ
  84. 84. /tmp ᐅ du -hs talks.gz 28K talks.gz 28K * 10,000,000 = 0,26 TB
  85. 85. 28K * 10,000,000 = 0,26 TB Harddrive space is cheap. By saving the files non-gz and using a data deduplication file system you just need 5-10% of the 0,26 TB. Nginx can gzip the files on the fly.
  86. 86. Nginx will happily read a cookie and find the pre- rendered page in a given directory structure.
  87. 87. HTTP/1.1 vs. HTTP/2
  88. 88. HTTP/2 provides an average WebPerformance improvement of 20%. It’s a no-brainer. You have to use it!
  89. 89. CDNs, HTTP/2 and Rails 5.2
  90. 90. Long story short:
 In most cases where a CDN made sense with HTTP/1.1 it doesn’t make sense any more. Just deliver everything from your Rails server!
  91. 91. Image Formats It is still an often underused way of saving bandwidth.
  92. 92. JPEG, PNG or WebP
  93. 93. https://caniuse.com/#feat=webp
  94. 94. CSS
  95. 95. Less CSS = Faster Webpages Do not underestimate the WebPerformance impact of optimized CSS.
  96. 96. Webfonts
  97. 97. If possible don’t use Webfonts.
  98. 98. Apache vs. Nginx
  99. 99. It doesn’t matter! Just use your favorite one.
  100. 100. Brotli vs. gzip
  101. 101. Always offer both! Brotli can be used to save bandwidth and CPU-Resources.
  102. 102. Heroku vs. Bare Metal
  103. 103. Heroku is good for a quick start but has never been a good choice for good WebPerformance. Bare Metal is the way to go if you need maximum WebPerformance.
 
 BTW: It’s cheaper too.
  104. 104. P R E L O A D I N G U N D P R E F E T C H I N G
  105. 105. P R E L O A D I N G U N D P R E F E T C H I N G <link rel="dns-prefetch"... <link rel="prefetch"... DNS pre-resolution TCP pre-connect prefresh preloader
  106. 106. M A N U A L D N S - P R E F E T C H <link rel="dns-prefetch" href="//abc.com"> http://www.chromium.org/developers/design-documents/dns-prefetching „Most common names like google.com and yahoo.com are resolved so often that most local ISP's name resolvers can answer in closer to 80-120ms. If the domain name in question is an uncommon name, then a query may have to go through numerous resolvers up and down the hierarchy, and the delay can average closer to 200-300ms.“
  107. 107. P R E F E T C H <link rel="prefetch" href=„http://abc.com/important.js"> http://www.whatwg.org/specs/web-apps/current-work/#link-type-prefetch „The prefetch keyword indicates that preemptively fetching and caching the specified resource is likely to be beneficial, as it is highly likely that the user will require this resource.“ T I P P : " A C C E P T - R A N G E S : B Y T E S “ H E A D E R
  108. 108. Y O U C A N T E L L N G I N X T O P U S H T H O S E F I L E S V I A H T T P / 2 .
  109. 109. The Most Important Tool?
  110. 110. Set a Time Budget! If you run out of your time budget you have to cancel features on your website.
  111. 111. Is WebPerformance really so hard?
  112. 112. The WebPerf Bible. => https://hpbn.co
  113. 113. @wintermeyer
  114. 114. sw@wintermeyer-consulting.de last name | twitter | github e-mail

×