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

Cache = Cash

Cache = Cash

Don't waste money and computer resources because of lazy coding.

Stefan Wintermeyer

March 01, 2013
Tweet

More Decks by Stefan Wintermeyer

Other Decks in Programming

Transcript

  1. Stefan Wintermeyer • Author of a Ruby on Rails book:

    http://xyzpub.com/en/ ruby-on-rails/3.2/ • @wintermeyer
  2. Why should we care? I borrow some data from Ilya

    Grigor for this. http://www.igvita.com/
  3. Delay User reaction 0 - 1 s :-)) 1 -

    2 s :-) 2 - 3 s hmmm... 3 - 4 s :-( > 4 s :-((
  4. •We use the same test procedure for every time measurement.

    •That means that actual data is changed in the SQL database. •Every page has to show the current data set (no guestimations).
  5. The Basic Setup • Ruby on Rails 3.2 • Ruby

    1.9.3 • Nginx • Unicorn • MySQL • Raspbian Wheezy
  6. 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
  7. require 'watir-webdriver' url = 'http://0.0.0.0:3000' b = Watir::Browser.new # A

    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
  8. require 'watir-webdriver' url = 'http://0.0.0.0:3000' b = Watir::Browser.new # A

    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
  9. 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
  10. 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
  11. 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
  12. / 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)
  13. Before %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
  14. After - 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
  15. After - 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 one row
  16. •Each row and each table (#index). •Each #show content. •The

    shopping cart. •The navigation bar and the footer.
  17. More fragment caching > grep "cache " app/views/*/* | sed

    "s/.*-//g" cache line_item do cache [current_user, 'navigation_bar'] do cache [current_user, @current_cart, 'application_html'] do cache ['footer'] do cache [current_user, @products, 'products_index_table'] do cache [current_user, product, 'products_index_table_row'] do cache [current_user, @product, 'products_show'] do cache [current_user, @product, 'products_show_ratings_table'] do cache [rating, 'products_show_ratings_table_row'] do >
  18. A browser can use Last-Modified: and Etag: to check if

    a locally cached webpage is still good.
  19. > 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"
  20. > 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"
  21. 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 [...]
  22. 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 [...]
  23. 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 [...]
  24. > 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.
  25. page_cache stores completely rendered HTML pages in a gz-format. Nginx

    is happy to deliver them without bothering Rails at all.
  26. page caching for current_users gets a bit tricky. But nginx

    will happily read the cookie and use a different directory for the cached pages.
  27. Lessons learned • Use the not so busy time (e.g.

    3 am) to preheat your cache. • Harddrive space is cheap. Take advantage of this! • Start to think about caching at the beginning of your development. You need a clean model structure!