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

Bullet Train on Rails: Improving web app performance w/ tools

Bullet Train on Rails: Improving web app performance w/ tools

Three Ruby-based tools for improving redis cache store performance, identifying N+1 queries, and pre-rendering simple html structures in your Ruby/Rails application

Alyss Noland

June 27, 2017
Tweet

Other Decks in Programming

Transcript

  1. HI, I'M ALYSS Pronounced "Alice" Developer Advocate @ BigCommerce SaaS

    E-Commerce Platform Part-time cider consumer
  2. Refresher on Caching & Key Expiration <% cache("top_products", :expires_in =>

    1.hour) do %> <div id="topSellingProducts"> <% @recent_product = Product.order("units_sold DESC").limit(20) %> <%= render :partial => "product", :collection => @recent_products %> </div> <% end %> cache:views/top_products/3-20160820173416161439/dc2dabdcef7aee08968d3c45b8b9b0de Object cache key MD5 hash of view contents Caching code looks like: Cache key looks like:
  3. Auditing Tools Summary -----------------------------+--------------+-------------------+--------------------- Key | Memory Usage | Expiry

    Proportion | Last Access Time -----------------------------+--------------+-------------------+--------------------- notification_3109439 | 88.14% | 0.0% | 2 minutes user_profile_3897016 | 11.86% | 99.98% | 20 seconds -----------------------------+--------------+-------------------+--------------------- Using redis-audit: snmaynard/redis-audit antirez/redis-sampler
  4. When to use eager loading <tbody> <% @posts.each do |post|

    %> <tr> <td><%= post.title %></td> <td><%= post.body %></td> <td><%= post.author.name %></td> </tr> <% end %> </tbody> Post Load (0.6ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."created_at" DESC Author Load (0.5ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 3]] Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 2]] Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = ? LIMIT 1 [["id", 1]] Code: Resulting N+1 query:
  5. Configuring Bullet # config/environments/development.rb config.after_initialize do Bullet.enable = true Bullet.alert

    = true Bullet.bullet_logger = true Bullet.console = true Bullet.rails_logger = true Bullet.rollbar = true Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ] Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ] end # Gemfile gem 'bullet', group: 'development'
  6. Kill N+1 Queries with Bullet Started GET "/" for ::1

    at 2017-06-23 14:13:25 -0700 ActiveRecord::SchemaMigration Load (0.1ms) SELECT "schema_migrations".* FROM "schema_migrations" Processing by PostsController#index as HTML Post Load (0.2ms) SELECT "posts".* FROM "posts" Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Comment Load (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]] Rendered posts/index.html.erb within layouts/application (24.2ms) Completed 200 OK in 1297ms (Views: 1283.8ms | ActiveRecord: 0.9ms) user: alyss.noland http://localhost:3000/ N+1 Query detected Post => [:comments] Add to your finder: :includes => [:comments] flyerhzm/bullet
  7. Fixing the N+1 Query def index @posts = Post.order(published_at: :desc).joins(:author).select("posts.*,

    authors.name as author_name") end SELECT posts.*, authors.name as author_name FROM "posts" INNER JOIN "authors" ON "authors"."id" = "posts"."author_id" ORDER BY "posts"."created_at" DESC Code: Resulting query:
  8. Pre-rendering with Nokogiri require 'nokogiri' def toc_data(page_content) html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content)

    # get a flat list of headers headers = [] html_doc.css('h1, h2, h3').each do |header| headers.push({ id: header.attribute('id').to_s, content: header.content, level: header.name[1].to_i, children: [] }) end [3,2].each do |header_level| header_to_nest = nil headers = headers.reject do |header| if header[:level] == header_level header_to_nest[:children].push header if header_to_nest true else header_to_nest = header if header[:level] == (header_level - 1) false end end end headers end Limitation: no user-specific content Faster than javascript, but hurts our TTFB