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

RailsConf 2013 - "Cache = Cash"

RailsConf 2013 - "Cache = Cash"

A Railsconf 2013 talk.

Stefan Wintermeyer

April 30, 2013
Tweet

More Decks by Stefan Wintermeyer

Other Decks in Programming

Transcript

  1. Today we only discuss dynamic HTML which gets rendered views!

    The asset pipeline does a decent job. But never the less there is a lot of room for improvement.
  2. If you can deliver a webpage in half the time

    you only need half the amount of servers.
  3. What's the impact of slow sites? Lower conversions and engagement,

    higher bounce rates... Ilya Grigorik @igrigorik Make The Web Faster, Google Have a look at Ilya‘s book!
  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 The broswer needs 100 - 150ms to render the page.
  10. Software Stack • Ruby on Rails 3.2 (no need to

    wait till 4.0) • Ruby 1.9.3 • Nginx • Unicorn • MySQL • Raspbian Wheezy
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. / 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)
  17. 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
  18. 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
  19. 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
  20. •Each row and each table (#index). •Each #show content. •The

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

    They use Last-Modified and Etag to avoid that.
  22. Web browser: „Hello web server. How‘s life? My user wants

    to have a look at xyz.html. I cached a copy last week. Is that still good?“
  23. Web server: „Hi! Good to see you again! xyz.html hasn‘t

    changed since last week. Have a nice day!“ aka 304 Not Modified
  24. > 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"
  25. > 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"
  26. 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 [...]
  27. 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 [...]
  28. > 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.
  29. ᵓᴷᴷ Gemfile ᵓᴷᴷ [...] ᵓᴷᴷ public ᴹ ᵓᴷᴷ 404.html ᴹ

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

    are rendered by the Rails framework?
  31. Add caches_page to your controller to save views as static

    gz files in your public directory: caches_page :index, :show, :gzip => :true
  32. Again: Brute Force is your friend. The server has a

    hard time to keep awake at night any way.
  33. 0 30 60 90 120 Vanilla Fragment Caching HTTP Cache

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

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

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

    -m http://emberjs.com/edge_template.rb Find Tom Dale and Yahuda Katz at the conference for more Ember.js info.
  40. 0 37,5 75 112,5 150 Vanilla Fragment CachingHTTP Cache Preheater

    Page Caching Ember.js 8 s 14.5 x faster!
  41. Thank you! [email protected] @wintermeyer Contact me if you need an

    performance audit of your existing Web application.