Upgrade to Pro — share decks privately, control downloads, hide ads and more …

1.000 Milisekunden

1.000 Milisekunden

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

Stefan Wintermeyer

October 29, 2013
Tweet

More Decks by Stefan Wintermeyer

Other Decks in Technology

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. P H P C O N F E R E

    N C E . C O M / 2 0 1 3 / E N
  3. What's the impact of slow sites? Lower conversions and engagement,

    higher bounce rates... Ilya Grigorik @igrigorik Make The Web Faster, Google
  4. 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%
  5. 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
  6. Yo ho ho and a few billion pages of RUM

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

    W I E G E H T E S W E I T E R ?
  11. 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 !
  12. 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 !
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. / 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)
  20. 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
  21. 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
  22. 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
  23. •Each row and each table (#index). •Each #show content. •The

    shopping cart. •The navigation bar and the footer.
  24. Web browsers and proxies don‘t want to fetch webpages twice.

     ! They use Last-Modified and Etag to avoid that.
  25. Web browser:  „My user wants to fetch xyz.html. I

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

    Go a head with your copy!“ ! aka 304 Not Modified
  27. > 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"
  28. > 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"
  29. 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 ! [...]

  30. 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 ! [...]

  31. > 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.
  32. ᵓᴷᴷ Gemfile ᵓᴷᴷ [...] ᵓᴷᴷ public ᴹ ᵓᴷᴷ 404.html ᴹ

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

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

    server has a hard time to stay awake any way.
  36. 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
  37. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

    Preheater Page Caching 19 s 6.1 x faster!
  38. /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 ᐅ
  39. 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.
  40. Nginx will happily read a cookie and find the pre-

    rendered page in a given directory structure.
  41. 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.
  42. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

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

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

    Preheater Page Caching Ember.js 8 s 14.5 x faster!
  45. C D N S Man sollte wissen was man tut!

    ! Sonst bringt der Einsatz von Content Delivery Networks (CDN) nichts.
  46. 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.
  47. 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!
  48. P R E L O A D I N G

    U N D P R E F E T C H I N G
  49. 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
  50. 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.“
  51. 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
  52. P R E R E N D E R <link

    rel="prerender" href=„http://abc.com/faq.html“>!
  53. 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.
  54. Der erste Eindruck sprich die erste Seite ist am wichtigsten.

    Aber auch weitere Seiten müssen schnell zu laden sein.
  55. 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 ?
  56. 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.