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

    View Slide

  2. 1.000 ms Page Loading Time ist
    der Mount Everest.

    View Slide

  3. P H P C O N F E R E N C E . C O M / 2 0 1 3 / E N

    View Slide

  4. W W W. G O O G L E . C O M

    View Slide

  5. 0.6s 0.8s

    View Slide

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

    View Slide

  7. What's the impact of slow sites?
    Lower conversions and engagement, higher bounce rates...
    Ilya Grigorik @igrigorik
    Make The Web Faster, Google

    View Slide

  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%

    View Slide

  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

    View Slide

  10. Yo ho ho and a few billion pages of RUM
    How speed affects bounce rate
    @igrigorik

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 ?

    View Slide

  15. •Caching
    •Prefetching
    •SPDY
    •Misc
    •Lazy Boy
    quick Fix

    View Slide

  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 !

    View Slide

  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 !

    View Slide

  18. C A C H I N G

    View Slide

  19. Example Plattform
    Raspberry Pi

    View Slide

  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

    View Slide

  21. Example:
    Online Shop

    View Slide

  22. The Models

    View Slide

  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

    View Slide

  24. The UI

    View Slide

  25. View Slide

  26. current_user

    View Slide

  27. View Slide

  28. View Slide

  29. View Slide

  30. Bob‘s cart

    View Slide

  31. View Slide

  32. Discounted price

    View Slide

  33. Rate this product

    View Slide

  34. Measure the
    Success

    View Slide

  35. Watir script
    http://watir.com

    View Slide

  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

    View Slide

  37. View Slide

  38. View Slide

  39. Login Bob
    b.goto url
    b.goto "#{url}/users/sign_in"
    b.text_field(:id,
    "user_email").set("[email protected]")
    b.text_field(:id, "user_password").set("123")
    b.button(:name, "commit").click

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  43. Logout
    b.link(:text, 'Logout').click

    View Slide

  44. Total: 43 webpages

    View Slide

  45. 30 sec. localhost demo

    View Slide

  46. View Slide

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

    View Slide

  48. 0
    29
    58
    87
    116
    Vanilla
    116 s

    View Slide

  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)

    View Slide

  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

    View Slide

  51. Fragment
    Caching
    aka Easy Money

    View Slide

  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

    View Slide

  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

    View Slide

  54. complete table
    single row

    View Slide

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

    !
    Don‘t forget to run bundle install.

    View Slide

  56. production.rb:
    config.cache_store = :dalli_store

    View Slide

  57. A performance
    boost by 5
    LOC?!

    View Slide

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

    View Slide

  59. 5 LOC result in
    50% fewer
    servers!

    View Slide

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

    View Slide

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

    •Each #show content.

    •The shopping cart.

    •The navigation bar and the footer.

    View Slide

  62. Don‘t just use a plain
    russian doll cache.
    !
    Cluster the dolls!

    View Slide

  63. Cluster of 10
    Cluster of 10

    View Slide

  64. 2 hours of optimizing
    save another 15 s.

    View Slide

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

    View Slide

  66. IMPORTANT:
    You need a clean
    data structure!

    View Slide

  67. HTTP Caching

    View Slide

  68. Web browsers and proxies
    don‘t want to fetch webpages
    twice.
    !
    They use Last-Modified and Etag
    to avoid that.

    View Slide

  69. The idea of Last-Modified

    View Slide

  70. Web browser:
    „My user wants to fetch xyz.html.
    I cached a copy last week.
    Is that still good?“

    View Slide

  71. Web server:
    „xyz.html hasn‘t changed since
    last week.
    Go a head with your copy!“
    !
    aka 304 Not Modified

    View Slide

  72. The curl version for Etag

    View Slide

  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"

    View Slide

  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"

    View Slide

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


    View Slide

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


    View Slide

  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.

    View Slide

  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!

    View Slide

  79. The beauty of HTTP
    caching is that the
    view isn‘t rendered.

    View Slide

  80. 0
    30
    60
    90
    120
    Vanilla Fragment Caching HTTP Cache
    45 s 35 s

    View Slide

  81. Not good enough?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  85. Use the night to
    preheat your cache.
    !
    And don‘t be afraid
    of brute force!

    View Slide

  86. A U T O B A H N

    View Slide

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

    View Slide

  88. ᵓᴷᴷ Gemfile

    ᵓᴷᴷ [...]

    ᵓᴷᴷ public

    ᴹ ᵓᴷᴷ 404.html

    ᴹ ᵓᴷᴷ 422.html

    ᴹ ᵓᴷᴷ 500.html

    ᴹ ᵓᴷᴷ favicon.ico

    ᴹ ᵋᴷᴷ robots.txt

    ᵓᴷᴷ [...]
    That‘s already
    done for the
    files in the
    public directory.

    View Slide

  89. Is there an easy way to
    save complete pages
    which are rendered by
    the Rails framework?

    View Slide

  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

    View Slide

  91. Brute Force is your friend!
    !
    During the night the server has a
    hard time to stay awake any way.

    View Slide

  92. Tricky part:

    How to delete out of date
    gz files?

    View Slide

  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

    View Slide

  94. 0
    30
    60
    90
    120
    Vanilla Fragment Caching HTTP Cache Preheater Page Caching
    19 s
    6.1 x faster!

    View Slide

  95. caches_page
    vs.
    !current_user.nil?
    !
    ???

    View Slide

  96. caches_page is good to cache
    customized user content too.
    !
    It just takes more thinking.

    View Slide

  97. Let us assume a user base
    of 10,000,000 people.

    View Slide

  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 ᐅ

    View Slide

  99. /tmp ᐅ du -hs talks.gz
    28K talks.gz
    28K * 10,000,000 = 0,26 TB

    View Slide

  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.

    View Slide

  101. Nginx will happily read a
    cookie and find the pre-
    rendered page in a given
    directory structure.

    View Slide

  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.

    View Slide

  103. 0
    30
    60
    90
    120
    Vanilla Fragment Caching HTTP Cache Preheater Page Caching
    19 s
    Any chance to
    get a single
    digit here?

    View Slide

  104. Add Ember.js to your software stack!
    !
    http://emberjs.com
    rails new testapp -m http://emberjs.com/edge_template.rb

    View Slide

  105. 0
    30
    60
    90
    120
    Vanilla Fragment Caching HTTP Cache Preheater Page Caching Ember.js
    8 s
    14.5 x faster!

    View Slide

  106. W H AT E R FA L L

    View Slide

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

    View Slide

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

    View Slide

  109. C D N

    View Slide

  110. C D N S
    Man sollte wissen was man tut!
    !
    Sonst bringt der Einsatz von Content Delivery
    Networks (CDN) nichts.

    View Slide

  111. I N L I N I N G

    View Slide

  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.

    View Slide

  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!

    View Slide

  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

    View Slide

  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
    !
    DNS pre-resolution
    TCP pre-connect
    prefresh
    preloader

    View Slide

  116. M A N U A L D N S - P R E F E T C H

    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.“

    View Slide

  117. P R E F E T C H
    !
    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

    View Slide

  118. P R E R E N D E R
    !

    View Slide

  119. S P D Y

    View Slide

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

    View Slide

  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.

    View Slide

  122. Der erste Eindruck sprich die erste Seite ist am
    wichtigsten. Aber auch weitere Seiten müssen schnell zu
    laden sein.

    View Slide

  123. L A Z Y B O Y Q U I C K F I X

    View Slide

  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 ?

    View Slide

  125. https://developers.google.com/speed/pagespeed/

    View Slide

  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.

    View Slide

  127. [email protected]
    first name last name | twitter | github company name lang
    e-mail

    View Slide