1.000 Milisekunden

1.000 Milisekunden

Vortrag über PageSpeed Optimierung (Ladezeiten) auf der International PHP 2013 Conference in München.

Ad005fac83baa60843ddf2bc3bc8fe93?s=128

Stefan Wintermeyer

October 29, 2013
Tweet

Transcript

  1. 1 . 0 0 0 M I L L I

    S E K U N D E N S T E FA N W I N T E R M E Y E R
  2. 1.000 ms Page Loading Time ist der Mount Everest.

  3. P H P C O N F E R E

    N C E . C O M / 2 0 1 3 / E N
  4. W W W. G O O G L E .

    C O M
  5. 0.6s 0.8s

  6. I S T D A S W I C H

    T I G ?
  7. What's the impact of slow sites? Lower conversions and engagement,

    higher bounce rates... Ilya Grigorik @igrigorik Make The Web Faster, Google
  8. Performance Related Changes and their User Impact Web Search Delay

    Experiment @igrigorik • The cost of delay increases over time and persists • Delays under half a second impact business metrics • "Speed matters" is not just lip service Type of Delay Delay (ms) Duration (weeks) Impact on Avg. Daily Searches Pre-header 50 4 Not measurable Pre-header 100 4 -0.20% Post-header 200 6 -0.59% Post-header 400 6 -0.59% Post-ads 200 4 -0.30%
  9. Performance Related Changes and their User Impact Server Delays Experiment

    • Strong negative impacts • Roughly linear changes with increasing delay • Time to Click changed by roughly double the delay @igrigorik
  10. Yo ho ho and a few billion pages of RUM

    How speed affects bounce rate @igrigorik
  11. 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
  12. 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
  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. V E R S TA N D E N !

    W I E G E H T E S W E I T E R ?
  15. •Caching •Prefetching •SPDY •Misc •Lazy Boy quick Fix

  16. P H P, R U B Y O N R

    A I L S O D E R D J A N G O ? ! E G A L !
  17. J P E G , G I F, P N

    G O D E R W E B P ? ! K L E I N V I E H N I E U N T E R S C H ÄT Z E N !
  18. C A C H I N G

  19. Example Plattform Raspberry Pi

  20. Software Stack • Ruby on Rails 3.2 (use 4.0 if

    you can) • Ruby 1.9.3 (use 2.0 if you can) • Nginx • Unicorn • MySQL • Raspbian Wheezy
  21. Example: Online Shop

  22. The Models

  23. Cart state string LineItem price decimal (8,2) quantity integer Category

    name string Product description text name string price decimal (8,2) DiscountGroup discount integer name string User email string ∗ encrypted_password string ∗ first_name string last_name string Rating value integer
  24. The UI

  25. None
  26. current_user

  27. None
  28. None
  29. None
  30. Bob‘s cart

  31. None
  32. Discounted price

  33. Rate this product

  34. Measure the Success

  35. Watir script http://watir.com

  36. require 'watir-webdriver' ! url = 'http://ip.address:3000' b = Watir::Browser.new !

    # An anonymous user looks around. # ['AA', 'AB', 'AH'].each do |product_name| b.goto url Watir::Wait.until { b.link(:text, product_name).exist? } b.link(:text, product_name).click Watir::Wait.until { b.link(:text, 'Webshop').exist? } b.link(:text, 'Webshop').click end 3 different products
  37. None
  38. bob@aol.com

  39. Login Bob b.goto url b.goto "#{url}/users/sign_in" b.text_field(:id, "user_email").set("bob@aol.com") b.text_field(:id, "user_password").set("123")

    b.button(:name, "commit").click
  40. Bob looks around too ['AA', 'AB', 'AH'].each do |product_name| b.goto

    url Watir::Wait.until { b.link(:text, product_name).exist? } b.link(:text, product_name).click Watir::Wait.until { b.link(:text, 'Webshop').exist? } b.link(:text, 'Webshop').click end
  41. Bob rates products ['AA', 'AD', 'AI'].each do |product_name| b.goto url

    b.link(:text, product_name).click star_id = 'product_' + product_name + '_rating_2' b.link(:id, star_id).click end
  42. Bob fills his cart [1, 3, 5].each do |product_id| b.goto

    url add_button_id = 'add_' + product_id.to_s b.link(:id, add_button_id).click end
  43. Logout b.link(:text, 'Logout').click

  44. Total: 43 webpages

  45. 30 sec. localhost demo

  46. None
  47. How fast or slow is on the Raspberry Pi?

  48. 0 29 58 87 116 Vanilla 116 s

  49. / Started GET "/" for x.x.x.x at 2013-02-28 21:05:34 +0000

    Processing by ProductsController#index as HTML Rendered products/index.html.haml within layouts/application (3022.3ms) Rendered layouts/_navbar.html.haml (12.5ms) Rendered layouts/_footer.html.haml (2.9ms) Completed 200 OK in 3097ms (Views: 3013.3ms | ActiveRecord: 72.6ms)
  50. Started GET "/" for x.x.x.x at 2013-02-28 21:05:34 +0000 Processing

    by ProductsController#index as HTML Rendered products/index.html.haml within layouts/application (3022.3ms) Rendered layouts/_navbar.html.haml (12.5ms) Rendered layouts/_footer.html.haml (2.9ms) Completed 200 OK in 3097ms (Views: 3013.3ms | ActiveRecord: 72.6ms) 3013.3ms
  51. Fragment Caching aka Easy Money

  52. Vanilla HAML Version %table.table.table-striped{:id => 'products'} %thead %tr %th Name

    %th Category [...] %tbody - @products.each do |product| %tr %td= link_to product.name, [...] %td= product.description
  53. Russian Doll - cache [current_user, @products] do %table.table.table-striped{:id => 'products'}

    %thead %tr %th Name %th Category [...] %tbody - @products.each do |product| - cache [current_user, product] do %tr %td= link_to product.name, [...] %td= product.description complete table single row
  54. complete table single row

  55. Add these line to your Gemfile: gem 'dalli' gem 'cache_digests'

    ! Don‘t forget to run bundle install.
  56. production.rb: config.cache_store = :dalli_store

  57. A performance boost by 5 LOC?!

  58. 0 30 60 90 120 Vanilla Fragment Caching (4 LOC)

    116 s 60 s
  59. 5 LOC result in 50% fewer servers!

  60. But, it‘s all about the details.

  61. •Each row and each table (#index). •Each #show content. •The

    shopping cart. •The navigation bar and the footer.
  62. Don‘t just use a plain russian doll cache.  !

    Cluster the dolls!
  63. Cluster of 10 Cluster of 10

  64. 2 hours of optimizing save another 15 s.

  65. 0 30 60 90 120 Vanilla Fragment Caching 116 s

    45 s
  66. IMPORTANT: You need a clean  data structure!

  67. HTTP Caching

  68. Web browsers and proxies don‘t want to fetch webpages twice.

     ! They use Last-Modified and Etag to avoid that.
  69. The idea of Last-Modified

  70. Web browser:  „My user wants to fetch xyz.html. I

    cached a copy last week.  Is that still good?“
  71. Web server:  „xyz.html hasn‘t changed since last week. 

    Go a head with your copy!“ ! aka 304 Not Modified
  72. The curl version for Etag

  73. > curl -I http://0.0.0.0:3000/ 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/ HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "fa8fc1e981833a6885b583d351c4d823"
  74. > curl -I http://0.0.0.0:3000/ 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/ HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 X-Ua-Compatible: IE=Edge Etag: "fa8fc1e981833a6885b583d351c4d823"
  75. Set the Etag class ProductsController < ApplicationController # GET /products

    def index @products = Product.limit(25) ! fresh_when :etag => [current_user,
 @products.order(:updated_at).last] end ! [...]

  76. Set the Etag class ProductsController < ApplicationController # GET /products

    def index @products = Product.limit(25) ! fresh_when :etag => [current_user,
 @products.order(:updated_at).last] end ! [...]

  77. > curl -I http://0.0.0.0:3000/ -c cookies.txt HTTP/1.1 200 OK Etag:

    "4d348810e69400799e2ab684c0ef4777" ! ! > curl -I http://0.0.0.0:3000/ -b cookies.txt HTTP/1.1 200 OK Etag: "4d348810e69400799e2ab684c0ef4777" The cookie is needed for the CSRF-Token.
  78. > curl -I http://0.0.0.0:3000/ -b cookies.txt --header 'If-None-Match: "4d348810e69400799e2ab684c0ef4777"' HTTP/1.1

    304 Not Modified Etag: "4d348810e69400799e2ab684c0ef4777" 304!
  79. The beauty of HTTP caching is that the view isn‘t

    rendered.
  80. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    45 s 35 s
  81. Not good enough?

  82. Writing the initial cache wastes a lot of time.

  83. Let‘s preheat the cache in off business hours!

  84. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    Preheater 26 s
  85. Use the night to preheat your cache. ! And don‘t

    be afraid of brute force!
  86. A U T O B A H N

  87. The fastest page is delivered by Nginx without ever contacting

    Ruby on Rails.
  88. ᵓᴷᴷ Gemfile ᵓᴷᴷ [...] ᵓᴷᴷ public ᴹ ᵓᴷᴷ 404.html ᴹ

    ᵓᴷᴷ 422.html ᴹ ᵓᴷᴷ 500.html ᴹ ᵓᴷᴷ favicon.ico ᴹ ᵋᴷᴷ robots.txt ᵓᴷᴷ [...] That‘s already done for the files in the public directory.
  89. Is there an easy way to save complete pages which

    are rendered by the Rails framework?
  90. 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 4.0
  91. Brute Force is your friend! ! During the night the

    server has a hard time to stay awake any way.
  92. Tricky part: How to delete out of date gz files?

  93. 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
  94. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    Preheater Page Caching 19 s 6.1 x faster!
  95. caches_page vs. !current_user.nil? ! ???

  96. caches_page is good to cache customized user content too. !

    It just takes more thinking.
  97. Let us assume a user base of 10,000,000 people.

  98. /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 ᐅ
  99. /tmp ᐅ du -hs talks.gz 28K talks.gz 28K * 10,000,000

    = 0,26 TB
  100. 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.
  101. Nginx will happily read a cookie and find the pre-

    rendered page in a given directory structure.
  102. To setup a complex page_cache system is a lot of

    work. You have to tackle not only Rails but Nginx too. It does increase the snappiness of your application but might not be worth the effort for small systems.
  103. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    Preheater Page Caching 19 s Any chance to get a single digit here?
  104. Add Ember.js to your software stack! ! http://emberjs.com rails new

    testapp -m http://emberjs.com/edge_template.rb
  105. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    Preheater Page Caching Ember.js 8 s 14.5 x faster!
  106. W H AT E R FA L L

  107. http://www.webpagetest.org/result/131029_MB_MVS/

  108. Daumen-Regel: Nie länger als ein Browserfenster.

  109. C D N

  110. C D N S Man sollte wissen was man tut!

    ! Sonst bringt der Einsatz von Content Delivery Networks (CDN) nichts.
  111. I N L I N I N G

  112. I N L I N I N G • Es

    kann Sinn machen CSS und JavaScript in die HTML- Seite zu packen. • Beispiele: • Sehr wenig CSS oder JavaScript. • Schnelle Ladezeit bei der ersten Seite. • Einsatz von TurboLinks.
  113. I N L I N I N G V O

    N B I L D E R N Auch das kann Sinn machen. Man sollte aber hier darauf achten, wie lange der Browser braucht das Bild zu „entpacken“. Auf langsamen Geräten kann das länger dauern, als der Download einer eigenen Datei. ! Vorsicht!
  114. P R E L O A D I N G

    U N D P R E F E T C H I N G
  115. 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"...! <link rel="prerender"...! ! DNS pre-resolution TCP pre-connect prefresh preloader
  116. 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.“
  117. 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 : " ACCEPT-RANGES: BYTES“ H E A D E R
  118. P R E R E N D E R <link

    rel="prerender" href=„http://abc.com/faq.html“>!
  119. S P D Y

  120. http://en.wikipedia.org/wiki/SPDY

  121. D E F I N I E R E N

    S I E A M A N FA N G D E R A R B E I T E I N E W U N S C H L A D E Z E I T.
  122. Der erste Eindruck sprich die erste Seite ist am wichtigsten.

    Aber auch weitere Seiten müssen schnell zu laden sein.
  123. L A Z Y B O Y Q U I

    C K F I X
  124. A L L E S V E R S TA

    N D E N , A B E R V O R M W E I H N A C H T S G E S C H Ä F T K E I N E Z E I T M E H R ?
  125. https://developers.google.com/speed/pagespeed/

  126. S H E R PA S H E L F

    E N G E R N E A U F D E N M O U N T E V E R E S T.
  127. stefan.wintermeyer@amooma.de first name last name | twitter | github company

    name lang e-mail